---
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`](01-interview-framework.md)) задал, как вести беседу, оценки ([`02-estimations.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`](../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`](../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 Нюансы — проговаривать все четыре

1. **Стоимость:** трафик в/из CDN платный, кэшировать нечасто запрашиваемое бессмысленно — редкие объекты из CDN убираем.
2. **TTL-баланс:** короткий — стейл меньше, но чаще ребайты с origin; длинный — наоборот. Для контента, зависящего от времени, выбираем осознанно.
3. **Отказ CDN:** у клиента должен быть fallback — обнаружить недоступность края и запросить с origin. «CDN упал — упал сервис» не принимается.
4. **Инвалидация до истечения 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`](../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`](../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`](../methodology.md) §7; канон — [`../knowledge-base.md`](../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`](../methodology.md) §6; сверка с полными разборами — [`../classic-designs.md`](../classic-designs.md) §1. Если лестница рассказывается без пауз, а на «зачем здесь LB?» рука тянется не к определению, а к боли и числу — раздел готов.

## Связанные документы

- [`00-overview.md`](00-overview.md) — обзорная статья по всем 12 разделам брифа (TASK-45.40)
- [`01-interview-framework.md`](01-interview-framework.md) · [`02-estimations.md`](02-estimations.md) — соседние статьи: каркас, на этапе 3 которого рисуется схема, и сигнальная таблица §8, из которой приходят шаги этого раздела
- [`../brief.md`](../brief.md) — бриф: раздел 03 с критерием «умею, если»
- [`../knowledge-base.md`](../knowledge-base.md) — §3 классы хранилищ (куда выносить стейт), §13.1 DNS, §13.2 алгоритмы балансировщика, §13.8 CDN push/pull и метрики края
- [`../methodology.md`](../methodology.md) — §2 карточки этапов 3–4, §4 топ-10 ошибок, §6 протокол тренировок, §7 одностраничная карточка на секцию
- [`../classic-designs.md`](../classic-designs.md) §1 — эталон URL shortener: IPVS ×4 ДЦ, ко-локация KV, 43 сервера на 500k RPS
- [`../materials/alex-xu-vol1/ch-01-scaling-from-zero.md`](../materials/alex-xu-vol1/ch-01-scaling-from-zero.md) · [`../materials/sdp-guide.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` (мульти-ДЦ и отказы)
