Прямой ответ
Load shedding — намеренный отказ части запросов при перегрузке. Без него система принимает больше, чем может обработать, очередь растёт, задержка увеличивается, и в итоге отказывают все запросы вместо части.
Ключевое различие: ограничение скорости (rate limiting) отказывает по формальному признаку — числу запросов от клиента. Load shedding отказывает по фактическому состоянию системы — насыщению ресурсов.
Три уровня защиты:
| Уровень | Механизм | Что делает |
|---|---|---|
| Параллелизм | Ограничение числа одновременных обработок | Не даёт занять всю память и соединения |
| Очередь | Ограничение длины ожидания | Не даёт запросам ждать дольше полезного |
| Адаптивный сброс | Отказ по метрике насыщения | Снижает нагрузку до уровня, который система выдерживает |
Простейшая реализация через семафор:
import asyncio
from fastapi import HTTPException
LIMIT = 200 # измерено под нагрузкой
sem = asyncio.Semaphore(LIMIT)
@app.post("/checkout")
async def checkout(payload: CheckoutIn):
if sem.locked() and sem._value == 0:
raise HTTPException(429, "Сервис перегружен",
headers={"Retry-After": "2"})
async with sem:
return await process(payload)
Код 429 Too Many Requests определён в RFC 6585. Заголовок Retry-After сообщает клиенту, через сколько повторить; без него клиенты начинают повторять немедленно, что усиливает перегрузку.
Важная деталь: проверка выполняется до приёма тела запроса. Если прочитать тело, а затем отказать, ресурсы уже потрачены.
Адаптивный сброс опирается на метрику насыщения:
def should_shed() -> bool:
# вероятность отказа растёт с загрузкой
load = current_load() / MAX_LOAD
if load < 0.8:
return False
return random.random() < (load - 0.8) / 0.2
Постепенный рост доли отказов предпочтительнее порога «всё или ничего»: при скачкообразном отказе клиенты синхронно повторяют запросы, создавая новую волну.
Практика: настройка и проверка
1. Измеряйте порог под нагрузкой. Число одновременных обработок, при котором задержка начинает расти нелинейно, — точка насыщения. Ограничение ставят на 10–20 % ниже; о методике — в статье про оценку нагрузки.
2. Отбрасывайте по приоритету. Фоновые задачи и неаутентифицированные запросы отбрасываются раньше, чем оформленные заказы. Единый отказ для всех теряет выручку там, где можно было обслужить важное.
3. Отличайте отказ от ошибки. 429 означает «повтори позже», 500 — «что-то сломалось». Клиент должен различать их: на 429 повторяют с задержкой, на 500 — нет.
4. Не складывайте ретраи. Если клиент повторяет запрос после 429 без задержки, а прокси добавляет собственные повторы, нагрузка растёт лавинообразно; о проблеме — в статье про retry storm.
5. Сбрасывайте раньше, чем растёт очередь. Запрос, ожидавший пять секунд и получивший отказ, бесполезен: клиент уже ушёл. Лучше отказать сразу.
6. Логируйте отказы отдельно. Метрика числа отброшенных запросов — основной индикатор того, что ёмкости не хватает.
7. Не путайте сброс с ограничением по клиенту. Rate limiting защищает от злоупотреблений конкретного источника; о стратегиях — в статье про rate limiting.
Рекомендации:
- Начинайте с ограничения параллелизма. Это самая простая и самая эффективная мера: она защищает память и пул соединений.
- Проверяйте поведение на перегрузке. Нагрузочный тест, который превышает ёмкость в два раза, показывает, отказывает ли система частично или падает целиком; о настройке — в статье про Locust.
- Согласуйте с клиентами. Поведение при 429 должно быть описано в контракте API, иначе каждый клиент придумает своё.
- Учитывайте автомасштабирование. Сброс нагрузки даёт время на добавление реплик; без него система падает до завершения масштабирования — см. статью про HPA.
- Сочетайте с circuit breaker. Отказ на входе и размыкание цепи на выходе решают разные задачи и применяются вместе; о паттернах — в статье про circuit breaker.
Практический сценарий: всплеск трафика уложил сервис целиком
Контекст: маркетинговая рассылка привела к четырёхкратному росту трафика. Сервис оформлений перестал отвечать всем пользователям, хотя до всплеска работал с загрузкой 40 %.
- Диагностика. Все обработчики были заняты, очередь запросов выросла до нескольких тысяч. Задержка превысила таймаут клиента, но сервер продолжал обрабатывать уже ненужные запросы.
- Причина. Ограничения параллелизма не было: сервер принимал все соединения. Память расходовалась на тела запросов в очереди, что привело к вытеснению подов.
- Усугубляющий фактор. Клиенты получали таймаут и повторяли запрос, поэтому нагрузка удваивалась каждые несколько секунд.
- Действия. (а) Ввели семафор на 180 одновременных обработок — на 15 % ниже измеренной точки насыщения; (б) отказ возвращали кодом 429 с
Retry-After: 2; (в) фоновые задачи перевели на отдельный пул с меньшим приоритетом и сбрасывали их первыми; (г) добавили метрику отброшенных запросов и алерт при превышении пяти процентов; (д) провели нагрузочный тест с двукратным превышением ёмкости и убедились, что система отказывает частично. - Результат. При следующем всплеске 22 % запросов получили 429, остальные обслуживались с обычной задержкой. Полного отказа не произошло.
Вывод: система без ограничения принимаемой нагрузки падает целиком при любом всплеске. Частичный отказ с корректным кодом ответа сохраняет основную функцию и даёт время на масштабирование.
FAQ
Чем load shedding отличается от rate limiting? Rate limiting ограничивает по формальному признаку — числу запросов от источника. Load shedding реагирует на фактическое насыщение системы и может отбрасывать запросы даже от клиента, не превысившего лимит.
Какой код ответа возвращать?
429 для перегрузки с Retry-After. Код 503 уместен, если сервис намеренно недоступен, например во время обслуживания.
Не потеряет ли клиент данные при отказе? Нет, если отказ происходит до обработки. Клиент получает 429 и повторяет запрос; идемпотентность защищает от двойного выполнения.
Можно ли обойтись только автомасштабированием? Нет. Масштабирование занимает минуты, всплеск длится секунды. Сброс нагрузки закрывает промежуток между всплеском и добавлением ёмкости.
Источники: Google SRE Book, RFC 6585 (429), RFC 9110 (Retry-After) — ссылки в шапке.