Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Load shedding: отказ вместо падения

Load shedding: как отбрасывать нагрузку и оставаться живым

Load shedding: границы очереди и параллелизма, адаптивный сброс по насыщению, приоритеты, 429 с Retry-After и проверка под нагрузкой.

Прямой ответ

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.

Рекомендации:

  1. Начинайте с ограничения параллелизма. Это самая простая и самая эффективная мера: она защищает память и пул соединений.
  2. Проверяйте поведение на перегрузке. Нагрузочный тест, который превышает ёмкость в два раза, показывает, отказывает ли система частично или падает целиком; о настройке — в статье про Locust.
  3. Согласуйте с клиентами. Поведение при 429 должно быть описано в контракте API, иначе каждый клиент придумает своё.
  4. Учитывайте автомасштабирование. Сброс нагрузки даёт время на добавление реплик; без него система падает до завершения масштабирования — см. статью про HPA.
  5. Сочетайте с circuit breaker. Отказ на входе и размыкание цепи на выходе решают разные задачи и применяются вместе; о паттернах — в статье про circuit breaker.

Практический сценарий: всплеск трафика уложил сервис целиком

Контекст: маркетинговая рассылка привела к четырёхкратному росту трафика. Сервис оформлений перестал отвечать всем пользователям, хотя до всплеска работал с загрузкой 40 %.

  1. Диагностика. Все обработчики были заняты, очередь запросов выросла до нескольких тысяч. Задержка превысила таймаут клиента, но сервер продолжал обрабатывать уже ненужные запросы.
  2. Причина. Ограничения параллелизма не было: сервер принимал все соединения. Память расходовалась на тела запросов в очереди, что привело к вытеснению подов.
  3. Усугубляющий фактор. Клиенты получали таймаут и повторяли запрос, поэтому нагрузка удваивалась каждые несколько секунд.
  4. Действия. (а) Ввели семафор на 180 одновременных обработок — на 15 % ниже измеренной точки насыщения; (б) отказ возвращали кодом 429 с Retry-After: 2; (в) фоновые задачи перевели на отдельный пул с меньшим приоритетом и сбрасывали их первыми; (г) добавили метрику отброшенных запросов и алерт при превышении пяти процентов; (д) провели нагрузочный тест с двукратным превышением ёмкости и убедились, что система отказывает частично.
  5. Результат. При следующем всплеске 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) — ссылки в шапке.