Распределённые системы
Retry amplification: разорви metastable storm
Читать про retry storm — не то же, что вытаскивать сервис из него. Собери короткую многослойную цепочку вызовов, впрысни короткий outage зависимости и смотри, как временный blip превращается в самоподдерживающийся простой, который не заживает, когда ты убираешь триггер. Затем приложи лестницу защит, пока storm не сможет стартовать — с доказательствами на каждом шаге.
Преврати ментальную модель юнита в воспроизводимый цикл: воспроизведи metastable retry storm, измерь fan-out, затем ограничь его jitter, retry budget и circuit breaker и докажи числами до/после, что система теперь сама восстанавливается, когда триггер прошёл.
Собери цепочку вызовов глубиной 3-4 слоя, которая схлопывается в metastable retry storm под коротким outage зависимости, затем добавь jitter, retry budget и circuit breaker, чтобы тот же outage остался blip'ом — доказывая каждый шаг измерениями, а не утверждениями.
- Таблица до/после: пиковая частота вызовов зависимости (как кратность базы), отношение retry-к-исходным, время восстановления после устранения триггера и p99-задержка — всё измерено под идентичным впрыснутым outage, не на глаз.
- Базовый прогон доказуемо показывает метастабильность: частота вызовов зависимости держится повышенной и запросы продолжают сбоить после конца 8-секундного outage, пока ты насильно не снизишь нагрузку.
- Со всеми включёнными защитами пиковая нагрузка на зависимость в полном outage держится в пределах ~10% над базой (retry budget держит), и breaker наблюдаемо открывается, затем через half-open-пробу возвращается в closed.
- Абзац-разбор, объясняющий, какая защита ограничила какое свойство (jitter -> тайминг, budget -> объём, breaker -> окно восстановления, идемпотентность/дедлайн -> бесполезная работа) и почему одного backoff было недостаточно.
- Добавь страницу on-call-runbook: как распознать metastable retry storm по четырём панелям, почему охота за устранённым триггером — ловушка, и playbook load-shed / breaker / restart для форсирования восстановления.
- Сделай вызов зависимости неидемпотентным (например, он инкрементит счётчик), добавь ключ идемпотентности и покажи, что ретраи больше не применяют эффект дважды под storm.
- Прогони свип по числу ретраев (1, 2, 3) и глубине цепочки (2, 3, 4) и построй измеренную пиковую нагрузку зависимости против предсказанной кривой retries^depth, чтобы эмпирически подтвердить геометрический закон.
- Сравни full jitter, equal jitter и без jitter под той же толпой и воспроизведи результат AWS: варианты с jitter завершаются за гораздо меньшее общее число вызовов с меньшим временем восстановления, чем фиксированный backoff.
Это цикл, который ты прогоняешь в каждом реальном retry-инциденте: воспроизведи storm и подтверди, что он метастабилен (он переживает триггер), измерь fan-out против retries^depth, затем ограничь каждое свойство правильным инструментом — jitter для тайминга, retry budget ~10% для объёма, circuit breaker для окна восстановления, идемпотентность и проброс дедлайна для бесполезной работы — и проверь числами до/после под идентичным outage. Сделав это раз на игрушечной цепочке, ты доводишь production-версию до мышечной памяти: ты мгновенно узнаешь сигнатуру и потянешься к снижению нагрузки, а не к устранённому триггеру.