Зачем
«Сделай быстрее» и «сделай масштабируемее» — это разные задачи, и их часто путают. Когда продакт говорит «у нас тормозит», он может иметь в виду одну из двух совершенно разных проблем:
- Performance problem: система медленная для одного пользователя — даже когда других нет. Лечится профилированием, индексами, кешем, переписыванием горячего кода.
- Scalability problem: система быстра для одного, но падает с ростом нагрузки. Лечится архитектурными изменениями — vertical scale, horizontal scale, sharding, multi-region.
Эта разница задаёт направление работы. Если у тебя p99=500ms на пустой системе — никакой sharding не поможет, у тебя медленный код. Если у тебя p99=20ms при 1K RPS и 2s при 10K RPS — оптимизация кода не вытащит, нужна другая топология.
Иногда эти задачи противоречат друг другу: чтобы система стала масштабируемее, придётся пожертвовать производительностью при низкой нагрузке. Добавишь network hop между app и DB — выиграешь capacity, но потеряешь 1-5ms на запрос. Шардируешь БД по user_id — выиграешь write throughput, но cross-shard JOIN-ы станут дороже. Это нормально: ты осознанно платишь latency за headroom.
Mental model
«Performance — быстро сейчас. Scalability — быстро и через год при 100× нагрузке.»
Сформулировано Вернером Фогельсом (CTO Amazon) в эссе «A Word on Scalability» (2006). Та же идея в более прикладной форме:
- Performance измеряется в latency (p50/p95/p99) на фиксированной нагрузке.
- Scalability измеряется в headroom: насколько ты можешь вырасти, не переписывая архитектуру.
Vertical vs Horizontal — два пути:
- Vertical (scale up): взять сервер побольше. Простой путь — переезд с m5.large на x1e.32xlarge даёт x100 по RAM без единой строчки кода. Упирается в потолок железа (~128 vCPU, ~4TB RAM на топовых инстансах AWS) и в цену (топовый инстанс часто стоит дороже 50 средних).
- Horizontal (scale out): добавить серверов. Без потолка по железу, но требует архитектурных изменений: stateless app, external state, sharding, координация.
- Hybrid: реальность. Stack Overflow крутится на ~9 серверах (vertical-heavy), Twitter — на тысячах нод (horizontal-heavy). Большинство систем посередине.
Что показывает диаграмма
Топология собирает в одной картинке 5 эволюционных стадий одной и той же системы — от 1K до 100M пользователей. Каждая стадия добавляет компоненты, а не переписывает существующие.
- Client — пользователь (браузер или мобильный клиент).
- CDN edge cache — статика (JS/CSS/изображения) с edge-нод.
- EU region (mirror) — региональная копия системы для multi-region.
- Load Balancer — единая точка входа, распределяет запросы по app fleet.
- App server 1/2/3 — stateless фронтенды бизнес-логики (на app-1 висит ADR-001).
- Redis — external cache, shared между всеми app-серверами.
- DB primary — единственная нода, принимающая writes.
- DB replica 1/2 — асинхронные реплики, обслуживают reads.
- DB shard 2 — отдельный шард, маршрутизация по hash(user_id).
Edges — физические соединения (LB → app, app → cache, app → DB). Анимация ходит по ним в обе стороны (запрос → ответ через те же провода) — это базовый паттерн FlowBuilder.
Сценарии
Пять сценариев = пять стадий жизни системы. Каждая включается, когда предыдущая упирается в потолок.
- Stage 1 — 1K users / monolith. Один сервер делает всё: web, бизнес-логика, БД на той же машине. ~10 RPS, latency ~20ms. Это правильная архитектура для этой нагрузки — Stack Overflow доказал, что vertical scale тянет миллионы пользователей без героики. Premature horizontal на этом этапе = waste money + сложность без пользы.
- Stage 2 — 10K users / split DB. CPU начинает контентиться между app и postgres на одной машине. Выносим БД на отдельный сервер: +3-5x capacity ценой ~3-5ms network hop. Минимальное изменение, большой выигрыш — классическая первая итерация.
- Stage 3 — 100K users / cache + read replica. DB primary задыхается на reads, p99 переваливает за 500ms. Добавляем Redis перед БД: 90% горячих запросов ловятся из памяти за <1ms. Cache miss идёт в read-replica, а не в primary. Primary освобождается для writes. Reads и writes разделены — теперь они масштабируются независимо.
- Stage 4 — 1M users / horizontal + sharding. Один app-сервер упирается в CPU при 10K RPS. Ставим LB перед stateless fleet — теперь app масштабируется добавлением нод. Но DB primary не тянет writes от 1M пользователей — шардируем по hash(user_id) % N. Каждый shard принимает 1/N writes. Horizontal на всех уровнях.
- Stage 5 — 100M users / CDN + multi-region. EU users видят 200ms RTT к US — конверсия падает. CDN offload 90% статики на edge (<20ms). Geo-routing через DNS/Anycast: EU users идут в EU region (~20ms вместо 200ms). Region-pinned data: user-данные привязаны к «своему» региону. Глобальный продукт с локальной latency.
Каждый переход — реакция на конкретную боль, а не «давайте сразу строить как Facebook».
Trade-offs (ADR-001)
См. полный ADR на ноде app-1 в диаграмме. Ключевые точки:
- Эволюционный путь, а не big-bang architecture. 90% стартапов первые 3 года не упираются в horizontal scale. Premature sharding = месяцы работы + операционная сложность ради проблем, которых нет. Сначала monolith + vertical, шардирование — когда реально упрётся.
- Vertical scale — не «несерьёзно». Один AWS x1e.32xlarge (128 vCPU, 3.9TB RAM) тянет огромную нагрузку и часто стоит дешевле, чем 50 малых нод + оркестратор + DBA, который умеет шарды. Stack Overflow на пике использовал ~9 серверов.
- Stateless обязателен для horizontal. Если app хранит session/cache в памяти процесса — LB не может слать запросы куда попало, нужны sticky sessions. Sticky sessions ломают равномерное распределение, ломают rolling deploys, ломают auto-scaling. Внешний state (Redis для cache, JWT/Redis для session) — цена за свободу масштабирования.
- Цена координации. Каждый horizontal-шаг добавляет network hop (1-5ms) и точку отказа. На низкой нагрузке monolith быстрее распределённой системы. Distributed архитектура выигрывает только когда задача физически не помещается в один сервер.
- Sharding — последний шаг. Шарды усложняют ВСЁ: миграции, бэкапы, JOIN-ы, транзакции, debugging. Делается, когда single primary упёрся в потолок по writes и ничего больше не работает (cache, read-replicas, queue-based writes уже выжаты).
Реальные системы
- Instagram (2010-2012). Стартанули с monolith Django + Postgres на одном сервере. Сначала vertical scale, потом read-replicas, потом vertical sharding (отдельные БД для users / photos / comments), потом horizontal sharding по user_id. Каждый шаг — response to scaling pain, а не префетчинг. К моменту покупки Facebook ($1B) у них было ~13 инженеров.
- Stack Overflow. До сих пор vertical-heavy: ~9 серверов на пик трафика, monolith на ASP.NET, SQL Server без шардинга. Маркос Хенрик-Ассунсао писал, что им проще купить сервер за $50K, чем нанимать инженеров для distributed системы. Vertical works.
- Pinterest. Прошли через 30+ итераций sharding strategy за первые годы. Меняли алгоритм шардинга несколько раз — каждая миграция занимала месяцы. Урок: если знаешь, что будешь шардить, продумай ключ сразу; но если не знаешь — не шарди.
- Twitter. Fanout-on-write требует sharding на ранней стадии — иначе single primary не вытянет даже 10K твитов в секунду. Это исключение, а не правило: социальные сети с high-fanout — единственный класс задач, где premature sharding оправдан.
- WhatsApp. До покупки Facebook (450M MAU) обслуживался ~50 инженерами на Erlang/FreeBSD. Vertical scale + правильный выбор runtime (Erlang lightweight processes) дал capacity, для которой обычно нужны тысячи микросервисов.
Anti-patterns
- Premature horizontal. Добавили Kubernetes + 5 микросервисов на этапе 1K users. Latency выросла (network hops), debugging стал кошмаром, deployment усложнился, ничего не выиграли. Решение: monolith пока хватает.
- Stateful app-серверы. Хранят session в
process.memory, локальный in-memory cache, file uploads на локальном диске. Потом удивляются, почему horizontal scale не работает — каждый запрос должен идти на «свой» сервер. Решение: вынеси state в Redis / S3 / external DB с первого дня.
- Sharding до того, как выжали vertical + read-replicas. Один engineer-год на миграцию схемы под шарды, после которой выяснилось, что vertical x4 + 3 read-replicas решили бы проблему за неделю. Решение: измерь, какой компонент реально упёрся (writes? reads? CPU? IOPS?), и масштабируй именно его.
- Игнорирование hot keys. Шардировали по user_id, но 1% пользователей (celebrity accounts) генерят 50% нагрузки. Один шард всегда красный. Решение: целевой шардинг для hot keys (отдельный pool), либо изменение ключа (hash(user_id, action_type)).
- Считают, что «cloud сам масштабирует». Auto-scaling работает только если app stateless и горизонтально масштабируема. Если у тебя monolith с in-memory session — никакой auto-scaling не спасёт.
Когда НЕ использовать
Эта диаграмма — map путей эволюции, а не рецепт «делай так сразу». Не нужно:
- Прыгать через стадии. Если у тебя 1K users, тебе не нужны read-replicas, sharding, multi-region. Это сложность без пользы.
- Считать Stage 5 «правильной» архитектурой. 99% продуктов никогда не доходят до 100M users — для них Stage 2-3 это конечная точка.
- Использовать «как у Netflix» как аргумент. Netflix-style архитектура работает у Netflix, потому что у них тысячи инженеров и миллиарды доходов на её содержание. У тебя — другие constraints.
Когда стадии не применимы:
- Real-time системы (trading, gaming). Их паттерны масштабирования другие — partition by region/asset, локальная co-location, custom networking. Web-эволюция не работает.
- Аналитика / batch. Там Spark/Presto/ClickHouse сразу — это не масштабирование web-стека, это другой класс систем.
- Embedded / IoT. Сценарий «1 сервер → миллионы клиентов» с принципиально иными constraints (battery, bandwidth, offline-first).
Дальше читать
Источники. Werner Vogels — «A Word on Scalability» (2006); System Design Primer — «Performance vs scalability»; Instagram Engineering Blog (post-mortem миграций); Stack Overflow Architecture (Nick Craver blog).