← назад к разделу

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

Знать названия блоков недостаточно. Каждый блок что-то даёт и чего-то стоит. Понять эту цену — и есть суть системного дизайна.

Сервисы без состояния и балансировка

Представьте кассу в магазине. Одна касса не справляется с очередью — открывают ещё одну. С сервисами то же самое: запускают несколько копий и ставят перед ними балансировщик, который распределяет запросы между ними.

Это работает только если сервис без состояния (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, идемпотентность.