awesome-everything EN
↑ Обратно к восхождению

Архитектура бэкенда

Таймауты и хвостовая задержка: бюджеты, дедлайны и ловушка fan-out

Суть Каждому хопу нужен таймаут, и таймауты должны складываться в бюджет на весь запрос. На масштабе доминирует хвост: разветвитесь к достаточному числу сервисов — и самый медленный решает задержку почти каждого пользователя.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 18 min

Каждый сервис в цепочке здоров: любая зависимость отвечает за 10 мс на 99-м перцентиле. Страница продукта разветвляется к 100 из них параллельно и ждёт всех. И всё же 63% загрузок страницы занимают больше секунды. Ни один сервис не медленный. Медленна математика хвоста — и интуиция большинства инженеров про средние полностью её прячет.

Таймаут на каждом хопе, или зависание на каждом инциденте

Сетевой вызов без таймаута — баг, ждущий инцидента. Когда зависимость перестаёт отвечать (не отказывает — зависает), вызов без таймаута ждёт вечно, удерживая поток или соединение. Достаточно таких вызовов — и пул исчерпан, и одна медленная зависимость роняет здоровый сервис. Каждому исходящему вызову — запросу к БД, кешу, HTTP, RPC — нужен явный таймаут. Дефолт в большинстве клиентов — нет таймаута, и это худший дефолт в backend-инженерии.

Таймауты должны складываться в бюджет

Потаймауты на хоп, выставленные изолированно, лгут. Если у запроса SLA 1 с, но он вызывает сервис A (таймаут 1 с), который вызывает сервис B (таймаут 1 с), то когда B медленный, A ждёт всю секунду, а клиент уже сдался — A теперь делает работу, которую никто не ждёт. Лечение — бюджет таймаута (дедлайн): точка входа выделяет общий объём, и каждый хоп передаёт оставшееся время вниз. gRPC формализует это как дедлайн, распространяемый в метаданных; каждый сервис вычисляет свой локальный таймаут как min(локальный дефолт, оставшийся бюджет).

ПодходЧто использует каждый хопРежим отказа
Нет таймаутовОдна зависшая зависимость исчерпывает пулы, каскад
Независимые потаймауты на хопФиксированное локальное значениеВнутренняя работа переживает терпение вызывающего
Распространяемый дедлайн (бюджет)min(локальный, оставшийся)Ограничен; внутренние хопы стоп, когда бюджет потрачен

Почему хвост, а не среднее, — это SLA

Пользователи не переживают ваше среднее. Они переживают свой запрос, и медленные — то, что они запоминают и что срывает алерты. Поэтому задержку отчитывают перцентилями: p50 (медиана), p99 (1 из 100 хуже), p99.9. Разрыв между p50 и p99 — «хвостовая задержка», вызванная паузами GC, очередями, промахами кеша, конкуренцией за блокировки и повторами.

Опасность — усиление хвоста под fan-out. Если один запрос к сервису медленный с вероятностью p, то запрос, разветвляющийся к N сервисам параллельно и ждущий всех, медленный, если медленный любой один — вероятность 1 − (1 − p)^N. При посервисном p99 (p = 1%) и N = 100 это 1 − 0.99^100 ≈ 63%. Это число из Hook, прямо из The Tail at Scale Дина и Барросо: сервис, разветвляющийся к 2000, оставляет около 20% запросов дольше секунды, даже когда p99 каждого бэкенда в норме.

Защита хвоста: хеджирование, а не только таймауты

Таймаут ограничивает худший случай, но не улучшает типичный хвост. Техника из The Tail at Scaleхеджированные запросы: отправьте запрос, и если ответ не пришёл к задержке p95, отправьте вторую копию другой реплике и возьмите ту, что вернётся первой. Поскольку хеджируются только медленные ~5%, лишняя нагрузка мала (~5%), а хвост схлопывается — в измерениях Google отправка хеджа после задержки в 10 мс срезала p99.9 с 1800 мс до 74 мс ценой ~2% лишних запросов. Связанные запросы идут дальше: дубликаты говорят друг другу отмениться, как только один начал выполняться, обрезая зря потраченную работу.

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

Почему не просто повтор по таймауту вместо хеджирования? Повтор срабатывает только после того, как вы уже заплатили полный таймаут — поэтому он улучшает доступность, но не задержку, а наивные повторы усиливают нагрузку ровно тогда, когда сервис и так в беде (шторм повторов). Хеджирование срабатывает спекулятивно на p95, до таймаута, поэтому атакует задержку напрямую; и поскольку оно ограничено медленным хвостом, добавляет ограниченную нагрузку. Эти двое дополняют друг друга: хеджируй, чтобы срезать хвост, повторяй с backoff и jitter, чтобы пережить сбои, и circuit breaker (следующий юнит), чтобы остановить оба, когда зависимость по-настоящему лежит.

Викторина

Страница разветвляется к 100 независимым сервисам параллельно и ждёт всех. У каждого p99 = 10 мс (1% шанс, что вызов превысит это). Примерно какая доля загрузок страницы превысит 10 мс хотя бы на одном вызове?

Викторина

Почему независимые потаймауты на хоп не защищают запрос с общим SLA?

Викторина

Как хеджированный запрос снижает хвостовую задержку без большой лишней нагрузки?

Вспомните перед уходом
  1. 01
    Почему каждому исходящему вызову нужен явный таймаут, и почему распространяемый дедлайн лучше независимых потаймаутов на хоп?
  2. 02
    Объясните усиление хвоста под fan-out с математикой и каноническими числами.
  3. 03
    Что такое хеджированные и связанные запросы, и почему их предпочитают повторам для среза хвоста?
Итог

Последняя остановка превращает запрос из «он возвращается» в «он возвращается вовремя». Каждому исходящему вызову нужен явный таймаут, потому что дефолт ждать вечно даёт одной зависшей зависимости исчерпать пулы и пойти каскадом. Но изолированные таймауты не складываются, поэтому они должны сворачиваться в распространяемый дедлайн, где каждый хоп использует min(локальный, оставшийся) — модель, которую стандартизирует gRPC. На масштабе среднее — ложь: пользователь чувствует свой запрос, поэтому отслеживай p99/p99.9, а fan-out усиливает хвост жестоко — 1 − (1 − p)^N достигает 63% медленных при p=1%, N=100. Таймауты ограничивают худший случай, но не чинят типичный хвост; хеджированные и связанные запросы, запущенные на p95, схлопывают его ценой пары процентов лишней нагрузки. Это мост к устойчивости: когда зависимость не просто медленна, а сбоит, таймаутов и хеджирования мало, и circuit breakers и bulkheads следующего юнита берут управление.

Связанные уроки
встречается в185
Продолжить восхождение ↑Жизненный цикл запроса: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.