Когда система растёт и появляются миллионы пользователей, один сервер перестаёт справляться. Инженеры давно придумали набор готовых решений для типовых проблем — балансировщики, кеши, очереди и другие компоненты. Их называют строительными блоками: их меньше двадцати, и почти любая крупная система собирается из них.
Знать названия блоков недостаточно. Каждый блок что-то даёт и чего-то стоит. Понять эту цену — и есть суть системного дизайна.
Сервисы без состояния и балансировка
Представьте кассу в магазине. Одна касса не справляется с очередью — открывают ещё одну. С сервисами то же самое: запускают несколько копий и ставят перед ними балансировщик, который распределяет запросы между ними.
Это работает только если сервис без состояния (stateless): каждый запрос обрабатывается независимо, без памяти о предыдущих. Сессия пользователя хранится в токене, а не в памяти сервера. Файлы — в отдельном хранилище, а не на диске процесса.
Что даёт: можно добавлять копии под нагрузку; падение одной копии незаметно — остальные продолжают работать.
Что стоит: нужно явно выносить любое состояние — в базу, в кеш, в хранилище файлов.
Кеширование
Представьте справочник, который лежит у вас на столе. Вместо того чтобы каждый раз идти в библиотеку за одной и той же книгой, вы держите нужные страницы рядом.
Кеш — это быстрое временное хранилище (обычно в памяти, например Redis). Часто запрашиваемые данные кладут туда, и следующие обращения отвечают за миллисекунды вместо десятков.
Что даёт: скорость чтения и разгрузку базы данных.
Что стоит: данные в кеше могут устареть. Когда оригинал меняется, кеш нужно сбрасывать или обновлять — это называется инвалидацией, и сделать её правильно сложнее, чем кажется. Ещё одна ловушка: если кеш пустой и тысячи запросов приходят одновременно — все ударят в базу разом (cache stampede).
Кеш появляется не «на всякий случай», а когда база реально не справляется с нагрузкой на чтение.
Репликация базы данных
Что если основная база упадёт? Или на неё придёт слишком много запросов?
Репликация — это механизм, при котором все изменения с главного сервера (мастера) автоматически копируются на один или несколько дополнительных серверов (реплики). Если мастер недоступен, одна из реплик становится новым мастером.
Что даёт: устойчивость к отказу; читать можно с реплик, не нагружая мастер.
Что стоит: репликация не мгновенная — реплика всегда немного отстаёт. Если пользователь только что что-то сохранил и сразу читает, он может получить старые данные. Это лечится направлением «чтения своего» на мастер.
Важный момент: репликация не ускоряет запись — все изменения по-прежнему идут через один мастер.
Шардинг
Репликация не помогает, когда данных становится так много, что они не помещаются на один сервер. Тогда применяют шардинг: данные делят на части (шарды) и хранят каждую часть на отдельном сервере.
Например, пользователей с ID 1–1 000 000 — на одном сервере, с ID 1 000 001–2 000 000 — на другом.
Что даёт: данные и запись масштабируются горизонтально — можно хранить терабайты и петабайты.
Что стоит: выбор ключа шардирования — сложная задача. Неверный выбор приводит к тому, что один шард перегружен, а другие пустые. Запросы, которые затрагивают несколько шардов, медленнее и сложнее. Перераспределение данных при изменении схемы шардирования — болезненная операция.
Правило: сначала вертикальный рост (более мощный сервер), затем партиционирование внутри одной базы, и только потом шардинг — когда числа действительно требуют.
Очереди и события
Представьте, что у вас есть интернет-магазин. Когда покупатель оформляет заказ, нужно: сохранить заказ, списать деньги, отправить письмо, уведомить склад. Если делать всё это синхронно — пользователь ждёт несколько секунд.
Очередь позволяет разделить это во времени: сохранили заказ, ответили пользователю «принято», а остальное — письмо, уведомление склада — выполняется в фоне.
Популярные инструменты: Kafka, RabbitMQ.
Что даёт: скорость ответа пользователю; сглаживание пиков нагрузки (очередь накапливает задачи в пик и отдаёт их равномерно); один источник событий может обрабатываться несколькими независимыми потребителями.
Что стоит: результат доступен не сразу — это называется eventual consistency. Потребитель должен уметь обрабатывать одно и то же событие дважды (это происходит при сбоях), не ломая данные — это называется идемпотентностью. Плюс брокер — ещё один компонент инфраструктуры, за которым нужно следить.
Полнотекстовый поиск
Обычная база данных умеет искать точные значения: WHERE name = 'Иван'. Но она плохо справляется с поиском по смыслу: «найди всё про котов», с учётом опечаток, с релевантностью результатов.
Для этого используют специализированные поисковые движки, чаще всего Elasticsearch. Он строит инвертированный индекс: для каждого слова хранит список документов, где оно встречается — и отвечает очень быстро.
Что даёт: полнотекстовый поиск с релевантностью, фасеты (фильтры), подсказки.
Что стоит: это второе хранилище, которое нужно держать синхронным с основной базой. Данные в поиске появляются с небольшим опозданием (лаг индексации). Поиск по подстроке в таблице на тысячу записей — не повод заводить Elasticsearch.
Аналитика
Транзакционная база (PostgreSQL, MySQL) оптимизирована для быстрого чтения и записи отдельных строк. Но аналитические запросы работают иначе: «сколько заказов было за прошлый месяц по всем регионам» — это агрегация по миллиардам строк.
Для таких задач существуют колоночные базы данных, например ClickHouse. Данные в них хранятся по столбцам, что позволяет очень быстро считать агрегаты по большим объёмам.
Что даёт: ответы на аналитические запросы за секунды вместо часов.
Что стоит: отдельный пайплайн для доставки данных (например, через Kafka), данные в аналитике обновляются с задержкой.
Хранилище файлов и CDN
Хранить файлы (изображения, видео, документы) в базе данных — плохая идея: база станет огромной, а работать с файлами неудобно. Для файлов существует объектное хранилище — по модели Amazon S3. Оно рассчитано на хранение миллиардов объектов любого размера.
CDN (сеть доставки контента) — это распределённая сеть серверов, расположенных географически близко к пользователям. Когда пользователь из Владивостока запрашивает картинку, она отдаётся с ближайшего узла CDN, а не едет с сервера в Москве.
Что даёт хранилище: практически неограниченный объём, удобная работа с файлами через ссылки.
Что даёт CDN: меньше время загрузки для пользователей по всему миру; разгрузка основного сервера.
Что стоит: когда файл обновился, закешированная в CDN старая версия может оставаться ещё некоторое время (инвалидация). Приватные файлы требуют подписанных ссылок с ограниченным сроком жизни.
Rate limiting и защита от перегрузки
Что если один пользователь или скрипт отправляет тысячи запросов в секунду? Без защиты сервис ляжет.
Rate limiting — ограничение числа запросов: не больше 100 в минуту с одного пользователя. При превышении возвращается ошибка 429 (Too Many Requests).
Внутри системы парный механизм — backpressure: если очередь задач переполнена, новые задачи отклоняются сразу, вместо того чтобы накапливаться до падения.
Что даёт: защита от злоупотреблений и пиковой нагрузки; предсказуемое поведение под давлением.
Что стоит: легитимные пользователи при превышении лимита получат отказ. Нужно думать, по какому признаку ограничивать: по IP, по пользователю, по API-ключу.
Согласованность в распределённых системах
Когда данные хранятся на нескольких серверах, возникает фундаментальная проблема: что происходит, если два сервера одновременно получили разные данные и не успели синхронизироваться?
Есть известная теорема CAP: в распределённой системе при сетевом сбое нельзя одновременно гарантировать доступность (сервис отвечает) и согласованность (все видят одни и те же данные). Нужно выбирать.
На практике это решают по-разному в зависимости от требований:
- Единственный мастер для записи (PostgreSQL) — простое и надёжное решение.
- Optimistic locking — перед обновлением проверяем, что данные не изменились с момента чтения; если изменились — повторяем.
- Распределённые блокировки (через Redis или ZooKeeper) — дорого и хрупко; почти всегда есть способ проще.
Алгоритмы консенсуса (например, Raft) — это то, что используют внутри Kafka и других распределённых систем. Их берут готовыми, а не реализуют самостоятельно.
Коротко
- Блоков меньше двадцати, и почти любая крупная система собирается из них.
- Каждый блок решает конкретную проблему и несёт конкретную цену — знать оба.
- Stateless + балансировка — основа горизонтального масштабирования; любое состояние в процессе ломает масштабирование.
- Кеш появляется когда измерили, что база не справляется с чтением; без измерений — лишняя сложность.
- Репликация даёт устойчивость и масштабирование чтения, но не записи.
- Шардинг — крайняя мера, когда данные не помещаются на один узел.
- Очереди развязывают компоненты во времени; потребители должны быть идемпотентными.
- Поиск (Elasticsearch) нужен когда важна релевантность, а не просто совпадение.
- Аналитика (ClickHouse) — отдельная база для агрегатов по большим данным.
- CDN снижает задержку для географически распределённых пользователей.
- Rate limiting защищает от пиков и злоупотреблений.
- При сетевом сбое в распределённой системе выбирают: доступность или согласованность.
Что почитать дальше
- Метод проектирования системы — как применять блоки шаг за шагом.
- Сквозной пример: система уведомлений — блоки в действии на одной задаче.
- Распределённые паттерны — как связывать блоки: saga, outbox, идемпотентность.