open atlas
↑ К треку
Основы System Design SD · 01 · 02

Вертикальное против горизонтального масштабирования

Масштабировать вверх — машина побольше; вширь — машин побольше. Вверх просто, но упирается в потолок железа и остаётся SPOF; вширь эластично и отказоустойчиво, но требует stateless и платит координационный налог, который закон универсальной масштабируемости делает конкретным.

SD Middle ◷ 18 min
Уровень
ОсновыJuniorMiddleSenior

API стартапа крутился на одном мощном сервере БД. Трафик рос — и они делали очевидное: инстанс побольше. Потом ещё больше. Потом самый большой, какой давало облако, — 128 vCPU, дороже в месяц, чем два инженера. Когда и он насытился, «следующего размера» не было, а миграция на шардированный многоузловой дизайн, которую надо было начать два года назад, теперь шла во время аварии. Урок не в том, что вертикальное масштабирование плохо. А в том, что они выбрали путь с потолком, ни разу не спросив, где потолок.

Два направления

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

Вертикальное масштабирование (вверх) — дать одной машине больше ресурсов: больше CPU, RAM, быстрее диски, инстанс крупнее. Приложение почти не меняется; перезагружаешься на железо побольше — и ёмкость растёт. Горизонтальное (вширь) — добавить машины и размазать работу по ним за балансировщиком.

Привлекательность «вверх» — почти нулевые инженерные затраты: никаких распределённых проблем, никаких вопросов согласованности, твой код не знает, что он на коробке побольше. Привлекательность «вширь» — нет врождённого потолка и переживает смерть машины: потерял один узел из пятидесяти — потерял 2% ёмкости, а не сервис.

Реальные системы используют оба: масштабируй БД вверх, пока один primary не станет неуютным, а stateless (без локального состояния) app-слой — вширь свободно. Senior-вопрос никогда не «вверх или вширь?» абстрактно — он «какой ресурс узкое место, есть ли у него потолок, в который я упрусь, и чем каждый путь обходится в режимах отказа?».

Два потолка масштабирования вверх

Вертикальное масштабирование отказывает по двум независимым причинам, и ты ловишь ту, что наступит раньше:

  1. Потолок железа. Есть самый большой инстанс. Когда ты на нём — всё, коробки больше нет, а срок переархитектуры теперь измеряется кварталами.
  2. Кривая цены. Топовое железо стоит сверхлинейно: самый большой инстанс часто дороже, чем 2× от вдвое меньшего, ведь ты платишь за редкий многоядерный кремний. Две средних машины часто бьют одну большую и по цене, и по устойчивости.

И под обоими: одна вертикально масштабированная машина — единая точка отказа. Перезагрузка, смерть диска, падение AZ — и у тебя полная авария, потому что нагрузке некуда уйти.

Почему это работает

Почему «вширь» «требует» statelessness? Потому что если запрос 1 попал на узел A и записал сессию в память A, запрос 2 от того же пользователя может попасть на узел B, который о нём не слышал. Решение, к которому тянется junior, — sticky sessions (прибить пользователя к узлу), но это возвращает единую точку отказа на каждого пользователя и ломает ребалансировку: умер узел A — все прибитые к нему теряют сессию. Прочное решение — вытолкнуть состояние из app-слоя совсем: в общую БД, распределённый кэш (Redis) или клиент (подписанный токен). Тогда любой узел обслужит любой запрос, а app-слой становится стадом одинаковых одноразовых «коров», которых добавляешь и убиваешь как угодно.

Координационный налог: закон универсальной масштабируемости

Опасный миф горизонтального масштабирования — что пропускная способность растёт линейно с узлами: удвой узлы, удвой ёмкость. Не растёт. Закон универсальной масштабируемости (USL) Нила Гюнтера моделирует реальную кривую двумя штрафами:

            N
C(N) = ─────────────────────────
       1 + α(N−1) + βN(N−1)

N = число узлов
α = contention (сериализованная работа — закон Амдала: общий лок, единый координатор)
β = coherency  (межузловая координация — держать кэши/реплики согласованными)

С одним α (contention) пропускная способность подходит к потолку — закон Амдала: 5% серийной доли упирают тебя в 20× сколько узлов ни добавь. Но β (coherency) хуже: он делает кривую ретроградной — после некоторого пика добавление узлов снижает общую пропускную способность, ведь каждый новый узел добавляет больше переговоров, чем работы. Поэтому кластер из 50 узлов может быть медленнее 30-узлового. Масштабирование вширь не бесплатно; ты покупаешь ёмкость координационным налогом, и налог в итоге может превысить ёмкость.

Куда на самом деле переезжает трудность

Масштабировать вширь stateless-слой (web/API-серверы) — почти решённая задача: за балансировщик, автоскейл по CPU, готово. Трудность не исчезает — она переезжает в stateful-слой. Твоя БД, очереди, кэши всё ещё держат состояние, которое должно оставаться корректным между узлами, и вот там живут репликация, шардинг и трейдоффы CAP (следующий юнит про распределение данных). Так что «просто масштабируем горизонтально» легко лишь для той части, что не держит состояния; как только масштабируешь вширь слой данных — ты подписался на распределённые проблемы.

Частая ошибка

Классическая и дорогая ошибка: масштабировать вширь сервис, что выглядит stateless, но нет. In-memory кэши, что каждый узел наполняет независимо; загрузки файлов на локальный диск узла; счётчики rate-limit на процесс; cron-задачи, что теперь бегут на каждом узле разом. Каждое работает на одной машине и тонко ломается на многих — дубли писем, несогласованные счётчики, файлы на одном узле. Прежде чем добавить второй узел, проведи аудит каждого куска состояния процесса и реши, где он на самом деле живёт.

Викторина

Stateless API-слой масштабируется вширь прекрасно, но после ~40 узлов общая пропускная способность перестаёт расти и потом слегка падает. По закону универсальной масштабируемости, какой член доминирует?

Викторина

Команда хочет масштабировать вширь API-сервер, что хранит сессии в локальной памяти процесса. Какое минимальное корректное изменение перед добавлением второго узла?

Закончи аналогию

Чтобы масштабировать app-слой горизонтально, каждый узел должен быть _______ — не держать локально состояния запроса — чтобы балансировщик мог направить любой запрос на любой узел, а умирающий узел не терял ничего незаменимого.

Вспомните перед уходом
  1. 01
    Назови два потолка вертикального масштабирования плюс его структурную слабость.
  2. 02
    Почему горизонтальное масштабирование требует statelessness и каков прочный фикс для stateful app-слоя?
  3. 03
    Что USL добавляет сверх закона Амдала?
Итог

Вертикальное масштабирование (машина побольше) стоит почти нулевых инженерных усилий, но упирается в потолок железа, сверхлинейную кривую цены и остаётся единой точкой отказа. Горизонтальное (машин побольше) не имеет врождённого потолка и переживает смерть узла — но лишь если работа stateless, то есть состояние сессий/кэша/файлов вынесено в общий стор или клиент (sticky sessions — пластырь, воссоздающий SPOF на пользователя). Вширь тоже не бесплатно: закон универсальной масштабируемости оценивает это через α (contention → потолок Амдала) и β (coherency → ретроградная кривая, где лишние узлы могут снизить пропускную способность). Stateless app-слой масштабируется вширь легко; настоящая трудность переезжает в stateful-слой — БД и кэши — что и разбирает следующий юнит про распределение данных. Теперь, когда увидишь команду, тянущуюся к инстансу побольше, спроси первым делом: этот ресурс stateless? Есть ли потолок, в который упрёмся через год? Эти два вопроса — и есть всё решение.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 7 завершено
Связанные уроки

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.