Получить доступ

Как бороться с хвостовыми задержками в распределённой системе?

SeniorЗадержка, пропускная способность и очереди
Ещё спрашивают так:
  • Что такое hedged requests?
  • Как снизить p99 при fan-out на много серверов?
  • Откуда берутся медленные запросы, если все серверы здоровы?
Коротко

Хвосты неизбежны: сборка мусора, фоновые задачи, соседи по машине, переключения. При fan-out они становятся нормой. Главные приёмы — не ждать медленного: дублирующие запросы, умная балансировка, частичные ответы.

  • Hedged request: нет ответа за p95 — отправить копию на другую реплику, взять первый ответ
  • Балансировка по нагрузке (least requests, power of two choices) вместо round robin
  • Разделять тяжёлые и лёгкие запросы, ограничивать очереди

А ещё Частичный ответ лучше позднего: поиск без одного шарда, лента без одного блока

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

Откуда хвосты даже на здоровых машинах:

  • паузы сборщика мусора и JIT-компиляция;
  • фоновые задачи: compaction в LSM, VACUUM, бэкапы, ротация логов;
  • общие ресурсы: соседние контейнеры, throttling CPU в Kubernetes, шумные соседи по диску и сети;
  • очереди: временный всплеск на одном экземпляре;
  • потери пакетов и повторная передача TCP (200 мс на RTO);
  • холодные кэши после перезапуска.

Устранить всё нельзя, поэтому строят системы, терпимые к хвостам (идея статьи Google «The Tail at Scale»).

Приёмы:

  • hedged requests — отправить запрос одной реплике; если ответа нет дольше p95, отправить копию другой и взять тот, что придёт первым. Добавляет ~5% нагрузки, а хвост срезает в разы. Только для идемпотентных чтений;
  • tied requests — отправить сразу двум, и когда один начал обработку, он отменяет копию у другого;
  • балансировка с учётом нагрузки — round robin продолжает слать на тормозящий экземпляр; least outstanding requests, power of two choices и выбрасывание медленных (outlier detection) уводят трафик;
  • разные очереди и пулы для тяжёлых и лёгких запросов, чтобы отчёт на 10 секунд не стоял перед запросом на 5 мс;
  • частичные ответы — агрегатор с дедлайном отдаёт то, что успело прийти: поисковая выдача без одного шарда, лента без рекомендаций;
  • меньше fan-out — меньше шардов на запрос, предагрегация, кэш на уровне агрегатора;
  • дедлайны вместо таймаутов — передавать оставшееся время по цепочке, чтобы не делать работу, результат которой уже не нужен.

Измерять — по гистограммам на стороне клиента, отдельно для каждой зависимости; смотреть не только p99, но и p99.9 — при большом fan-out именно он определяет p99 агрегатора.

Пример

async def hedged(get, replicas, hedge_after=0.05):
    first = asyncio.create_task(get(replicas[0]))
    done, _ = await asyncio.wait({first}, timeout=hedge_after)   # ждём ~p95
    if done:
        return first.result()
    second = asyncio.create_task(get(replicas[1]))               # дублируем
    done, pending = await asyncio.wait({first, second}, return_when=asyncio.FIRST_COMPLETED)
    for t in pending: t.cancel()
    return done.pop().result()

Что хотят услышать

  • Источники хвостов на здоровых машинах.
  • Hedged requests и почему они дёшевы.
  • Балансировка с учётом нагрузки и изоляция тяжёлых запросов.
  • Частичные ответы и дедлайны.

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

  • Дублировать неидемпотентные запросы.
  • Пытаться «победить» хвост только тюнингом GC.

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем