Все уроки
АрхитектураПродвинутый14 мин чтенияВ моём стеке

Масштабирование и балансировка нагрузки

Вертикальное и горизонтальное масштабирование, балансировщики, слои кэширования, реплики, пулинг и шардинг.

  • Vertical / horizontal
  • Load balancers
  • CDN
  • Read replicas
  • Sharding
Шаг 1/4
Один сервер, очень много пользователей. CPU на 100%, все ждут. Так себе.

За 30 секунд

Масштабирование — это способность системы оставаться быстрой и доступной по мере роста трафика. Можно купить сервер помощнее (вертикально) или добавить серверов (горизонтально) — для этого нужны stateless-приложения и load balancer перед ними. Большинство реальных узких мест находится в базе данных, поэтому кэширование, read-реплики, пулинг соединений и очереди обычно идут раньше шардинга. Сначала измеряйте, потом масштабируйте ту часть, которая действительно медленная.

Почему я это использую

  • Я этим занимаюсь, потому что трафик редко бывает ровным: акции, пуш-кампании и обеденные заказы создают всплески, которые один процесс не выдержит.
  • Если с самого начала проектировать приложения stateless, добавление серверов становится изменением конфигурации, а не переписыванием.
  • Большинство проблем с производительностью, которые я исправляю, — это проблемы базы и кэширования, поэтому знание слоёв экономит деньги: кэш или индекс часто выигрывает у нового железа.
  • Планирование мощностей позволяет сказать клиенту, что выдержит его конфигурация и сколько будет стоить следующий шаг, ещё до того, как он понадобится.

Как это работает

01Вертикально или горизонтально и почему важен stateless

Вертикальное масштабирование — это машина помощнее: больше CPU, RAM, быстрее диски. Это просто, не требует изменений в коде и часто оказывается правильным первым шагом, но у него есть потолок, оно дорожает, и одна машина — всё равно одна точка отказа. Горизонтальное масштабирование — это больше машин, на которых крутятся одинаковые копии приложения за load balancer'ом. Оно масштабируется дальше и переживает потерю ноды, но только если любой инстанс может обслужить любой запрос. Для этого нужно stateless-приложение: сессии в Redis или подписанные токены, загрузки в объектном хранилище вместо локального диска, кэш в общем хранилище, задачи по расписанию, которые запускаются один раз, а не на каждой ноде, и никакого важного состояния в памяти. Делайте приложения stateless заранее, даже на одном сервере, — и горизонтальное масштабирование станет простым делом.

  • Сессии и кэш: Redis, а не память процесса или локальные файлы.
  • Файлы: S3-совместимое объектное хранилище, раздача через CDN.
  • Cron: один планировщик или распределённая блокировка, чтобы задачи не выполнялись N раз.

02Балансировщики нагрузки: L4 и L7, алгоритмы, health check'и

Load balancer распределяет запросы между инстансами. Балансировщики уровня 4 работают с TCP/UDP-соединениями: очень быстрые, не зависят от протокола, но не видят URL и заголовков. Балансировщики уровня 7 понимают HTTP: терминируют TLS, маршрутизируют по хосту или пути, добавляют заголовки, повторяют упавшие запросы, сжимают и ограничивают частоту. nginx, HAProxy, Envoy, Traefik и облачные application load balancer'ы — распространённые L7-варианты. Среди алгоритмов — round robin (простой), least connections (лучше, когда запросы различаются по длительности), weighted (разное железо) и hashing (один и тот же клиент — на один и тот же сервер). Health check'и убирают сломанные инстансы: пассивные проверки следят за ошибками в реальном трафике, активные опрашивают health-эндпоинт. Sticky sessions привязывают пользователя к одному инстансу; они маскируют наличие состояния и разбалансируют нагрузку, поэтому предпочитайте общее хранилище сессий, а «липкость» оставьте для случаев вроде fallback'ов для WebSocket.

nginx
upstream app {
    least_conn;
    server 10.0.0.11:3000 max_fails=3 fail_timeout=10s;
    server 10.0.0.12:3000 max_fails=3 fail_timeout=10s;
    keepalive 32;
}
 
server {
    listen 443 ssl;
    server_name example.com;
    # ssl_certificate / ssl_certificate_key lines go here
    location / {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_next_upstream error timeout http_502 http_503;
    }
}

03Слои кэширования: CDN, reverse proxy, приложение, база данных

Самый дешёвый запрос — тот, что никогда не доходит до вашего приложения. CDN кэширует статические ассеты и кэшируемые страницы в edge-точках рядом с пользователями, а заодно поглощает простой DDoS-трафик. Кэш reverse proxy (nginx, Varnish) может несколько секунд отдавать повторяющиеся ответы, сглаживая всплески на горячих страницах. В приложении кэш Redis по паттерну cache-aside хранит результаты дорогих запросов с TTL. У базы данных есть собственный буферный кэш, так что хорошие индексы держат горячие данные в памяти. Для каждого слоя нужна ясная история инвалидации: версионируйте имена файлов ассетов, для страниц используйте короткие TTL или очистку при изменении, а при изменении данных удаляйте или обновляйте ключи. Защищайтесь от cache stampede, когда истекает популярный ключ, — блокировками, объединением запросов или слегка рандомизированными TTL.

04Масштабирование базы: пулинг, реплики, партиционирование, шардинг

База данных — обычное узкое место, потому что она хранит состояние и её трудно размножить. Действуйте по возрастанию стоимости. Сначала — индексы и исправление запросов, которые находятся через slow query log и EXPLAIN. Затем — пулинг соединений: PostgreSQL создаёт процесс на каждое соединение, так что десятки инстансов приложения могут его исчерпать; PgBouncer в режиме transaction позволяет тысячам клиентских соединений делить небольшой пул. Дальше — read-реплики для нагрузки с преобладанием чтения; помните, что реплики отстают, поэтому сценарии «прочитать только что записанное» должны идти в primary. Потом — партиционирование больших таблиц (по дате или tenant'у) внутри одной базы, чтобы индексы оставались маленькими, а старые данные удалялись дёшево. И последним — шардинг: разделение данных между несколькими базами по ключу шардирования, например ID tenant'а. Шардинг масштабирует запись, но делает трудными запросы между шардами, транзакции и ребалансировку, поэтому откладывайте его, пока не измерили реальную потребность.

ini
; /etc/pgbouncer/pgbouncer.ini
[databases]
app = host=127.0.0.1 port=5432 dbname=app
 
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20

05Очереди, back-pressure и планирование мощностей

Не всё обязано происходить внутри запроса. Отправка писем, генерация отчётов, ресайз изображений, синхронизация с ERP — отправьте это в очередь, и пусть воркеры обрабатывают задачи в ровном темпе. Очереди поглощают всплески, изолируют медленных внешних партнёров и позволяют масштабировать воркеры отдельно от веб-серверов. Добавьте и back-pressure: rate limit на каждого клиента, таймауты на каждый исходящий вызов, circuit breaker'ы для падающих зависимостей и плавную деградацию при перегрузке (отдавать кэшированные данные, скрывать необязательные виджеты). Всё это связывает воедино планирование мощностей. Измерьте текущую пропускную способность, перцентили задержки (p95, p99) и использование ресурсов; проведите нагрузочное тестирование инструментами вроде k6, чтобы найти точку отказа; определите, какой ресурс насыщается первым; затем держите запас (часто 30–50 процентов) на всплески и отказы нод. Повторяйте после крупных фич, потому что узкие места перемещаются.

  • Смотрите на задержку p95/p99, а не на средние: средние скрывают медленных пользователей.
  • Нагружайте копию staging с реалистичными объёмами данных.
  • Планируйте потерю одной ноды в пик (мощность N+1).

Когда применять

  • Рост трафика или данных приближает CPU, память, соединения с базой или задержку к пределу.
  • Нужна высокая доступность: отказ одного инстанса не должен ронять сайт.
  • Впереди предсказуемые всплески (кампании, запуски, сезонные пики).
  • Медленная фоновая работа блокирует запросы пользователей.

Когда не стоит

  • До измерений: чаще всего медленность — это отсутствующий индекс или N+1-запрос, а не нехватка серверов.
  • Шардинг базы, с которой ещё годы справлялись бы более мощный инстанс, реплики и кэширование.
  • Добавление инстансов приложения, которое всё ещё хранит сессии или файлы на локальном диске.

Альтернативы

  • Сначала сервер помощнее

    Вертикальное масштабирование дёшево по инженерному времени и часто даёт год или больше.

  • Управляемые платформы

    PaaS и serverless-платформы масштабируются сами, взамен вы теряете контроль и предсказуемость расходов.

  • Edge-кэширование

    Если отдавать больше страниц из CDN, для контентных сайтов можно убрать большую часть нагрузки на origin.

  • Автомасштабирование в Kubernetes

    Горизонтальное автомасштабирование подов для множества сервисов; тяжеловато для одного-двух приложений.

Лучшие практики

  1. Держите приложения stateless: сессии, кэш и файлы — в общих сервисах.
  2. Добавьте health-эндпоинт в каждое приложение и дайте load balancer'у им пользоваться.
  3. Ставьте таймауты и повторы с backoff на каждый сетевой вызов.
  4. Внедрите пулинг соединений до того, как добавлять инстансы приложения, делящие одну базу.
  5. Переносите медленную или внешнюю работу в очереди с повторами и обработкой dead-letter.
  6. Проводите нагрузочное тестирование перед крупными запусками и держите запас на отказ ноды в пик.

Частые ошибки

  • Масштабировать серверы приложения, когда настоящее узкое место — единственная база данных.
  • Полагаться на sticky sessions, чтобы скрыть локальное состояние, и терять пользователей, когда нода умирает.
  • Читать из отстающей реплики сразу после записи и показывать устаревшие данные.
  • Кэшировать без плана инвалидации и в итоге показывать неправильные цены или остатки.
  • После горизонтального масштабирования запускать cron-задачи на каждом инстансе, так что они выполняются по нескольку раз.

Глубже

06Теория очередей, которую можно применять: закон Литтла и утилизация

Закон Литтла связывает три числа: запросов в обработке = скорость поступления x среднее время в системе. При 200 запросах в секунду и 250 ms на запрос в любой момент в работе около 50 запросов — это говорит, сколько нужно воркеров, потоков или соединений с базой. Второй урок нелинеен: когда ресурс приближается к полной загрузке, время ожидания не растёт плавно, а взрывается. У сервера, занятого на 50 процентов, очереди короткие; на 90 процентах небольшие всплески создают длинные очереди, и p99 latency подскакивает. Вот почему важен запас и почему средние значения обманывают. Это же объясняет, почему добавление соединений к перегруженной базе делает её медленнее: больше параллельной работы — больше конкуренции, а не больше пропускной способности. Считайте размер пулов по закону Литтла с запасом и ограничивайте параллелизм, чтобы лишняя нагрузка ждала в очереди, которую вы контролируете.

  • В обработке = скорость x latency; используйте это для расчёта пулов и воркеров.
  • Планируйте, чтобы в пик ключевые ресурсы работали заметно ниже насыщения.
  • Ограничивайте параллелизм на краю, а не позволяйте ему копиться в базе.

07Шторм ретраев, бюджеты таймаутов и load shedding

Ретраи помогают при коротких сбоях и вредят при перегрузке. Если каждый из трёх слоёв ретраит по три раза, один пользовательский запрос может превратиться в 27 вызовов к нижнему сервису — ровно тогда, когда ему и так тяжело. Ретрайте только на одном слое, только идемпотентные операции, с exponential backoff и full jitter, и ограничивайте ретраи бюджетом (например, ретраи могут добавить не более 10 процентов нагрузки). Давайте каждому запросу дедлайн и передавайте оставшееся время вниз по цепочке, чтобы внутренние вызовы никогда не переживали терпение пользователя. При перегрузке отказывайте рано — 429 или 503 с Retry-After, вместо бесконечного ожидания в очереди: быстрый отказ дешевле медленного. Расставьте приоритеты трафика: отбрасывайте аналитику и рекомендации раньше, чем checkout. Circuit breaker прекращает вызывать зависимость, которая постоянно падает, и периодически её проверяет, давая время восстановиться.

nginx
# nginx: per-IP rate limit with a small burst, reject the rest fast
limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
limit_req_status 429;
 
server {
    location /api/ {
        limit_req zone=api burst=40 nodelay;
        proxy_connect_timeout 2s;
        proxy_read_timeout 10s;
        proxy_pass http://app;
    }
}

08Consistent hashing и масштабирование кэша

Если распределять ключи по N узлам кэша или шардам через hash(key) mod N, добавление одного узла переназначает почти все ключи: кэш пустеет, и база разом получает всю нагрузку. Consistent hashing размещает узлы и ключи на кольце; каждый ключ принадлежит следующему узлу по часовой стрелке, поэтому добавление или удаление узла перемещает лишь около 1/N ключей. Виртуальные узлы (много точек на один сервер) выравнивают распределение. Redis Cluster использует родственную идею: 16,384 hash-слота, распределённых по узлам и переносимых порциями при решардинге, а hash tag вроде {tenant:42} держат связанные ключи в одном слоте. В балансировщиках директива hash в nginx с параметром consistent делает то же самое при выборе upstream. Для горячих ключей, которые один узел не вытягивает, добавьте перед общим кэшем небольшой in-process кэш с коротким TTL.

09Tail latency и fan-out

Когда один запрос расходится на множество бэкендов, время ответа определяет самый медленный. Если у каждого бэкенда 1 процент вероятности медленного ответа, запрос, затрагивающий 100 из них, медленный большую часть времени. Tail latency возникает из-за пауз сборки мусора, холодных кэшей, шумных соседей, конкуренции за блокировки и очередей. Защита: уменьшайте fan-out за счёт предрасчёта или денормализации; ставьте таймауты на каждый вызов и возвращайте частичный результат, когда второстепенные части опаздывают; используйте hedged requests — отправку второй копии на другую реплику, если вызов превысил p95, только для идемпотентных чтений; держите размеры инстансов и настройки рантайма одинаковыми, чтобы один слабый узел не тянул вниз весь пул. Для ключевых эндпоинтов всегда показывайте p99 и p99.9 рядом с p50 и разбирайте самые медленные трейсы, а не только средние.

Реальные ситуации

01

После автоскейлинга закончились соединения с базой

Проблема

Во время кампании приложение масштабировалось с 4 до 16 инстансов. Через несколько минут сервер PostgreSQL начал отклонять соединения с ошибкой «too many clients», и весь сайт лёг.

Как решить

У каждого инстанса был пул на 25 соединений, так что 16 инстансов запросили 400 соединений при лимите около 200. Поставьте перед PostgreSQL PgBouncer в режиме transaction и направьте на него все инстансы, с серверным пулом, рассчитанным по закону Литтла (примерно 20–40 реальных соединений). Уменьшите пулы на инстанс, задайте statement timeout и ограничьте автоскейлинг числом, которое выдерживает слой базы данных. Проверьте поддержку prepared statements: PgBouncer 1.21 и новее умеет отслеживать prepared statements на уровне протокола через max_prepared_statements. Перед следующей кампанией прогоните нагрузочный тест масштабированной конфигурации.

02

Истечение кэша роняет сайт

Проблема

Список товаров на главной кэшируется на 10 минут. Каждые 10 минут latency подскакивает, а CPU базы на несколько секунд упирается в 100 процентов.

Как решить

Это cache stampede: когда ключ истекает, сотни запросов одновременно промахиваются и все его перестраивают. Используйте блокировку, чтобы перестраивал только один запрос, а остальные отдавали устаревшее значение (stale-while-revalidate), или обновляйте ключ в фоне незадолго до истечения. Добавьте к TTL случайный jitter, чтобы связанные ключи не истекали одновременно. На прокси proxy_cache_lock и proxy_cache_use_stale updating в nginx делают то же самое для целых страниц. И наконец, проверьте исходный запрос через EXPLAIN: перестроению, которое занимает секунды, обычно нужен индекс.

03

Одна медленная зависимость тормозит все страницы

Проблема

Сторонний API рекомендаций иногда отвечает 8 секунд. Пока он медленный, все воркеры приложения заняты ожиданием, и даже страницы, которые его не используют, падают по таймауту.

Как решить

Исчерпание воркеров: заблокированные вызовы занимают все слоты. Поставьте на этот клиент короткий таймаут (например, 800 ms), оберните его в circuit breaker и рендерьте страницу без рекомендаций, пока он разомкнут. Изолируйте зависимость bulkhead: отдельный небольшой лимит параллелизма для этого API, чтобы он никогда не занимал больше нескольких воркеров. Ещё лучше — загружайте рекомендации асинхронно из браузера или предрассчитывайте их в фоновой задаче. Настройте отдельный алерт на latency зависимости, чтобы видеть причину, а не только симптом.

Шпаргалка

Расчёт размера пула
Параллелизм = запросов/с x секунд на запрос (+ запас)
Upstream по least-connections
upstream app { least_conn; server 10.0.0.11:3000; }
Consistent hashing в nginx
hash $request_uri consistent;
Rate limit по IP
limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
Отдавать устаревшее во время обновления
proxy_cache_use_stale updating error timeout; proxy_cache_lock on;
Пул для множества инстансов
PgBouncer pool_mode = transaction
Найти медленные запросы
EXPLAIN (ANALYZE, BUFFERS) SELECT ...;
Нарастание нагрузки в тесте
k6 run --vus 200 --duration 10m script.js
Политика ретраев
Только идемпотентные, exponential backoff + full jitter, максимум 3, на одном слое

В моих реальных проектах

  • POS SaaS работает на Docker Compose за reverse proxy nginx и CDN, с PostgreSQL, Redis и отдельными воркерами BullMQ, обслуживающими 20 очередей.
  • Его realtime-слой использует комнаты socket.io для каждой компании, филиала, терминала и пользователя, а Redis pub/sub разносит события по инстансам.
  • Я ставлю Cloudflare перед сайтами ради DNS и базовой защиты от DDoS и переношу загрузки в S3-совместимое объектное хранилище, чтобы серверы приложений оставались stateless.
  • На платформе зоотоваров поиск работает на Algolia с rate limiting, и это снимает тяжёлый поисковый трафик с основной базы.

Вопросы и ответы

Q1Как бы вы масштабировали приложение, которое начинает тормозить?

Я измеряю, прежде чем что-либо менять. Метрики и трейсы показывают, куда уходит время: в приложение, в базу или во внешний вызов. Чаще всего это база: отсутствующие индексы, N+1-запросы или слишком много соединений. Я исправляю запросы, добавляю кэширование для горячих чтений, внедряю пулинг соединений и переношу медленную работу в очереди. Если насыщен сам уровень приложения, убеждаюсь, что оно stateless, и добавляю инстансы за load balancer'ом. Следующий шаг для нагрузки с преобладанием чтения — read-реплики. Шардинг — крайняя мера. После каждого изменения я провожу нагрузочный тест, чтобы убедиться, что узкое место действительно сдвинулось.

Q2Чем балансировка на L4 отличается от L7?

L4-балансировщик работает на транспортном уровне: пересылает TCP- или UDP-соединения по IP и порту, не заглядывая внутрь. Он очень быстрый, дешёвый и работает с любым протоколом, включая базы данных и сырые сокеты. L7-балансировщик понимает HTTP: терминирует TLS, маршрутизирует по имени хоста или пути, смотрит заголовки и cookie, повторяет идемпотентные запросы, применяет rate limit и может кэшировать. Это делает его идеальным для веб-приложений и API ценой большей обработки на каждый запрос. Многие конфигурации совмещают оба: L4-балансировщик для грубого распределения и L7-прокси за ним для маршрутизации.

Q3Почему sticky sessions считаются «запашком»?

Sticky sessions привязывают каждого пользователя к одному инстансу, обычно через cookie. Они создают видимость, что stateful-приложение работает за load balancer'ом, но проблемы никуда не деваются: когда этот инстанс перезапускается или удаляется, пользователь теряет сессию; нагрузка становится неравномерной, потому что тяжёлые пользователи остаются на одной ноде; деплой и автомасштабирование усложняются. Лучшее решение — убрать состояние из инстанса: сессии держать в Redis или использовать подписанные токены, а файлы — в объектном хранилище. «Липкость» всё ещё полезна в узких случаях, например для long-polling fallback'ов у WebSocket.

Q4Когда нужны read-реплики и в чём их подводные камни?

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

Q5Что такое пулинг соединений и почему он важен?

Открывать соединение с базой дорого, а PostgreSQL создаёт процесс на каждое соединение, так что несколько сотен соединений могут съесть много памяти и замедлить всё. Когда вы масштабируете приложение до множества инстансов, каждый со своим пулом, суммарное число легко превышает то, с чем база хорошо справляется. Пулер вроде PgBouncer стоит между ними и позволяет многим клиентским соединениям делить небольшое число реальных. В режиме transaction серверное соединение берётся только на время транзакции. Функции уровня сессии, такие как SET или advisory locks, в этом режиме требуют осторожности.

Q6Как вы подходите к планированию мощностей перед запуском?

Я начинаю с ожидаемых бизнес-показателей, например пикового числа заказов в минуту, и перевожу их в запросы в секунду на ключевые эндпоинты. Затем нагружаю staging-окружение с реалистичными данными инструментом вроде k6, наращивая нагрузку, пока перцентили задержки или доля ошибок не выйдут за согласованные цели, и отмечаю, какой ресурс насыщается первым. Продакшен-конфигурацию я рассчитываю на ожидаемый пик плюс запас — как правило, достаточный, чтобы потерять одну ноду в пик и всё равно справиться. Ещё я заранее готовлю рычаги: правила кэширования, переключатели фич, чтобы отключать тяжёлые виджеты, и воркеры очередей, которые можно быстро масштабировать.

Вопросы уровня senior

S1Когда бы вы шардировали и как бы выбрали ключ шардирования?

Только после того, как измерения покажут, что один primary, даже крупный, с репликами, кэшированием и партиционированными таблицами, не справится с объёмом записи или размером данных на планируемом горизонте. Ключ шардирования должен соответствовать доминирующему паттерну доступа, чтобы большинство запросов попадало в один шард: в B2B SaaS естественный выбор — tenant ID, потому что почти каждый запрос ограничен тенантом. Ключ должен равномерно распределять нагрузку, поэтому следите за очень крупными тенантами, которым нужен собственный шард. Избегайте ключей, создающих горячие точки, например временных меток. Держите справочник соответствия ключей шардам, чтобы тенантов можно было переносить без перехеширования всего. Издержки реальны: межшардовые запросы требуют fan-out или отдельного аналитического хранилища, транзакции остаются в пределах одного шарда, а миграции запускаются для каждого шарда.

S2Как вы решаете, по какой метрике автоскейлить?

Масштабируйте по сигналу, который лучше всего предсказывает насыщение того, что вы масштабируете. Для CPU-bound веб-слоёв подходит загрузка CPU; для I/O-bound приложений лучше число параллельных запросов или latency, потому что CPU остаётся низким, пока воркеры ждут. Для воркеров очередей масштабируйте по backlog, делённому на скорость обработки, — это оценка времени на разбор очереди. Добавьте периоды cool-down и задержки scale-in, чтобы избежать дёрганья, и масштабируйтесь заранее перед известными событиями, потому что новым инстансам нужно время на запуск и прогрев кэшей. Главное — проверьте, на что давит автоскейлинг: больше веб-инстансов означает больше соединений с базой и больше давления на общие зависимости. У хорошей политики автоскейлинга есть потолок, определяемый самым слабым компонентом ниже по цепочке.

S3Как сохранить отзывчивость системы, когда спрос превышает мощность?

Примите, что часть работы не будет выполнена, и выберите, какая именно. Сначала защитите ядро: admission control на краю быстро отклоняет лишние запросы с 429 или 503, вместо того чтобы дать очередям расти, пока всё не упадёт по таймауту. Расставляйте приоритеты по бизнес-ценности: сохраняйте checkout и логин, отбрасывая рекомендации, аналитические beacon и тяжёлые экспорты. Деградируйте функции мягко, отдавая кэшированные или упрощённые страницы. Откладываемую работу переносите в очереди ограниченного размера. Внутри ограничивайте параллелизм на каждую зависимость и используйте дедлайны, чтобы никакая работа не продолжалась после того, как клиент сдался. Компромисс явный: часть пользователей увидит ошибку или урезанную страницу, но система останется доступной для большинства, а не рухнет для всех.

Глоссарий

Горизонтальное масштабирование
Добавление новых одинаковых инстансов за балансировщиком вместо более мощной машины.
Закон Литтла
Число элементов в системе равно скорости поступления, умноженной на среднее время пребывания в ней.
Tail latency
Самые медленные ответы, например p99, которые всё равно получают многие пользователи.
Back-pressure
Сигналы или ограничения, замедляющие производителей, когда потребители или серверы не успевают.
Load shedding
Ранний отказ части запросов при перегрузке, чтобы остальные обрабатывались быстро.
Circuit breaker
Переключатель, который на время прекращает вызывать падающую зависимость, а затем проверяет её.
Bulkhead
Отдельные лимиты ресурсов на каждую зависимость, чтобы один сбой не съел всё.
Consistent hashing
Размещение ключей, при котором добавление узла перемещает лишь небольшую долю ключей.
Cache stampede
Множество запросов, одновременно перестраивающих одну и ту же истёкшую запись кэша.

Есть идея продукта?Давайте создадим его.

Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.