Job 2026 md

title: Статья 08 — Отказоустойчивость и надёжность source: подготовлено 2026-09-18, TASK-45.48; каркас — brief.md §08; развёртка knowledge-base.md §9 (мосты в §7, §10, §13.5); источники — DDIA 2-е изд. гл. 9 (TASK-45.39), статья Яндекса (TASK-45.5), Сюй гл. 6 (TASK-45.38), эталоны classic-designs.md §1–2, §4


Статья 08: отказоустойчивость и надёжность

Восьмая статья серии — и она особенная: отказоустойчивость заявлена в фокусе нашей секции («нарисовать крупноблочную схему сервиса и объяснить отказоустойчивость»). Критерий «умею, если» из брифа: для нарисованной схемы прохожу по каждой ноде и говорю, что будет при её падении. Это не «рассказать про circuit breaker» — это протокол: интервьюер смотрит на вашу схему, тычет в блок и спрашивает «а это упало — что?». Ответ должен быть мгновенным и однотипным для любого блока — именно этому посвящена статья, и §3 с §10 дают протокол прогонки целиком.

Место раздела в серии: статья 05 уже разобрала отказы нод с данными (failover лидера — §5), статья 06 — отказ кэша (§8). Здесь это собирается в общий метод: словарь надёжности (девятки, N+1), поведение любого вызова на отказ (таймаут → ретрай → circuit breaker), самозащита под перегрузкой, отказ целого ДЦ (majority-коммит) — и сводная прогонка по типовой схеме из статьи 03 §6. Раздел живёт на этапе 6 (эксплуатация), но пропитывает всю схему: N+1 и health checks — этап 3, таймауты и идемпотентность — этап 2. Методичка говорит прямо: «эксплуатацию не режем никогда — именно она отличает senior-ответ» (../methodology.md §1). Глубина — ../knowledge-base.md §9; полные разборы — ../classic-designs.md.

1. Словарь: девятки, N+1 и арифметика надёжности

Три термина, с которых начинается любой разговор об надёжности, — и которые кандидаты чаще всего путают:

  • Fault (сбой) — отклонение одного компонента от спецификации: умер диск, отвалилась реплика, завис запрос. Failure (отказ) — система в целом перестала выполнять свои функции. Формула DDIA: надёжность = система продолжает работать при сбоях компонентов; задача — чтобы fault не дорастал до failure. Мы не «предотвращаем сбои» (невозможно) — мы их изолируем.
  • SLA / SLO и девятки. 99.9 % («три девятки») — это 8.7 ч простоя в год / 43 мин в месяц; 99.99 % — 52 мин в год; 99.999 % — 5 минут в год, это уже дорого. На секции достаточно трёх девяток с объяснением цены: каждая следующая девятка — примерно на порядок дороже (резервирование, много-ДЦ, автоматика).
  • N+1 — на каждом ярусе держим на одну ноду больше, чем нужно для пика: любой один отказ поглощается запасом, ёмкость не деградирует. «N+2» — если хотим переживать отказ во время обновления (эталон Яндекса держит до двух запасных воркеров на ДЦ).

Арифметика, которую полезно произнести вслух (она же — аргумент за N+1 и мульти-ДЦ):

  • Последовательное соединение (запрос проходит через все компоненты): доступность перемножается. Пять компонентов по 99.9 % дают 0.999⁵ ≈ 99.5 % — 43 часа простоя в год. Вывод: каждый новый компонент на пути запроса — минус к надёжности; длинные синхронные цепочки — враг.
  • Параллельное (запрос обслужит любой из N): доступность = 1 − ∏(1 − ai). Две реплики по 99.9 % → 1 − 0.001² = 99.9999 % — полминуты в год. Вывод: репликация превращает unreliable-компоненты в reliable-систему (DDIA: «надёжная система из ненадёжных деталей»).

Две аббревиатуры для разговора о планах восстановления: RTO (recovery time objective — сколько максимум восстанавливаемся: «failover за 30 с») и RPO (recovery point objective — сколько данных можно потерять: «semi-sync → потерянный хвост стремится к нулю, async → секунды»). Они мгновенно переводят разговор об отказах из «будем надёжнее» в конкретные числа — маркер production-зрелости. Формулировка-канва раздела: «отказ любого одного элемента не должен быть виден пользователю; всё, что видно — это деградация, и она спроектирована заранее».

2. Partial failure: почему «работает или нет» больше нет

Фундамент раздела — глава 9 DDIA: распределённая система принципиально отличается от одиночного компьютера тем, что ломаться может частично и недетерминированно (partial failure). На одной машине «либо работает, либо нет»; в сети между нодами — «в основном работает, иногда что-то где-то зависло», и это не баг, а свойство среды. Что нужно держать в голове из DDIA гл. 9 (конспект — ../materials/ddia-2ed/ch-09-trouble-distributed.md):

  • Сеть асинхронна и гарантий не даёт. Отправили запрос и нет ответа — различить невозможно: запрос потерялся, стоит в очереди, нода умерла, нода жива но в GC-паузе, обработано но ответ потерялся, ответ просто задерживается. Шесть ситуаций — один симптом.
  • Таймаут — это догадка. Единственный доступный детект — «не дождались за T». Короткий T → ложные срабатывания (нода жива, но медленна под нагрузкой) и каскад: «мёртвую» ноду разгрузили на соседей, которым и так тяжело. Длинный T → пользователь ждёт. Выбор T — экспериментально, по измеренному распределению RTT (§4).
  • «Нет ответа» ≠ «не выполнено». Запрос мог выполниться, а ответ потеряться. Отсюда железное правило: ретраить можно только идемпотентные операции (§5) — иначе двойной платёж.
  • TCP не спасает. ACK означает «ядро получило», а не «приложение обработало»; закрытый с ошибкой коннект не сообщает, сколько данных обработано; реконнект + повторная отправка = дубликаты. Надёжность — обязанность приложения, а не транспорта.
  • Process pauses. GC, миграция VM, swap — нода «засыпает» на секунды, не замечая, что её lease истёк, и просыпается зомби-лидером. Лечение — fencing token (§8; подробно в разделе 09). Часы: wall-clock прыгает и дрейфует — длительности только по monotonic, времена машин несравнимы (мост в 09).
  • Gray failure (limping node). Нода не умерла, а «хромает»: отвечает, но в разы медленнее. Это хуже чистого отказа — health check зелёный, а пользователи страдают. Лечится перцентилями и выводом из ротации по латентности, а не только по «жив/мёртв».
  • Кворум объявляет ноду мёртвой — она обязана подчиниться. Узел знает только то, что получил сообщения; если большинство сказало «ты вне игры» — спорить бесполезно. Majority-кворум — единственный с гарантированным пересечением мнений (§9).

Практический вывод DDIA — «подозрение, пессимизм и паранойя окупаются»: отказы создают искусственно и смотрят, что происходит (chaos-инженерия), потому что непротестированная обработка отказов — это не «упадёт», а «может сделать что угодно». Одна фраза для секции: «мы не предотвращаем отказы — мы их репетируем».

Число для атмосферы: исследование среднего ДЦ из DDIA — ~12 сетевых инцидентов в месяц, половина отключает стойку целиком. Сеть падает регулярно, даже в идеальном датацентре; проектировать надо от этого.

3. Протокол SPOF-анализа: «прохожу по каждой ноде схемы»

Главный инструмент раздела — метод, которым отвечают на ключевой вопрос секции. SPOF (single point of failure) — элемент, отказ которого роняет систему целиком. Анализ делается в два слоя.

Слой 1 — по построению. Пока рисуем схему (этап 3), соблюдаем правила, после которых SPOF не остаётся:

  • N+1 на каждом ярусе: ≥2 балансировщика, ≥2 инстанса каждого сервиса, кэш-кластер, БД — лидер + реплики, очередь — кластер. Stateless-ноды (статья 03 §5) делают это дёшево: ноду можно убить и склонировать без потери данных.
  • Реплики — в разных стойках и ДЦ (rack/DC awareness): три реплики в одной стойке умирают вместе с ней; «мульти-ДЦ» из реплик в одном ДЦ — это не мульти-ДЦ.
  • Зависимости наружу тоже считаем: внешний платёжный шлюз, S3, SMTP — тоже наши зависимости: на них §4–6 (таймаут, CB, деградация), даже если реплицировать их нельзя.

Слой 2 — прогонка по готовой схеме. Взгляд интервьюера на этап 6: «прохожу по каждой ноде: узел → отказ → что происходит → что делаю → чем это видно в метриках». Четыре шага на каждый блок, одинаково для любого узла:

  1. Что происходит? — воздействие на пользователя за первые секунды: «незаметно», «медленнее», «часть фич недоступна», «запись стоит 30 с».
  2. Что делает система? — механизм: health check вывел из ротации, LB перелил, failover продвинул реплику, CB открылся, деградация включилась.
  3. Сколько это длится / что теряем? — время переключения (RTO) и потери (RPO): «запись недоступна ~30 с до выборов, потери — хвост async-репликации, поэтому semi-sync».
  4. Чем видно в метриках? — конкретный сигнал: hit rate, replication lag, consumer lag, 5xx, время failover.

Прогонка должна закончиться закрытием всей системы: после отдельных узлов — «а если весь ДЦ?» (§9) и «а если нагрузка ×10?» (§7). Это два стандартных продолжения вопроса, и оба заявлены в статье Яндекса как обязательные («переживание отказа сервера и ДЦ; превышение нагрузки в разы — чем жертвуем для выживания»).

Health checks — механика, которая делает прогонку автоматической. Без них LB льёт трафик на труп. Два вида: active (LB/оркестратор поллит /health) и passive (считает 5xx/таймауты живого трафика и выводит сам). Две проверки: liveness («процесс жив?» — рестартовать) и readiness («готов принимать трафик?» — проверяет зависимости; нода жива, но не готова → выводим из ротации, не рестартуем). Фраза для секции: «readiness выводит ноду из ротации LB раньше, чем это заметят пользователи; при деплое — grace period: вывели, дали дообработать in-flight, погасили» — мост к rolling-выкату из статьи 12.

4. Таймауты: первый и обязательный механизм

Если из всего раздела успеть сказать одну фразу — эта: таймаут на каждый внешний вызов, без исключений. Без него один зависший dependency (внешний API, БД, кэш) съедает потоки/соединения пула, очередь растёт, и система умирает целиком из-за одной медленной зависимости — классический каскад. Дефолтные таймауты клиентов (часто бесконечные или 30–60 с) — тоже fault.

Как выбирать значение — вопрос с подвохом, и на него есть взрослый ответ:

  • Измерить, не угадать: снять распределение RTT зависимости на рабочей нагрузке (p50/p95/p99) и взять p99 × запас 2–3. «Система в 10 раз медленнее обычного — уже сигнал беды; таймаут должен ловить именно беду, а не пиковую нагрузку».
  • Учитывать цепочку (бюджет времени): пользовательский SLO 200 мс минус сумма таймаутов на пути — если на зависимость осталось 50 мс, таймаут 500 мс бессмысленен: пользователя уже нет. Считаем назад от SLO — тот же back-of-envelope из статьи 02.
  • Адаптивные таймауты: системы меряют RTT непрерывно и подстраивают порог (phi accrual detector из DDIA — Akka, Cassandra; так же работает TCP RTO). Достаточно назвать как «знаю, что бывает».
  • Таймаут ≠ только на чтение: очередь в БД, пул соединений, продюсер в Kafka — везде есть свои дедлайны; runaway-запросы в БД убивают не хуже сетевых.

Связка с DDIA гл. 9: очереди — главный источник переменной латентности (коммутаторы, потоки, VM-паузы); система при 100 % утилизации имеет неограниченные задержки. Поэтому headroom ≥ 2× (KB §10) — это тоже механизм отказоустойчивости, а не расточительство.

5. Ретраи и идемпотентность: повторять — только безопасно

Таймаут сказал «не дождались» — что дальше? Правило из DDIA: раз «нет ответа» ≠ «не выполнено», ретраить можно только идемпотентные операции. Два столба:

Идемпотентность. Операция идемпотентна, если повтор даёт тот же результат (GET, PUT со старым содержимым, DELETE). Неидемпотентные (POST «списать $100», инкремент) ретраить напрямую нельзя — будет двойное списание. Инженерное решение — idempotency key: клиент генерирует UUID на операцию, сервер хранит результат по ключу и на повтор отдаёт тот же ответ вместо повторного исполнения (так работают платёжные API — Stripe). Внутри системы: уникальный индекс / INSERT ... ON CONFLICT / дедуп по ключу события (консюмеры очередей — at-least-once + дедуп, раздел 07). Фраза для секции: «транспорт дублирует, приложение дедуплицирует: at-least-once превращаю в effectively-once идемпотентным ключом».

Экспоненциальный backoff + jitter. Пауза между попытками растёт: 100 мс → 200 → 400 → …, максимум 2–3 попытки. Jitter (случайный разброс) обязателен: без него тысячи клиентов, получивших таймаут одновременно, ретраят синхронно — пачка запросов бьёт в ещё не восстановившуюся зависимость, таймауты растут, ретраев становится больше — retry storm, частный случай metastable failure (KB §10): система застревает в перегрузе даже после ухода исходной причины. Три ограничителя: jitter, лимит попыток, retry budget — ретраи не больше N % трафика.

Формулировка-трёхступенька, которой закрывается любой вопрос про ненадёжный вызов: «таймаут ловит зависание; ретрай с backoff и jitter чинит переходные сбои — только для идемпотентных; circuit breaker (§6) останавливает ретраи, когда зависимость лежит всерьёз».

6. Circuit breaker и bulkhead: изоляция отказов

Если зависимость упала надолго, ретраи (даже правильные) — лишняя нагрузка и лишняя латентность: каждый запрос ждёт таймаут. Circuit breaker — автоматический выключатель с тремя состояниями:

  • Closed (норма): запросы идут, счётчик ошибок копится в скользящем окне.
  • Open (сработал): при ошибках выше порога (например, >50 % за 10 с) — фейлим мгновенно, не тратя таймаут; запросы либо получают заглушку/деградацию, либо быструю ошибку.
  • Half-open (проба): через паузу (десятки секунд) пропускаем один пробный запрос; успех → closed, неудача → open снова.

Что проговаривать: CB дёшево фейлит (микросекунды вместо секунд таймаута), даёт зависимости время восстановиться (не долбим лежащий сервис ретраями) и разрывает каскад — перегруз не расползается по системе. Двойная польза с метриками: доля открытых CB — дежурный индикатор здоровья зависимостей. Нюанс, который любят проверять: CB бесполезен без таймаутов — пока запрос висит, ошибок нет, порог не копится, breaker не откроется. Порядок внедрения именно такой: таймаут → ретрай → CB.

Bulkhead (переборки, как на корабле): на каждую зависимость — отдельный пул соединений/потоков. Тогда зависимый от медленного API пул исчерпается, а остальные вызовы (БД, кэш) продолжат работать отдельными переборками. Без bulkhead один dependency съедает общий пул — тот же каскад, но по ресурсам. Вместе: CB — временная изоляция (не ходить к лежащему), bulkhead — пространственная (лежащий не топит соседей).

7. Перегрузка: rate limiting, load shedding, graceful degradation

Второй штатный сюжет отказов — система цела, но нагрузка превышает возможности: пик, рекламная кампания, бот-атака, а то и просто ретраи клиентов (§5). Статья Яндекса требует ответа на «превышение нагрузки в разы/порядки — чем жертвуем для выживания». Три механизма по нарастанию:

  • Rate limiting — ограничение входящего потока на пользователя/ключ/IP: token bucket / leaky bucket / sliding window (алгоритмы — статья 10, здесь только место в картине); ответ 429 + Retry-After, чтобы честный клиент отступил сам. Ставится на шлюзе (self-protection) и выдаётся клиентам как контракт (лимиты API).
  • Load shedding — при перегрузке отказываем быстро и дёшево: 503 без обработки, а не 30-секундный таймаут, который съест наши же потоки. Осознанно выбираем, кого shedsим: приоритеты по ценности — «платёж важнее рекомендаций, чтение ленты важнее счётчиков» (эталон ленты: приоритет «чтение > постинг > счётчики», shedding на постинге). Строгий вариант — приоритетные очереди запросов.
  • Graceful degradation — вместо ошибки отдаём уменьшенную версию: кэш-стейл вместо свежих данных, «старая лента» вместо пересборки, тексты без картинок, отключаем счётчики/рекомендации/превью. Формула: «жертвуем свежестью и полнотой, не доступностью». Проговаривать обязательно заранее спроектированный список деградаций — решать «чем жертвуем» в момент инцидента поздно: деградации — это фиче-флаги, выключатели по приоритету, прописанные до инцидента.

Плюс конкретика из эталона Яндекса (shortener) — что делаем при «нагрузке на чтение в разы»:

  1. Короткие пики гасит запас: очередь запросов на воркерах + headroom по времени ответа («на ответ 150 мс, воркер отвечает ≤10 мс — есть, чем гасить всплеск»).
  2. Подозрительный трафик — отдельно: детект аномалий на балансировщике (хосты/подсети), blacklist от воркеров (одинаковые/несуществующие ссылки — абьюз).
  3. Деградация вычислений: понижение криптостойкости SSL (handshake — самый дорогой ресурс воркера, статья 02 §5).

Метка зрелости — понимать, что перегруз и отказ — один и тот же каскад: metastable failure (перегруз → таймауты → retry storm → ещё перегруз) лечится всей триадой сразу (backoff+jitter у клиентов, shedding и CB у нас). Ссылка вперёд: наблюдаемость перегруза (saturation, очереди, p99) — золотые сигналы из KB §10, подробно в статье 12.

8. Failover данных: детект → выборы → реконфигурация

Отказ ноды с данными — самый дорогой отказ, и он целиком разобран в статье 05 §5; здесь — выжимка в четыре строки + что добавить из DDIA гл. 9:

  1. Детект: heartbeat не отвечает таймаут (~30 с) → нода считается мёртвой. Детект — кворумом реплик, не одной нодой; таймаут из измеренного RTT, не с потолка.
  2. Выборы: продвигается самая свежая реплика (минимум потерь); выборы большинства — это консенсус (Raft/Paxos, подробно — раздел 09).
  3. Реконфигурация: клиенты пишут новому лидеру; старый обязан подчиниться — «кворум объявил мёртвым — подчинись, даже если ты жив» (§2).
  4. Три ловушки — проговаривать все: потеря async-хвоста (GitHub-кейс переиспользования PK → semi-sync + снежинка ID), split brain (→ fencing token: эпоха/term, хранилище отвергает записи старого лидера), разогрев нового лидера (холодные кэши + шторм ретраев от клиентов → jitter).

RTO/RPO здесь обретают числа: авто-failover = RTO десятки секунд; semi-sync = RPO ≈ 0 ценой латентности записи. Позиция для секции (из 05): «автоматика быстрее, но ложный failover дороже простоя — для критичных данных полуручной failover с runbook; fencing — всегда». Отказ целого шарда — продвижение реплики шарда, чтение смягчает кэш; отказ кэша — статья 06 §8.

9. Много-ДЦ и majority-коммит

Финальный класс отказов — целый ДЦ: питание, сеть региона, пожар (байка DDIA: грузовик в HVAC). Топологии:

  • Active–passive: один ДЦ работает, второй — тёплый резерв (реплики, подняты сервисы без трафика). Проще (нет конфликтов записи), дешевле в сложности, но резерв простаивает и failover — переключение трафика (минуты). Подходит для «двух девяток с запасом».
  • Active–active: все ДЦ принимают трафик. Утилизация полная, failover мгновенный (трафик просто перераспределяется), но записи в нескольких ДЦ → синхронная репликация между ДЦ (латентность меж-ДЦ: RTT Москва–NY ~120 мс — синхронный коммит на другой континент убивает латентность записи; потому между ДЦ ставят в одном регионе или пары близких ДЦ) и риск конфликтов (multi-leader — «избегаю без необходимости», статья 05 §4).

Majority-коммит — как переживают потерю ДЦ с данными: запись подтверждается большинством реплик; кластер жив, пока живо большинство. Арифметика топологий:

  • 3 ДЦ (или 5): потеряли один — осталось 2 из 3, большинство есть, кластер продолжает принимать записи. Канонический ответ.
  • 2 ДЦ: потеряли один — остался 1 из 2, большинства нет: либо оба могут писать (split brain), либо никто (кластер встал на запись). Решения: третий мини-ДЦ/witness-нода для кворума (2 ДЦ + арбитр) или явный приоритет одного ДЦ (ручной tie-break). Проговорить как известную ловушку — «2 ДЦ — это не HA, это дилемма».
  • Реплики по ДЦ: RF=3 → лидер + 2 реплики в разных ДЦ; потеря ДЦ = потеря одной реплики каждого ключа.

Что происходит при потере ДЦ на этапе трафика: GeoDNS/балансировка по регионам перестают отдавать адрес мёртвого ДЦ (TTL короткий!) — трафик переливается в живые; внутри — лидеры, попавшие в потерянный ДЦ, переизбираются в живых (majority налицо). Метрики: доступность по регионам, счётчики failover, replication lag после переключения.

Между ДЦ — почти всегда AP (Сюй гл. 6, Dynamo-подход): синхронный кворум между ДЦ дороже доступности, поэтому на меж-ДЦ уровне допускают sloppy quorum + hinted handoff: при недоступности «своих» узлов пишем на ближайшие доступные с пометкой (hint), отдаём владельцу, когда тот вернётся; конфликты разрешаются на чтении (vector clocks). Назвать как паттерн «AP-кластера без кворума» — углубление по запросу в раздел 09.

Эталон для переноса (статья Яндекса, shortener): «пережить отказ целого региона, сохранив 25 воркеров» → 9 серверов × 4 ДЦ (запас на отказ до двух воркеров при недоступном регионе) + 4 IPVS (балансировщик в каждом ДЦ — не тянуть маршрут за пределы ДЦ) + 3 сервера СУБД с консенсусом для автопереключения primary. Итого 43 сервера — и это готовая фраза: «топология считается от требования пережить отказ ДЦ, а не от пиковой нагрузки».

10. Сводная прогонка по типовой схеме

Ядро раздела — таблица, которой отвечают на вопрос секции. Типовая схема из статьи 03 §6 (клиент → DNS → CDN → LB → stateless-сервисы → кэш → БД → очередь → внешние зависимости), прогон по каждой ноде. Это обобщение таблиц статей 05 §10 и 06 §8 — здесь она полная:

Узел Что происходит при падении Что делает система Чем видно в метриках
DNS / GeoDNS Клиенты не резолвят домен; GeoDNS перестаёт отдавать мёртвый ДЦ несколько NS/A-записей, короткий TTL, DNS-провайдеров ≥2 синтетические пробы резолва по регионам
CDN Статика не отдаётся с края fallback на origin (лимитируем!), мульти-CDN при строгих требованиях offload %, ошибки края, p95 отдачи
LB / IPVS Трафик на оставшиеся LB (запас); в per-ДЦ схеме — LB каждого ДЦ свой пара с VRRP/keepalived или кластер; stateless — переливание без потерь RPS по нодам LB, 5xx до переключения
Stateless-сервис Нода выведена из ротации, нагрузка на N−1 (N+1!) readiness-проба → вывод из LB; in-flight дообрабатываются 5xx-всплеск до вывода, RPS/нода ↑
Кэш L2 (Redis) Волна промахов ~1/N ключей в БД consistent hashing + виртуальные ноды; single-flight против stampede (06 §7–8) hit rate ↓, RPS/латентность БД ↑
Primary БД Запись стоит до failover (~30 с); хвост async может потеряться детект кворумом → выборы свежей реплики → реконфигурация; fencing старого (05 §5) время failover, ошибки записи, затем lag → 0
Реплика чтения Незаметно: читаем с остальных LB чтения перераспределяет; вернувшаяся — catch-up по логу replication lag соседей ↑
Очередь (Kafka) Партиция/брокер — кластер из RF-реплик; лидер-фейловер прозрачен min.insync=2, реплики партиций в разных брокерах/ДЦ; DLQ для ядовитых сообщений (07) consumer lag, старость сообщений, under-replicated партиции
Внешняя зависимость (платёжка, S3, push) Наш путь не ломается: вызов быстро фейлится таймаут → ретрай (идемпотентный) → CB open → деградация фичи доля открытых CB, p99 зависимости, 5xx по фиче
Целый ДЦ Половина трафика/реплик; лидеры за ДЦ мертвы GeoDNS переливает; majority в живых ДЦ переизбирает лидеров доступность по регионам, счётчики failover

Универсальная последовательность любого отказа (из 05 §10 — держать как рефлекс): детект → изоляция/переключение → деградация вместо падения → метрика, по которой видно.

Как это звучит вслух (эталон темпа — 60–90 секунд на прогон, не минута на узел): «Прохожу схему по стрелке. DNS — две NS, GeoDNS с коротким TTL. LB — пара, VRRP. Сервисы — stateless, N+1, readiness выводит из ротации, пользователю незаметно. Кэш — кластер, потеря ноды — волна промахов, single-flight держит БД. Primary БД — failover за ~30 с, semi-sync держит потери около нуля, fencing от зомби. Очередь — RF партиций, min.insync 2. Внешние — таймауты, CB, деградация. Целый ДЦ — GeoDNS + majority по живым. Нагрузка ×10 — headroom, shedding по приоритетам, blacklist. На каждом шаге — метрика, где я это увижу». Это и есть ответ на «объясните отказоустойчивость» из фокуса секции.

11. Что видно в метриках (и на что алерты)

Прогонка без метрик — недоделанная: четвёртый шаг протокола обязан быть конкретным. Правила:

  • Golden signals (latency / traffic / errors / saturation) на каждом классе узлов; латентность — отдельно для ошибок и успехов (ошибки быстрые — кривая врёт). Перцентили, не средние: среднее скрывает хвост, а отказ живёт в хвосте.
  • Симптомные алерты (SLO burn rate: «доступность тратит бюджет»), не причинные («CPU 90 %» — а вдруг это норма под пиком). Метрики причин — для поиска, симптомы — для побудки.
  • Дежурные графики именно отказов: replication lag, consumer lag, under-replicated партиции, доля открытых CB, failover-счётчики (каждый — стрелка конкретной строки таблицы §10).
  • Gray failure ловится только перцентилями: «нода отвечает, но p99 × 10» — вывод из ротации по латентности (§2).

Подробно наблюдаемость, SLO/error budget, релизы без простоя — статья 12; здесь достаточно финальной фразы: «по каждой строке прогонки у меня метрика и алерт — значит, любой отказ я не только переживаю, но и вижу».

12. Прикладное: как это звучит в эталонах

URL shortener (эталон Яндекса, ../classic-designs.md §1). Топология от отказа ДЦ: 9×4 воркеров, 4 IPVS, 3 СУБД с консенсусом = 43 сервера; таблица отказов по компонентам (IPVS → запас на остальных; веб-сервер/KV → размазывается по соседям, иначе ДЦ выводится из балансировки; secondary СУБД → не влияет; primary → влияет только на control plane, GET жив; RPS-лимитер → основные функции живут). Готовый шаблон «считаю топологию от гарантии пережить регион, а не от пика нагрузки».

Лента новостей (§2). Деградации выписаны по приоритетам: чтение > постинг > счётчики; перегрузка ×10 → shedding на постинге + адаптивный fan-out (пушим только активным за 30 дней, остальные дочитывают pull'ом); отказ fan-out воркеров → «лента стареет» (graceful degradation), главная метрика — lag очереди; отказ ДЦ → клиентская геобалансировка, кэш прогревается из постов (источник правды цел).

Медиа-хостинг (§4). Асинхронный пайплайн отказоустойчив по построению: транскодер упал → retry на другом воркере + DLQ, деградация = «обрабатывается дольше», потерь нет; глобальный отказ CDN → мульти-CDN или лимиты на origin. Пример того, как асинхронность (07) сама по себе механизм отказоустойчивости: отказ воркера — это рост очереди, а не падение сервиса.

Сквозной приём: на вопрос «объясните отказоустойчивость вашей схемы» не пересказывать §1–9 по очереди, а пройти таблицу §10 по своей схеме — интервьюер получит и метод, и механизмы, и метрики одним ходом.

13. Ошибки этого раздела

Подмножество топ-10 ошибок (../methodology.md §4) и частые провалы именно на этом вопросе:

Ошибка Как выглядит Противоядие
Нет ответа на прямой вопрос «А что будет, если упадёт вот это?» — пауза, «ну… поставим ещё один» Протокол §3/§10: четыре шага, таблица прогонки, тренировка на эталонах
SPOF по невнимательности Один LB, один Redis, один брокер «для простоты» Слой 1 §3: N+1 на каждом ярусе, реплики по стойкам/ДЦ
Ретраи неидемпотентных «Повторим POST при таймауте» — двойной платёж Идемпотентность: ключ дедупа; at-least-once + дедуп (§5)
Backoff без jitter Синхронный ретрай-шторм, metastable failure Jitter, лимит попыток, retry budget (§5)
Вызов без таймаута Пул потоков съеден одной зависшей зависимостью Таймаут на каждый вызов, из p99 × 2–3; бюджет от SLO (§4)
CB без таймаутов «Поставил circuit breaker» при зависающих вызовах — порог не копится Порядок: таймаут → ретрай → CB (§6)
«Поставим кластер» без кворума Нет ответа «а если потеряем большинство?» Majority-коммит; 3 ДЦ / witness; 2 ДЦ — дилемма, проговорить (§9)
Реплики в одном ДЦ/стойке «Мульти-ДЦ», а стойка одна Rack/DC awareness — слой 1 §3
Деградация «решим на месте» В инциденте спорим, что выключать Заранее спроектированный список деградаций с приоритетами (§7)
Прогонка без метрик «Переключимся» — а чем видно? — тишина Четвёртый шаг протокола: у каждой строки таблицы метрика (§10–11)
Gray failure не ловится Health check зелёный, p99 × 10, все страдают Вывод из ротации по латентности; перцентили, не средние (§2, §11)

14. Что назвать на секции (чек-лист раздела)

Обязательные фразы и действия. Полный чек-лист — ../methodology.md §7; канон — ../knowledge-base.md §9.

На этапе 3 (рисуем схему): - N+1 на каждом ярусе и health checks — произносить при рисовании, не ждать вопроса: «каждый блок дублирован, readiness выводит из ротации». - Реплики по стойкам/ДЦ, external зависимости помечены как внешние (на них — таймауты и CB).

На этапе 4 (детали): - Идемпотентность записей в контракте API (idempotency key) — из раздела 02/07, звучит здесь же.

На этапе 6 (эксплуатация) — ядро раздела: - Сами предложить прогонку: «пройду по каждой ноде: отказ → что происходит → что делаю → метрика» — и пройти таблицей §10 по своей схеме. - На любой внешний вызов: «таймаут из p99, ретрай с backoff+jitter только идемпотентный, circuit breaker, bulkhead на пулы». - Перегрузка в разы: «headroom ≥ 2×, rate limiting с 429 + Retry-After, load shedding по приоритетам, заранее спроектированные деградации — жертвуем свежестью и полнотой, не доступностью; подозрительный трафик — в blacklist». - Много-ДЦ: «2–4 ДЦ, GeoDNS с коротким TTL, majority-коммит: 3 ДЦ переживает потерю одного; active-active против active-passive — трейдофф утилизации и сложности; RTT меж-ДЦ ограничивает синхронную запись». - Failover данных: «детект кворумом → выборы самой свежей реплики → реконфигурация; semi-sync против потери хвоста; fencing от зомби-лидера» (детали — 05 §5). - Финал: возврат к листу требований — «доступность из требований обеспечена N+1 по всем ярусам и топологией ДЦ; каждым пунктом управляет метрика» (ход из статьи 01 §5).

Сквозные формулы раздела: - «Отказ любого одного элемента не виден пользователю; видна только спроектированная деградация». - «Детект → переключение → деградация → метрика» — универсальные четыре шага любого отказа. - «Таймаут на каждый вызов; ретрай — только идемпотентный; CB на каждую внешнюю зависимость». - «99.9 % = 8.7 ч/год: девятки покупаются резервированием — N+1, реплики, ДЦ — и каждый компонент на пути их перемножает».

15. Самопроверка «умею, если»

Критерий раздела из брифа: для нарисованной схемы прохожу по каждой ноде и говорю, что будет при её падении. Проверка с закрытыми материалами: нарисовать схему shortener (или ленты) за 5 минут этапа 3, затем прогнать её по протоколу — каждый узел четырьмя шагами (§3), без пауз между шагами, дойдя до «целый ДЦ» и «нагрузка ×10». Контрольное время: полный прогон — 3–4 минуты этапа 6; таблица §10 должна воспроизводиться по строкам, а не «вспоминаться». Маркеры готовности: (1) на вопрос «что будет, если упадёт X?» рука не тянется к «поставим ещё» — а к четырём шагам; (2) «а если весь ДЦ?» мгновенно рождает GeoDNS + majority и счёт реплик по ДЦ; (3) «а если нагрузка ×10?» — headroom/shedding/blacklist тремя словами; (4) ловушки (split brain, async-хвост, retry storm, 2-ДЦ-дилемма) называются без подсказки. Протокол тренировок и рубрика 0–3 — ../methodology.md §6; сверка с эталонами — ../classic-designs.md §1–2, §4. Если прогон идёт потоком и заканчивается финальной фразой «каждая строка — с метрикой, на которую стоит алерт» — раздел готов, а с ним и заявленный фокус секции закрыт.

Связанные документы

  • 00-overview.md — обзорная статья по всем 12 разделам брифа (TASK-45.40); §08 — карта раздела
  • 01-interview-framework.md — этап 6 и финальный возврат к листу требований; правило «эксплуатацию не режем никогда»
  • 02-estimations.md — латентности для выбора таймаутов; эталон Яндекса: 20 тыс. RPS/воркер, HTTPS как дефицитный ресурс
  • 03-scaling.md — §3.4 LB не SPOF (VRRP, DNS-слой), §5 stateless как фундамент N+1, §6 типовая схема для прогонки §10
  • 05-replication-sharding.md — §5 failover лидера (детект/выборы/ловушки), §10 таблица отказов нод с данными
  • 06-caching.md — §7 stampede, §8 отказ кэша как эталон graceful degradation
  • ../brief.md — бриф: раздел 08 с критерием «умею, если» и фокусом секции
  • knowledge-base.md — §9 отказоустойчивость (изоляция/деградация, резервирование, арифметика, DDIA-слабости), §7 fencing, §10 metastable failure и golden signals, §13.5 локи и fencing
  • methodology.md — §2 карточка этапа 6 (таблица отказов, перегрузка), §4 топ-10 ошибок, §6 протокол тренировок, §7 карточка на секцию
  • classic-designs.md — §1 shortener (43 сервера, таблица отказов, экстрим-нагрузка), §2 лента (приоритеты shedding, адаптивный fan-out), §4 медиа (асинхронный пайплайн как отказоустойчивость)
  • materials/ddia-2ed/ch-09-trouble-distributed.md — источник: partial failure, ненадёжная сеть, таймауты, process pauses, fencing, кворум
  • materials/yandex-564132.md — источник: топология 4 ДЦ / 43 сервера, сценарии отказов по компонентам, план на экстрим-нагрузку
  • materials/alex-xu-vol1/ch-06-key-value-store.md — источник: CAP между ДЦ, sloppy quorum + hinted handoff
  • Соседние статьи: 07-queues-streams.md (DLQ, идемпотентные консьюмеры, лаг как метрика отказа) · 09-consistency.md (кворумы, консенсус, fencing — глубина §8–9) · 10-components-patterns.md (алгоритмы rate limiter) · 11-reference-designs.md (эталоны, где прогонка живёт целиком) · 12-operations-observability.md (golden signals, SLO/error budget, canary-выкат)