Привет, Хабр! Я написал эту статью для тех, кто с System Design особо не сталкивался. Никаких предварительных знаний не нужно — всё объясню с нуля. Если вы уже знаете, что такое load balancer — местами будет скучно, но, может, в секции про очереди или мониторинг найдёте что-то новое.

Окей, а зачем QA вообще в это лезть?

Честный ответ: потому что без понимания архитектуры вы тестируете вслепую.

Вот реальная ситуация. Тестировщик прогнал все тесты на staging, всё зелёное, деплой в production. Через час — сайт лежит. Оказалось, на staging один сервер, а в production за load balancer-ом стоят три, и при определённом стечении обстоятельств запросы пользователя попадают на разные серверы, а сессия хранится в памяти одного. Тестами это не покроешь, если не знаешь, что вообще бывают load balancer-ы и sticky sessions.

Или вот: QA тестирует API, всё работает. А потом оказывается, что ответы летят из кэша, и реальные данные из базы вообще не те. Если не знаешь, что между клиентом и базой сидит Redis — даже не поймёшь, куда копать.

В общем, System Design для QA — это не про то, чтобы проектировать системы. Это про то, чтобы понимать, где они могут сломаться.

Load Balancer

Начнём с простого. У вас есть веб-приложение, оно крутится на сервере. Один сервер обрабатывает, скажем, 500 запросов в секунду. Пришло 2000 пользователей одновременно — сервер захлебнулся, сайт лёг.

Решение: поставить несколько серверов и перед ними — штуку, которая раскидывает запросы. Это и есть load balancer. По сути, диспетчер. Примерно как в больнице регистратура — вы не сами выбираете, к какому врачу идти, за вас распределяют. Ну, аналогия кривая, потому что в больнице распределяют по специализации, а тут скорее по загруженности, но суть такая.

Есть несколько способов раскидывать запросы:

  • Round Robin — по очереди: первый запрос на сервер 1, второй на сервер 2, третий на сервер 3, четвёртый опять на 1. Тупо, но работает.

  • Least Connections — на тот сервер, где меньше всего активных соединений. Логичнее, но чуть сложнее.

  • IP Hash — один и тот же пользователь всегда попадает на один и тот же сервер. Пригодится, если сессия хранится локально.

Что тут тестировать? Много чего. Самое очевидное — убить один сервер и посмотреть, что будет. Пользователь увидит ошибку? Или балансер молча перекинет его на другой сервер? А если пользователь был залогинен на том сервере, который умер — сессия пропала? Его выкинуло из аккаунта?

Ещё момент: health checks. Балансер должен понимать, что сервер умер, и перестать слать ему запросы. Но как быстро? Если проверка раз в 30 секунд, то полминуты часть пользователей будет получать ошибки. На собеседовании если упомянете это — будет плюс.

Кэширование

Кэш — штука, которая ломает тестировщикам жизнь регулярно. Вы обновили данные, проверяете — а на странице старые. Ctrl+F5 — новые. Всё, вроде работает? Нет, не работает, потому что у обычного пользователя Ctrl+F5 нет в привычке.

Как это устроено: между клиентом и базой данных есть промежуточная «память». Обычно это Redis или Memcached. Когда приходит запрос, система сначала смотрит в кэш. Если данные там есть (cache hit) — отдаёт мгновен��о, даже не трогая базу. Если нет (cache miss) — лезет в базу, получает данные, сохраняет копию в кэш на какое-то время, и отдаёт клиенту.

Запрос от клиента
       |
       v
  +----------+     cache hit      +----------+
  |   КЭШ    | ----------------> |  Ответ   |
  |  (Redis)  |                   | мгновенно|
  +----------+                    +----------+
       |
       | cache miss
       v
  +----------+     сохранить      +----------+
  |    БД    | ----------------> |   КЭШ    |
  | (Postgres)|    в кэш          |  (Redis)  |
  +----------+                    +----------+
       |
       v
  +----------+
  |  Ответ   |
  | клиенту  |
  +----------+

Кэш бывает на разных уровнях: в браузере (картинки, CSS), на CDN (об этом дальше), на сервере (Redis), в самой базе (query cache). И на каждом уровне он может подложить свинью.

Главная боль — cache invalidation. Это когда данные в базе изменились, а кэш ещё хранит старую версию. Классический баг: пользователь поменял аватарку, а у всех друзей она старая. Или хуже: цена на товар изменилась, а из кэша отдаётся старая цена — пользователь платит одну сумму, а списывается другая.

На собеседовании часто спрашивают: «Пользователь обновил профиль, но видит старые данные — что проверить?» Ответ: CDN-кэш, браузерный кэш, Redis-кэш, заголовки Cache-Control и ETag. Открыть инкогнито-окно — если там данные новые, значит проблема в браузерном кэше. Если и там старые — копать серверный кэш.

Базы данных: репликация и шардирование

Тут две отдельные темы, которые часто путают.

Репликация — это когда у вас одна основная база (master) и несколько копий (replica). Записывают данные всегда в master, а читают — из реплик. Зачем? Потому что на чтение обычно приходится 90% нагрузки, и если все запросы идут в одну базу, она не вытянет.

  INSERT / UPDATE / DELETE          SELECT (чтение)
         |                          /          \
         v                         v            v
  +-----------+             +-----------+ +-----------+
  |  MASTER   | ---------->|  REPLICA  | |  REPLICA  |
  |  (запись) | репликация  | (чтение)  | | (чтение)  |
  +-----------+             +-----------+ +-----------+

Тут есть подвох, на который я сам напоролся на реальном проекте. Пользователь создаёт пост, его редиректит на страницу профиля. Пост не отображается. Обновляет страницу — появился. Что произошло? Запись пошла в master, а чтение — из реплики, которая ещё не успела синхронизироваться. Это называется replication lag, и это настоящий баг, который тестами в лоб поймать сложно.

Шардирование — другая история. Это когда одна база не вмещает все данные (или не тянет нагрузку), и вы разбиваете данные на куски. Пользователи с именами A-M идут в одну базу, N-Z — в другую. Каждый такой кусок — шард.

  Все пользователи (1 000 000)
         |
    Shard Key: первая буква имени
         |
   +-----+-----+
   |           |
   v           v
+---------+ +---------+
| SHARD 1 | | SHARD 2 |
|  A - M  | |  N - Z  |
| 520 000 | | 480 000 |
+---------+ +---------+

Для QA тут главная проблема — запросы, которые затрагивают оба шарда. Например, «покажи всех пользователей, отсортированных по дате регистрации». Если пользователи раскиданы по двум базам, кто-то должен собрать данные из обеих и отсортировать. Иногда это работает криво или медленно. Стоит проверять.

Очереди сообщений

Допустим, пользователь зарегистрировался. Нужно: сохранить его в базу, отправить welcome-email, создать профиль, начислить бонус, отправить push-уведомление. Если всё это делать синхронно — пользователь будет ждать секунд 10, пока загрузится страница. Или, что ещё хуже, если сервис email не отвечает — весь процесс регистрации зависнет.

Поэтому придумали очереди. Основной сервис кладёт задачу в очередь (RabbitMQ, Kafka, SQS — неважно), а отдельный worker забирает и обрабатывает в фоне. Пользователь видит «Регистрация успешна» за полсекунды, а email придёт через пару секунд.

+----------+      +-----------------------------------+      +----------+
| PRODUCER | ---> |         MESSAGE QUEUE             | ---> | CONSUMER |
| (сервис) |      |  [msg1] [msg2] [msg3] [msg4] ... |      | (worker) |
+----------+      |     RabbitMQ / Kafka / SQS        |      +----------+
                  +-----------------------------------+
                        |                     |
                  сообщения ждут         обработка
                  своей очереди          по одному

Что тут может пойти не так? О, много всего:

  • Сообщение обработалось дважды — пользователь получил два одинаковых email. Бывает, если worker упал в процессе обработки, и сообщение вернулось в очередь, а потом другой worker его тоже обработал.

  • Сообщение потерялось — worker забрал его, упал, а в очередь оно не вернулось. Email не ушёл вообще.

  • Очередь переполнилась — producer пихает задачи быстрее, чем consumer справляется. В какой-то момент всё встаёт колом.

Был на собеседовании вопрос: «Пользователь оплатил заказ, а подтверждение на email пришло через 2 часа. Где копать?» Тут как раз про очередь — либо consumer-ов мало и очередь забилась, либо были ошибки обработки и сообщение перекидывалось в retry много раз.

Микросервисы

Когда приложение маленькое, весь код живёт в одном проекте — монолите. Один деплой, одна база, один процесс. Всё просто. Но когда команда растёт и функций становится много, монолит превращается в болото: каждое изменение потенциально ломает что-то в другом месте, деплоить страшно, тесты идут два часа.

Поэтому многие переходят на микросервисы: Auth отдельно, Users отдельно, Orders отдельно, Payments отдельно. Каждый сервис — свой код, своя база, свой деплой. Общаются по сети через API или очереди.

    МОНОЛИТ                          МИКРОСЕРВИСЫ

+----------------+          +------+    +-------+
|  Auth          |          | Auth | <--> | Users |
|  Users         |          +------+    +-------+
|  Orders        |              |            |
|  Payments      |              v            v
|  Notifications |          +--------+  +----------+
|                |          | Orders | <--> | Payments |
| один процесс  |          +--------+  +----------+
| один деплой   |              |
| одна БД       |              v
+----------------+         +---------+--------+
                           | Notifications    |
                           +------------------+

                           каждый сервис = свой
                           процесс, деплой, БД

Для QA это и хорошо, и плохо. Хорошо — потому что можно деплоить и тестировать один сервис, не трогая остальные. Плохо — потому что появляются баги на стыках. Сервис Orders ожидает поле user_id от Users, а разработчик Users переименовал его в userId. Юнит-тесты обоих сервисов зелёные. А в интеграции — 500 ошибка.

Ещё интересная штука — circuit breaker. Это когда один сервис перестаёт вызывать другой, если тот не отвечает. Как предохранитель в электрике — вырубается, чтобы не спалить всё остальное. Стоит проверить: если Payments лежит, Orders хотя бы показывает «Оплата временно недоступна», а не падает вместе с ним?

Масштабирование

Тут коротко, потому что идея простая. Два варианта:

Вертикальное — берём сервер помощнее. Было 8 ГБ RAM — поставили 64. Просто, но есть потолок. Самый мощный сервер в мире всё равно конечен.

Горизонтальное — ставим больше серверов. Было 2 — стало 20. Теоретически бесконечно, но нужен load balancer, нужно думать о сессиях, о данных, о синхронизации.

На собеседовании могут спросить: «API отвечает за 200мс при 100 пользователях. Что будет при 10 000?» Если не масштабировано — время ответа вырастет, начнутся таймауты, часть пользователей увидит ошибки. QA должен это проверять нагрузочным тестированием (k6, JMeter) ещё до того, как трафик вырастет.

Мониторинг

Вот это прям моя больная тема. Я видел проекты, где нагрузочные тесты, unit-тесты, интеграционные тесты — всё есть. А в production сервис лёг, и команда узнала через 40 минут из жалобы пользователя в Telegram-чате. 40 минут! Потому что мониторинга нет.

Google в своей SRE-книге описывает четыре сигнала, за которыми нужно следить:

Сигнал

Что это

Когда бить тревогу

Latency

Время ответа

API стал отвечать дольше 2 секунд

Traffic

Кол-во запросов

Трафик внезапно упал на 80%

Errors

Процент ошибок

5xx ошибки перевалили за 1%

Saturation

Нагрузка на ресурсы

CPU 95%, диск забит на 90%

Мониторинг можно разделить на уровни. Снизу вверх:

  Уровень 4:  БИЗНЕС-МЕТРИКИ ........... конверсия, выручка, регистрации
              ________________________________________________
  Уровень 3:  APPLICATION MONITORING .... ошибки, latency, throughput
              ________________________________________________
  Уровень 2:  INFRASTRUCTURE ........... CPU, RAM, disk, network
              ________________________________________________
  Уровень 1:  UPTIME / AVAILABILITY .... сайт жив? SSL валиден?

Начинать нужно с первого уровня — хотя бы проверять, что сайт вообще жив. Я для своих проектов использую SiteGuard — он проверяет uptime каждые 10-15 минут и шлёт алерты в Telegram. Не email, который я проверяю раз в день, а Telegram, который у меня всегда открыт. Ещё он мониторит SSL-сертификаты и проверяет, что формы на сайте работают. Бесплатного плана хватает для pet-проектов.

Кстати, на собеседовании «я настроил мониторинг и поймал downtime раньше пользователей» — это прям сильный аргумент. Большинство QA вообще не думают про мониторинг, считают это задачей DevOps. А зря.

Что проверять по мониторингу на проекте: алерты вообще настроены? Убейте сервис на staging — пришло уведомление? За сколько секунд? Нет ли ложных срабатываний? Если алерты приходят по 50 штук в день — команда их игнорирует, и реальный инцидент пропустят.

Rate Limiting

Если ваш API не ограничивает количество запросов — любой скрипт может заддосить его. Или злоумышленник будет подбирать пароли перебором тысячу раз в секунду.

Rate limiting — это ограничение: например, 100 запросов в минуту с одного IP. Запрос 101 получит 429 Too Many Requests.

Что проверять:

  • Отправить больше запросов, чем лимит — приходит 429?

  • Заголовки X-RateLimit-Remaining и Retry-After — они вообще есть? Клиенту нужно понимать, когда можно повторить.

  • Лимит привязан к пользователю или к IP? Потому что за корпоративным NAT может сидеть тысяча человек с одним IP. Если лимит per IP — заблокируется весь офис из-за одного чрезмерно активного коллеги.

  • Разные endpoint — разные лимиты? Логин обычно лимитируют строже (5-10 попыток в минуту), а чтение каталога — мягче.

CDN

Если ваш сервер стоит в Нью-Йорке, а пользователь сидит в Ашхабаде — данные летят через полмира. Физику не обманешь, скорость света конечна, плюс маршрутизация, плюс потери пакетов. В итоге сайт грузится 3 секунды вместо 300мс.

CDN (Content Delivery Network) — это куча серверов по всему миру, на которых хранятся копии вашей статики (картинки, JS, CSS). Пользователь получает файлы с ближайшего сервера, а не с origin-а.

  БЕЗ CDN:

  Ашхабад ------[ 8 000 км ]-----> Нью-Йорк (origin)
                                    latency: ~200 мс


  С CDN:

  Ашхабад ---[ 2 000 км ]---> Стамбул (edge)    Нью-Йорк (origin)
                               latency: ~30 мс     |
                               контент уже          | первый раз
                               закэширован    <-----+ скопировал

Тестировать тут нужно в основном кэширование. Обновили картинку на сайте — CDN отдаёт старую? Как быстро обновится? Некоторые CDN кэшируют на 24 часа, и если вы загрузили кривой баннер — сутки он будет висеть на всех edge-серверах мира.

Отказоустойчивость (High Availability)

Доступность измеряют в «девятках»:

Доступность

Downtime в год

Это как

99%

3.6 дня

Норм для домашнего блога

99.9%

8.7 часа

Большинство SaaS-ов

99.99%

52 минуты

Финансы, e-commerce

99.999%

5 минут

Банки, телеком. Очень дорого

Каждая дополнительная «девятка» стоит кратно дороже. И QA тут играет важную роль: нужно проверять, что failover работает. Основной дата-центр отвалился — резервный подхватил? Данные не потерялись? Сколько времени прошло между сбоем и восстановлением?

И отдельная тема — бэкапы. Все делают бэкапы. Мало кто проверяет, что из них можно восстановиться. Я серьёзно. Проведите на своём проекте учение — попробуйте поднять сервис из бэкапа. Есть ненулевой шанс, что бэкап битый или процедура восстановления не задокументирована. Лучше узнать об этом на учениях, чем в 3 часа ночи во время реального инцидента.

Задача с собеседования: «Спроектируйте систему уведомлений»

Эту задачу дают часто. Не пугайтесь — от QA не ждут идеального решения. Ждут правильных вопросов.

Первое, что стоит спросить:

  • Какие каналы? Email, SMS, Push, Telegram — или что-то одно?

  • Сколько уведомлений в день? Тысяча — это одна история, десять миллионов — совсем другая архитектура.

  • Насколько критична доставка? OTP-код для логина — должен дойти за 10 секунд. Маркетинговая рассылка — ну, дойдёт через час, ничего страшного.

Примерная архитектура:

  +------------+
  | API Server |  <-- "Отправь уведомление user #42"
  +-----+------+
        |
        v
  +---------------+
  | MESSAGE QUEUE |  <-- буфер, чтобы не перегрузить провайдеров
  +--+---+---+----+
     |   |   |   |
     v   v   v   v
  +----+ +---+ +----+ +--------+
  |Email| |SMS| |Push| |Telegram|   <-- workers (обработчики)
  +--+--+ +-+-+ +-+--+ +---+----+
     |      |     |         |
     v      v     v         v
  SendGrid Twilio Firebase  Bot API   <-- внешние провайдеры
     \      |     |         /
      v     v     v        v
  +--------------------------+
  |    NOTIFICATION DB       |   <-- лог: кому, что, когда, статус
  +--------------------------+

Что бы я тестировал:

  • Уведомление реально доходит по каждому каналу. Не только в логе «отправлено», а проверить, что получено.

  • Worker упал — сообщение не потерялось? Ушло в retry? Сколько попыток перед тем, как сдаться?

  • Дупликаты — пользователь не получит два одинаковых SMS?

  • Приоритеты — OTP-код проходит вне очереди? Или стоит за 50 000 маркетинговых рассылок?

  • Пользователь отписался — уведомления прекратились? (Это ещё и юридическое требование в некоторых странах.)

Частые ошибки на собеседовании

За двадцать собеседований я насобирал типичные грабли. Вот пять самых частых:

  1. Сразу рисовать архитектуру. Не уточнив требования, вы спроектируете что-то не то. Первые 5 минут задачи — только вопросы. Интервьюер это ценит.

  2. Забывать про нагрузку. «Работает» — это мало. Работает при 10 000 запросов/сек? При 100 000? Сколько данных в базе — тысяча записей или миллиард?

  3. Не спрашивать «а что если это упадёт?» Для QA это должен быть рефлекс. Каждый компонент на схеме — потенциальная точка отказа.

  4. Молчать про мониторинг. Если вы сами добавите «и ещё нужен мониторинг — алерты при росте ошибок, дашборд с latency» — это выделяет вас среди остальных кандидатов.

  5. Говорить «это не моя зона ответственности». Формально — да, System Design делают разработчики. Но Senior QA, который не понимает архитектуру, тестирует только формочки. А баги уходят в production.

Чек-лист: что выучить перед собеседованием

  • Load Balancer — что это, алгоритмы распределения, как тестировать failover

  • Кэш — Redis, уровни кэширования, cache invalidation

  • Репликация — master/replica, replication lag

  • Шардирование — зачем, как, проблемы cross-shard запросов

  • Очереди — RabbitMQ/Kafka, retry, дупликаты, dead letter queue

  • Микросервисы — contract testing, circuit breaker

  • Масштабирование — вертикальное vs горизонтальное

  • Мониторинг — 4 золотых сигнала, инструменты

  • Rate Limiting — 429, per user vs per IP

  • HA/DR — девятки, failover, бэкапы

Не обязательно знать всё это в деталях. Главное — понимать, что существуют эти компоненты, зачем они нужны, и что там может пойти не так. Этого хватит, чтобы на собеседовании не молчать.


Курс по тестированию с практическими заданиями — бесплатно на annayev.com (English, Русский, Türkçe).

Если ваш проект уже в production и мониторинга нет — начните с SiteGuard. Uptime-проверки + SSL-мониторинг + Telegram-алерты. Бесплатно, без карты, настраивается за минуту.

Ставьте плюс, если было полезно. Какие System Design вопросы вам задавали? Пишите в комментарии — может, про что-то напишу отдельный разбор.