title: Статья 03 — Масштабирование с нуля: LB, CDN, stateless source: подготовлено 2026-09-17, TASK-45.43; каркас — brief.md §03; развёртка knowledge-base.md §3, §13.1–13.2, §13.8; источники — Alex Xu гл. 1 (TASK-45.38), System Design Primer (TASK-45.7), эталон classic-designs.md §1
Статья 03: масштабирование с нуля — LB, CDN, stateless
Третья статья серии — путь «один сервер → миллионы пользователей». Критерий «умею, если» из брифа: могу объяснить, что и в каком порядке добавляю при росте нагрузки и почему. Это первая статья «про коробки»: фреймворк (01-interview-framework.md) задал, как вести беседу, оценки (02-estimations.md) — какими числами обосновывать выбор; здесь — какие блоки появляются на схеме, в каком порядке и чем за каждый платим.
Место раздела в каркасе: строительные блоки этапа 3 (крупноблочная схема рисуется из них) и материал для этапа 4 (детализация) и этапа 6 (отказы этих блоков). Контекст нашей секции (Yandex Codenv, этап 2): задача уровня «инстаграм/твиттер» не предполагает рисовать эволюцию — схему сразу рисуем как результат роста (LB → stateless-сервисы → кэш → реплицированная БД → CDN → очередь). Но за каждым блоком должен стоять ответ «какая боль это лечит»: интервьюер проверяет не знание компонентов, а понимание, зачем каждый нужен и чем платим. Лестница «боль → шаг» — способ держать этот ответ наготове.
1. Словарь: два трейдоффа и два направления
1.1 Performance vs scalability, latency vs throughput
Пара определений из System Design Primer, на которых строится язык всего раздела:
- Performance vs scalability. «Проблема производительности — система медленная для одного пользователя; проблема масштабируемости — быстрая для одного, но медленная под нагрузкой». Масштабируемая система — та, чья производительность растёт пропорционально добавленным ресурсам (определение CTO AWS). На секции различать обязательно: «медленный запрос» и «не тянет 50k RPS» лечатся разными средствами (индекс/профилирование vs масштабирование).
- Latency vs throughput. Латентность — время одного действия, throughput — число действий в единицу времени. Цель формулируем именно так: максимальный throughput при приемлемой latency — не наоборот. Балансировка, кэш, CDN и очереди — четыре способа двигаться в этом поле, и у каждого своя цена.
Сюда же базовая пара доступность/консистентность: горизонтальный масштаб множит копии, а копии — это уже вопрос согласования (раздел 09). Пока фиксируем лексику: любое решение этого раздела проговаривается как «получаю …, плачу …».
1.2 Вертикальное vs горизонтальное
Вертикальное (scale up) — мощнее железо: CPU, RAM, диск одного сервера. Горизонтальное (scale out) — больше серверов в пуле.
Вертикальное отлично для старта и малого трафика: его главное преимущество — простота (не трогаем архитектуру вообще). Два фатальных ограничения: у одного сервера жёсткий лимит ресурсов — их нельзя наращивать бесконечно; и вертикальное масштабирование не даёт отказоустойчивости — сервер один, упал он, упал сервис целиком.
Горизонтальное — стратегия для большого масштаба, и это не вкус, а физика: такты процессора замерли на ~3 ГГц с 2005 года, растут ядра, а не герцы (тренды железа — статья 02 §3.4). Но горизонтальное масштабирование не бывает бесплатно: оно требует, чтобы копии сервера были взаимозаменяемы, то есть не держали стейт (§5), и требует балансировщика (§3). Это и есть центральная связка раздела: горизонтальный масштаб = stateless + LB.
2. Лестница эволюции: «боль → шаг»
Канон раздела — глава 1 Alex Xu: система растёт итеративно, каждый компонент — ответ на конкретную боль, а не «сразу микросервисы на Kafka». Десять шагов, каждый с ценой:
| # | Боль (сигнал) | Шаг | Что получаем | Чем платим |
|---|---|---|---|---|
| 0 | — | Один сервер: web + БД + всё на одной машине | Старт за вечер | SPOF целиком; масштабируется только железом |
| 1 | CPU web забит, БД душится на тех же ресурсах | Разделить web и БД | Слои масштабируются независимо | Два сервера, сеть между ними |
| 2 | Одни и те же тяжёлые запросы к БД | Кэш в приложении | Минус повторные чтения | Память процесса, инвалидация |
| 3 | Один web-сервер: падает — всё падает, пика не тянет | LB + пул web-серверов | Отказоустойчивость и рост web-слоя | Сам LB не должен стать SPOF |
| 4 | Одна БД: чтение душит запись | Репликация БД (master — запись, реплики — чтение) | Read-масштаб + резерв | Лаг репликации, read-your-writes (раздел 05) |
| 5 | Кэш в приложении не масштабируется между нодами | Отдельный кэш-слой (Redis/Memcached) | Общий кэш для всех нод | Ещё система: TTL, вытеснение, инвалидация (раздел 06) |
| 6 | Статику тянет web-слой, глобальные пользователи далеко | CDN | Медиа ближе к пользователю, минус трафик с origin | Деньги, TTL-стейл, зависимость от провайдера |
| 7 | Нельзя просто добавить web-сервер: сессии на нодах | Stateless web-tier (стейт наружу) | Автомасштабирование, любой сервер любой запрос | Хранилище стейта (Redis/KV), +1 хоп |
| 8 | Пользователи на континентах, ДЦ один | Несколько ДЦ + GeoDNS | Латентность, живучесть региона | Синхронизация данных, тесты, деплой (раздел 08) |
| 9 | Пик нагрузки роняет синхронный путь | Очередь сообщений | Асинхронная развязка, независимое масштабирование | At-least-once, идемпотентность (раздел 07) |
Как читать на секции. Верх лестницы (3–9) — то, из чего состоит «крупноблочная схема» твиттера: рисуем сразу её, а лестницу держим как обоснование: «LB здесь, потому что web-слой горизонтальный; web-слой горизонтальный, потому что stateless; сессии — в KV, не на нодах». Обратный порядок — «нарисовал коробку, теперь придумываю зачем» — интервьюер слышит мгновенно.
Порядок шагов не догма, а дефолт: числа могут менять последовательность. Если read/write = 100:1, репликация (шаг 4) и кэш (5) окупятся раньше LB; если в ответе «видео/фото» (статья 02 §7), CDN выходит на первый план, а web-слой остаётся скромным. Решает расчёт, не табличка — и это проговаривается: «порядок зависит от дефицитного ресурса, для медиа-сервиса сначала CDN и объектное хранилище».
3. Балансировщик нагрузки: три работы и алгоритмы
3.1 Зачем
Три эффекта, формулировка SDP: не слать трафик на нездоровые серверы, не перегружать живые, убрать единую точку отказа на web-уровне. Первый и третий — про доступность, второй — про throughput. Из схемы включения (Сюй): клиенты подключаются только к публичному IP балансировщика; серверы за ним общаются по внутренним IP, невидимым из интернета — «клиенты больше не имеют прямого доступа к веб-серверам», что заодно и безопасность. Отказ сервера 1 → трафик на сервер 2, в пул добавляется новый: для клиентов ничего не меняется.
3.2 Алгоритмы
Экзаменационный набор — ../knowledge-base.md §13.2:
| Алгоритм | Когда |
|---|---|
| Round Robin | дефолт для stateless-нод |
| Weighted RR | ноды разной мощности (старые/новые поколения) |
| Least Connections | долгие соединения: websocket, streaming |
| Hash / IP-sticky | сессии, кэш-локальность; вместе с consistent hashing — ответ про масштабирование пула |
| Random | называем, что не выбираем |
Обязательная фраза, без которой алгоритм не засчитывается: «перед LB — health checks, нездоровую ноду выбрасываем из ротации». Health check — не деталь, а механизм, который делает список эффектов реальным: LB смотрит не только «жив ли порт», но и готов ли сервис (деплой, прогрев, зависимость).
3.3 L4 vs L7 и бонусы на краю
- L4 — решение по транспорту (IP + порт, NAT): дёшево, быстро, но «слепое» к содержимому. L7 — читает прикладной слой (путь, заголовки, куки, тело) и маршрутизирует осмысленно: «видео — на видеосерверы, биллинг — на усиленные». На современном железе цена L7 невелика, и дефолт секции — L7.
- SSL termination на LB: бэкенды не тратят CPU на TLS и не держат сертификаты на каждой ноде. Внимание: TLS остаётся дорогим — дефицитный ресурс ноды часто именно CPU на handshake (эталон: ~20k RPS на воркер из-за новых TLS-handshake'ов, а не из-за логики).
- Session persistence (sticky по куке) — есть у всех LB, но это костыль для stateful-приложения; правильный ответ — сделать слой stateless (§5), а не приклеивать пользователей к нодам.
3.4 LB сам не SPOF. И DNS как нулевой слой балансировки
Единый LB отменяет весь смысл схемы → LB живёт парой (active-passive или active-active). Перед LB стоит DNS — и это тоже слой балансировки: managed-DNS умеет weighted round robin (баланс кластеров разного размера, вывод серверов из-под трафика), latency-based и geolocation-based маршрутизацию; GeoDNS «ведёт в ближайший регион» — с этого начинается мульти-ДЦ (KB §13.1). Плата DNS: задержка lookup (смягчается кэшем), стейл-кэш при TTL, и сам DNS-провайдер как внешняя зависимость (DDoS на Dyn в октябре 2016 уложил Twitter у всех, кто не знал IP) — «не углубляться, но знать».
3.5 Что происходит вниз по стеку
Горизонтальный рост app-слоя умножает не только throughput: каждая новая нода открывает соединения к кэшам и БД — число соединений растёт как произведение, и его ограничивают пулом/прокси соединений (pgbouncer-класс). Проговаривать при росте пула — иначе интервьюер спросит «а БД выдержит 300 соединений?».
3.6 Эталон: LB в эталоне Яндекса
В URL shortener из статьи Яндекса (../classic-designs.md §1) балансировка решена IPVS: по балансировщику на ДЦ (маршрут не должен выходить за пределы ДЦ — иначе LB сам станет меж-ДЦ зависимостью), за ним воркеры. Воркер — это пара «веб-сервер + ко-локальный KV»: балансировщик раздаёт запросы нодам, а данные живут рядом с CPU (ко-локация по доминантному ресурсу). Итог этапа 5: 500k RPS ÷ 20k RPS на воркер (дефицит — TLS!) = 25 воркеров в пике, ×4 ДЦ с запасом = 36 воркеров + 4 IPVS + 3 СУБД = 43 сервера на 500k RPS. Это и есть «масштабирование числами»: LB не абстракция, а конкретный план ёмкости.
Сюда же смежное понятие reverse proxy (NGINX/HAProxy): фасад перед бэкендами — прячет внутренние сервисы, терминирует SSL, сжимает, кэширует, раздаёт статику. Отличие: LB осмыслен с несколькими серверами, reverse proxy полезен и с одним; на практике NGINX умеет оба режима сразу (L7). На секции достаточно: «на краю — reverse proxy / LB, единая точка терминирования TLS и health checks».
4. CDN: статику — ближе к пользователю
4.1 Когда
Сигнал из оценок (статья 02 §8): egress ≥ Gbps или медиа-контент → CDN с offload 90 %+. Противоположный пример там же: URL shortener отдаёт redirect-заголовки по ~200 байт — CDN не нужен, это не медиа. Двойной выигрыш CDN: пользователю — ближайший узел (120 мс до origin → 30 мс до края), нашим серверам — минус чужие запросы.
4.2 Как работает
Запрос image.png на домен провайдера → на краевом узле промах → CDN тянет с origin (наш сервер или S3), получает объект вместе с HTTP-заголовком TTL → кладёт в кэш края → следующий пользователь из этого региона получает объект с края, пока TTL жив. То есть CDN — это кэш с географией: к нему применимы все слова про инвалидацию и TTL из раздела про кэширование, только «серый» фон — провайдер.
Режимы (KB §13.8): push hot / pull rare / hybrid — горячее проталкиваем на край заранее, редкое тянем по промаху, обычно гибрид.
4.3 Нюансы — проговаривать все четыре
- Стоимость: трафик в/из CDN платный, кэшировать нечасто запрашиваемое бессмысленно — редкие объекты из CDN убираем.
- TTL-баланс: короткий — стейл меньше, но чаще ребайты с origin; длинный — наоборот. Для контента, зависящего от времени, выбираем осознанно.
- Отказ CDN: у клиента должен быть fallback — обнаружить недоступность края и запросить с origin. «CDN упал — упал сервис» не принимается.
- Инвалидация до истечения TTL: purge через API провайдера или версионирование URL (
logo.jpg?v=2) — классический приём «новый объект = новое имя», снимающий проблему стейла целиком.
Метрики края: cache miss outs, active/total resources — привязаны к узлу, подаются на этапе 6 (KB §13.8).
5. Stateless: стейт покидает вычислительные ноды
5.1 Почему стейт на сервере — враг масштаба
Если сервер хранит состояние (сессии пользователя, его данные), запрос обязан прийти «на свой» сервер: аутентификация пользователя A пройдёт только на сервере 1, где лежит его сессия. Лечение на уровне LB — липкие сессии — работает, но платит оверхедом и ломает главное: добавление/удаление серверов становится трудным, отказ сервера — потерей его сессий. Stateful-слой не масштабируется горизонтально в принципе, только вертикально.
5.2 Правило
Вычислительные ноды — без состояния; состояние — в разделяемом хранилище. Любой сервер обрабатывает любой запрос, подтягивая стейт из общего хранилища (Сюй: RDBMS / Memcached / Redis / NoSQL). Следствия: отказ ноды = «вывели из ротации», без потери пользовательских сессий; автомасштабирование — добавляем/убираем ноды по нагрузке; деплой — катим по нодам без «приклеенных» пользователей.
Тонкость, которую стоит проговорить отдельно: stateless ≠ «в системе нет состояния». Стейт никуда не исчез — он переезжает в специализированные хранилища, которые масштабируются сами (репликацией и шардированием, разделы 05–06). Формула: «стейт не на вычислительных нодах — стейт в правильных местах». «Правильные места» — по классам хранилищ из ../knowledge-base.md §3:
| Какой стейт | Куда | Почему |
|---|---|---|
| Сессии, фичи-флаги, счётчики | KV (Redis-класс) | O(1) по ключу, TTL, гигантский RPS на ноду |
| Данные домена, деньги, связи | RDBMS | ACID, источник правды |
| Горячие чтения | Кэш-слой (read-реплики/KV-зеркало) | Проекция под access pattern (раздел 06) |
| Медиа, бэкапы | Объектное хранилище (S3) + CDN | Бесконечный масштаб, дёшево (раздел 04) |
| События между сервисами | Очередь/лог (Kafka) | Асинхронная развязка (раздел 07) |
5.3 Web-слой ≠ app-слой
Бриф зовёт этот сюжет «web-сервер vs app-сервер». Разделение: web-слой — терминирование соединений, TLS, раздача статики (reverse proxy); app-слой — бизнес-логика и воркеры. Платим раздельно, масштабируем раздельно: новый API → докинули app-нод, web не трогаем. Воркеры в app-слое — машина асинхронности: тяжёлые операции уходят из request-path в фон через очередь (шаг 9 лестницы). Service discovery (Consul/etcd/ZooKeeper) — ответ на «как сервисы находят друг друга», когда нод и сервисов много. Микросервисы — не цель: SDP прямо предупреждает про цену слабосвязанных сервисов (деплой, операции) — на секции взвешиваем; яндексовский KISS: «система должна быть минимально сложной».
6. Конечная схема: как это рисуется на этапе 3
Пять минут этапа 3 — блоки из этой статьи ложатся в каноническую схему (уровень «инстаграм/твиттер»):
клиенты ──▶ DNS (GeoDNS: ведёт в ближайший регион)
│
├──── статика ────▶ CDN ──▶ S3/origin
│ (offload 90 %+)
└──── динамика ──▶ LB (L7, пара active-active,
SSL termination, health checks)
│
▼
stateless-сервисы (web/app, автомасштаб)
│ │ │
▼ ▼ ▼
кэш БД: primary очередь ──▶ воркеры
(KV) + реплики (Kafka) (транскодинг,
рассылки, …)
Проговариваем по ней: горячий путь целиком («клиент → GeoDNS в регион → CDN для статики / LB для динамики → любой stateless-сервис → кэш → при промахе реплика БД»), путь записи отдельно («сервис → primary → репликация»), и всё, что не нужно для ответа пользователю — асинхронно через очередь. Каждый блок на схеме — строка лестницы §2: LB (шаг 3), реплики (4), кэш (5), CDN (6), stateless (7), ДЦ (8), очередь (9).
7. Сводная таблица «сигнал из оценок → шаг масштабирования»
Мост к сигнальной таблице статьи 02 §8: расчёт называет проблему — этот раздел отвечает «какой блок добавляем»:
| Сигнал из оценок | Шаг масштабирования | Раздел |
|---|---|---|
| QPS_пика > RPS_ноды | LB + горизонтальный пул stateless-нод | 03 |
| CPU-bound из-за TLS на входе | LB с SSL termination; считать ноды от handshake'ов | 03 |
| Egress ≥ Gbps, медиа-ответы | CDN + объектное хранилище | 03, 04 |
| Read/write ≥ 100:1 | Кэш-слой + read-реплики | 06, 05 |
| Сессии на нодах мешают деплою/масштабу | Stateless + сессии в KV | 03 |
| Рост app-пула душит БД соединениями | Пул соединений, кэш перед БД | 03, 06 |
| Пики ×5–10 к среднему | Автомасштаб + очередь как буфер | 07 |
| Пользователи на нескольких континентах | Мульти-ДЦ + GeoDNS | 08 |
| Storage ≥ десятки ТБ | Шардирование / объектное хранилище | 05, 04 |
8. Ошибки этого раздела
Подмножество топ-10 ошибок (../methodology.md §4), относящееся к масштабированию:
| Ошибка | Как выглядит | Противоядие |
|---|---|---|
| «Сразу микросервисы на Kafka» | Коробки без боли: очередь «на всякий случай» | Каждый блок — от сигнала: боль → шаг (§2) |
| LB как SPOF | Один балансировщик на всю схему | Пара active-passive/active-active + health checks |
| Стики-сессии как план | «LB приклеит пользователя к ноде» | Stateless + сессии в KV; sticky — только как исключение с ценой |
| CDN «для всего» | Редкие объекты в CDN, нет fallback при отказе края | Hot-контент, TTL-баланс, origin-fallback (§4.3) |
| Stateless на словах | «Серверы без состояния», но локальный кэш/файлы как стейт | Аудит: что потеряется при гибели ноды — ничего |
| Вертикальный скейл как стратегия | «Докупим памяти master'у» на 10× рост | Тактика на старте; стратегия — горизонталь (§1.2) |
| Забытые соединения вниз по стеку | 100 app-нод × 50 коннектов = БД лежит | Пул соединений, проговаривать при росте пула (§3.5) |
| Компоненты без чисел | «CDN снизит нагрузку» — на сколько? | Offload 90 %+, RPS на ноду, ноды от дефицитного ресурса |
9. Что назвать на секции (чек-лист раздела)
Обязательные фразы и действия, по которым видно, что раздел освоен. Полный чек-лист всех этапов — ../methodology.md §7; канон — ../knowledge-base.md §3 (классы хранилищ — куда выносить какой стейт) и §13.1–13.2 (DNS, алгоритмы LB).
На этапе 3 (схема): - Нарисовать канон §6 и проговорить горячий путь и путь записи отдельно. - «Web-слой stateless — сессии в Redis, любой сервер берёт любой запрос» — одной фразой при первом же пуле серверов. - «Клиенты видят только публичный IP LB, серверы общаются по внутренним» — безопасность края.
На этапе 4 (детали): - Алгоритм LB под нагрузку: round robin для stateless; least connections для долгих соединений; weighted при разной мощности — плюс «health checks выводят нездоровые ноды». - «L7, SSL termination на LB — бэкенды не тратят CPU на TLS» + помнить, что TLS часто и есть дефицитный ресурс ноды (~20k RPS, эталон §3.6). - Куда какой стейт — по таблице §5.2 (KB §3): сессии → KV, домен → RDBMS, медиа → S3 + CDN, события → очередь. - CDN: «push hot / pull rare / hybrid», TTL-баланс, инвалидация версионированием URL, fallback на origin при отказе края. - При росте пула: «соединения вниз по стеку — пул, иначе задушим БД».
На этапе 6 (эксплуатация): - Отказ LB → пара active-passive/active-active; отказ ноды → stateless, вывели из ротации, сессии живут. - Отказ CDN → клиенты на origin (деградация латентности, не доступности). - Отказ ДЦ → GeoDNS переливает трафик; синхронизация данных между ДЦ — асинхронная (мост в раздел 08).
Сквозные формулы раздела: - «Боль → шаг»: каждый компонент схемы обязан назвать свою боль. - «Горизонтальный масштаб = stateless + LB» — центральная связка. - «Порядок шагов зависит от дефицитного ресурса» — медиа-сервис начинает с CDN, не с LB.
10. Самопроверка «умею, если»
Критерий раздела из брифа: могу объяснить, что и в каком порядке добавляю при росте нагрузки и почему. Проверяется прогоном: закрыть материалы, взять произвольный сервис (доставка кофе, электронная библиотека, парковки) и вслух пройти лестницу §2 от одного сервера до мульти-ДЦ — каждый шаг как «сигнал роста → добавляю X → получаю Y → плачу Z». Затем сверка: каноническая схема §6 воспроизводится от руки за 5 минут с горячим путём и путём записи; таблица «какой стейт куда» (§5.2) — без подглядывания; эталонные числа §3.6 (20k RPS на воркер из-за TLS, 43 сервера на 500k RPS) — от руки. Протокол тренировок и рубрика 0–3 — ../methodology.md §6; сверка с полными разборами — ../classic-designs.md §1. Если лестница рассказывается без пауз, а на «зачем здесь LB?» рука тянется не к определению, а к боли и числу — раздел готов.
Связанные документы
00-overview.md— обзорная статья по всем 12 разделам брифа (TASK-45.40)01-interview-framework.md·02-estimations.md— соседние статьи: каркас, на этапе 3 которого рисуется схема, и сигнальная таблица §8, из которой приходят шаги этого раздела../brief.md— бриф: раздел 03 с критерием «умею, если»../knowledge-base.md— §3 классы хранилищ (куда выносить стейт), §13.1 DNS, §13.2 алгоритмы балансировщика, §13.8 CDN push/pull и метрики края../methodology.md— §2 карточки этапов 3–4, §4 топ-10 ошибок, §6 протокол тренировок, §7 одностраничная карточка на секцию../classic-designs.md§1 — эталон URL shortener: IPVS ×4 ДЦ, ко-локация KV, 43 сервера на 500k RPS../materials/alex-xu-vol1/ch-01-scaling-from-zero.md·../materials/sdp-guide.md— источники: лестница эволюции и полный текст главы · сеть (DNS/CDN/L4-L7/reverse proxy) и прикладной слой- Соседние статьи:
04-data-models-storage.md(куда ложится стейт) ·05-replication-sharding.md(масштаб хранилища) ·06-caching.md(кэш-слой детально) ·07-queues-streams.md(асинхронность) ·08-fault-tolerance.md(мульти-ДЦ и отказы)