---
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`](../methodology.md) §1). Глубина — [`../knowledge-base.md`](../knowledge-base.md) §9; полные разборы — [`../classic-designs.md`](../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`](../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`](../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`](../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`](../methodology.md) §7; канон — [`../knowledge-base.md`](../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`](../methodology.md) §6; сверка с эталонами — [`../classic-designs.md`](../classic-designs.md) §1–2, §4. Если прогон идёт потоком и заканчивается финальной фразой «каждая строка — с метрикой, на которую стоит алерт» — раздел готов, а с ним и заявленный фокус секции закрыт.

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

- [`00-overview.md`](00-overview.md) — обзорная статья по всем 12 разделам брифа (TASK-45.40); §08 — карта раздела
- [`01-interview-framework.md`](01-interview-framework.md) — этап 6 и финальный возврат к листу требований; правило «эксплуатацию не режем никогда»
- [`02-estimations.md`](02-estimations.md) — латентности для выбора таймаутов; эталон Яндекса: 20 тыс. RPS/воркер, HTTPS как дефицитный ресурс
- [`03-scaling.md`](03-scaling.md) — §3.4 LB не SPOF (VRRP, DNS-слой), §5 stateless как фундамент N+1, §6 типовая схема для прогонки §10
- [`05-replication-sharding.md`](05-replication-sharding.md) — §5 failover лидера (детект/выборы/ловушки), §10 таблица отказов нод с данными
- [`06-caching.md`](06-caching.md) — §7 stampede, §8 отказ кэша как эталон graceful degradation
- [`../brief.md`](../brief.md) — бриф: раздел 08 с критерием «умею, если» и фокусом секции
- [`knowledge-base.md`](../knowledge-base.md) — §9 отказоустойчивость (изоляция/деградация, резервирование, арифметика, DDIA-слабости), §7 fencing, §10 metastable failure и golden signals, §13.5 локи и fencing
- [`methodology.md`](../methodology.md) — §2 карточка этапа 6 (таблица отказов, перегрузка), §4 топ-10 ошибок, §6 протокол тренировок, §7 карточка на секцию
- [`classic-designs.md`](../classic-designs.md) — §1 shortener (43 сервера, таблица отказов, экстрим-нагрузка), §2 лента (приоритеты shedding, адаптивный fan-out), §4 медиа (асинхронный пайплайн как отказоустойчивость)
- [`materials/ddia-2ed/ch-09-trouble-distributed.md`](../materials/ddia-2ed/ch-09-trouble-distributed.md) — источник: partial failure, ненадёжная сеть, таймауты, process pauses, fencing, кворум
- [`materials/yandex-564132.md`](../materials/yandex-564132.md) — источник: топология 4 ДЦ / 43 сервера, сценарии отказов по компонентам, план на экстрим-нагрузку
- [`materials/alex-xu-vol1/ch-06-key-value-store.md`](../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-выкат)
