Привет, Хабр! Я написал эту статью для тех, кто с 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-алерты. Бесплатно, без карты, настраивается за минуту.
Ставьте плюс, если было полезно. Какие System Design вопросы вам задавали? Пишите в комментарии — может, про что-то напишу отдельный разбор.
