Статья 11: System Design для QA: о чём спрашивают на собеседованиях
System Design для QA: о чём вас спросят на собеседовании (и как не сесть в лужу)
Всё, что нужно знать об архитектуре систем, чтобы перестать тестировать вслепую

Я написал эту статью для тех, кто с 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 маркетинговых рассылок?
- Пользователь отписался - уведомления прекратились? (Это ещё и юридическое требование в некоторых странах.)
Частые ошибки на собеседовании
За двадцать собеседований я насобирал типичные грабли. Вот пять самых частых:
- Сразу рисовать архитектуру. Не уточнив требования, вы спроектируете что-то не то. Первые 5 минут задачи - только вопросы. Интервьюер это ценит.
- Забывать про нагрузку. «Работает» - это мало. Работает при 10 000 запросов/сек? При 100 000? Сколько данных в базе - тысяча записей или миллиард?
- Не спрашивать «а что если это упадёт?» Для QA это должен быть рефлекс. Каждый компонент на схеме - потенциальная точка отказа.
- Молчать про мониторинг. Если вы сами добавите «и ещё нужен мониторинг - алерты при росте ошибок, дашборд с latency» - это выделяет вас среди остальных кандидатов.
- Говорить «это не моя зона ответственности». Формально - да, 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-алерты. Бесплатно, без карты, настраивается за минуту.