B08 · Надёжность и отказоустойчивость#
Блок отвечает на один вопрос: что делает система, когда её часть не работает.
Отправная точка: отказ — не гипотеза, а расписание. Сеть теряет пакеты, диски умирают, зависимость отвечает за 30 секунд вместо 30 миллисекунд, зона доступности пропадает. Дизайн, который «работает, если всё работает», не спроектирован.
Навигация: REL-01 · REL-02 · REL-03 · REL-04 · REL-05 · REL-06 · REL-07 · REL-08 · REL-09 · REL-10 · REL-11 · REL-12
REL-01 · Таймауты, повторы, exponential backoff, jitter#
Шаг W7
TL;DR. Каждому сетевому вызову нужен таймаут. Каждому повтору (retry) — exponential backoff (экспоненциально растущая задержка) и jitter (случайный разброс), всей цепочке повторов — общий бюджет.
Проблема. Вызов без таймаута занимает поток до бесконечности. Сотня таких вызовов исчерпывает пул, и сервис перестаёт отвечать даже на здоровые запросы — отказ зависимости превращается в отказ вашего сервиса. Повторы без задержки добавляют нагрузку ровно тогда, когда система и так не справляется. Повторы без jitter синхронизируются: все клиенты, получившие ошибку в одну секунду, повторяют одновременно, и следующий пик оказывается выше исходного. Так локальная деградация превращается в отказ всей системы (retry storm — шторм повторов, thundering herd — «стадный» наплыв).
Как работает.
- Таймаут выбирается из бюджета задержки (W1), а не «на глаз». Если вызов не уложился в свою долю бюджета, ответ клиенту уже опоздал, а ожидание только держит поток. Практический ориентир — p99.9 нормального времени ответа зависимости.
- Exponential backoff:
delay = base × 2^attempt, ограниченная потолком (cap). - Jitter — случайная добавка к задержке. Full jitter:
delay = random(0, min(cap, base × 2^attempt)). Jitter разводит клиентов во времени; без него экспонента лишь укрупняет пики. - Бюджет повторов (retry budget): ограничение вида «не более 10 % запросов — повторы» на уровне клиента. Останавливает лавину, когда ошибочны все запросы, а не единицы.
- Повторять только безопасное. Повтор допустим, если операция идемпотентна (NET-16) или ошибка заведомо произошла до применения эффекта.
- Не повторять на каждом уровне. Три уровня по три попытки дают 27 запросов. Повторы делает один слой — обычно ближайший к клиенту.
Когда применять. Для любых сетевых вызовов. Когда нет: при ошибках 4xx (кроме 429) и при исчерпании квоты повтор вернёт тот же ответ; при неидемпотентных операциях без ключа идемпотентности он опасен.
Альтернативы
| Вариант | Когда лучше | Цена |
|---|---|---|
| Повтор с backoff + jitter | Транзиентные сбои, короткие деградации | Усложняет клиента; требует бюджета |
| Hedged request (дублирующий запрос по таймауту p95) | Борьба с tail latency (хвостовой задержкой) при дешёвых идемпотентных чтениях | Дополнительная нагрузка 5–10 % |
| Fail fast (быстрый отказ) без повторов | Дешевле повторить пользователю, чем держать ресурс | Больше видимых ошибок |
| Постановка в очередь на повтор | Операция может подождать | Асинхронный контракт, сложнее UX |
Trade-offs и режимы отказа. Повторы маскируют проблему: метрика успеха выглядит нормально, пока задержка растёт. Слишком короткий таймаут превращает медленный, но рабочий вызов в отказ и порождает дубликаты (запрос выполнился, ответ не дошёл). При любом таймауте остаётся окно, где сервер операцию выполнил, а клиент считает её неудачной. Это лечится не таймаутом, а идемпотентностью.
Что мерить.
- долю повторов от общего числа запросов — её рост = ранний признак деградации;
- долю запросов, отсечённых таймаутом;
- p99 внешних вызовов отдельно от собственного времени;
- насыщенность пулов соединений и потоков.
Связано: REL-02 circuit breaker · NET-16 идемпотентность · MSG-09 backpressure · PERF-02 tail latency
Источники: AWS Builders' Library — Timeouts, retries, and backoff with jitter · AWS Architecture Blog — Exponential Backoff And Jitter · Google SRE Book, гл. 22 «Addressing Cascading Failures» · Michael Nygard, «Release It!», паттерн Timeouts
REL-02 · Circuit breaker и bulkhead#
Шаг W7
TL;DR. Circuit breaker (предохранитель) перестаёт звонить туда, где не берут трубку. Bulkhead (переборка) не даёт одной сбойной зависимости съесть ресурсы всех остальных.
Проблема. Когда зависимость деградировала, повторы к ней бесполезны и вредны: они держат ваши потоки и добивают её же. Хуже того, вызовы к одной медленной зависимости занимают общий пул, и запросы, которым эта зависимость не нужна, тоже начинают отваливаться. Отказ распространяется вширь — каскадный отказ (cascading failure).
Как работает.
Circuit breaker — машина из трёх состояний (state machine):
- Closed — вызовы проходят, считается доля ошибок и медленных ответов в скользящем окне.
- Open — порог превышен; вызовы немедленно отклоняются локально, не нагружая зависимость. Клиент получает быструю ошибку или fallback (запасной ответ).
- Half-open — по истечении паузы пропускается несколько пробных запросов. Успех → closed, неуспех → снова open.
Ключевая деталь: считать нужно не только ошибки, но и медленные ответы — деградация чаще выглядит как рост задержки, а не как рост 5xx.
Bulkhead — изоляция ресурсов: отдельный пул соединений и/или потоков на каждую зависимость, ограничение одновременных вызовов (семафор). Сбой одной зависимости исчерпывает только её пул.
Когда применять. Перед любой внешней зависимостью с непредсказуемым временем ответа: сторонний API, payment gateway (платёжный шлюз), чужой сервис. Когда нет: для локальных вызовов и для зависимостей, без которых операция не может завершиться, — размыкание там просто переносит ошибку.
Альтернативы
| Вариант | Когда лучше | Цена |
|---|---|---|
| Circuit breaker | Зависимость может деградировать надолго | Нужен fallback и настройка порогов |
| Rate limiting на вызывающей стороне | Известна пропускная способность зависимости | Не реагирует на деградацию |
| Load shedding (сброс части нагрузки) | Перегружен сам сервис | Часть пользователей получает отказ |
| Adaptive concurrency limit (адаптивный лимит параллелизма) | Пропускная способность плавает | Сложнее реализация и отладка |
Trade-offs и режимы отказа. Слишком чувствительный порог размыкает цепь на случайном всплеске и превращает частичную деградацию в полный отказ функции. Слишком мягкий — не срабатывает вовсе. Half-open, пропускающий много запросов, добивает восстанавливающуюся зависимость. Общий breaker на несколько зависимостей ломает диагностику: непонятно, кто виноват.
Что мерить.
- состояние каждого breaker как метрику — время в open;
- долю запросов, отклонённых локально;
- задержку по каждой зависимости отдельно;
- насыщенность пулов bulkhead.
Связано: REL-01 таймауты и повторы · REL-04 деградация · EDGE-06 rate limiting
Источники: Martin Fowler — CircuitBreaker · Azure Architecture — Circuit Breaker pattern · Azure Architecture — Bulkhead pattern · Michael Nygard, «Release It!», паттерны Circuit Breaker и Bulkhead
REL-03 · Load shedding и приоритизация запросов#
Шаг W7
TL;DR. При перегрузке система, которая пытается обслужить всех, не обслуживает никого. Осознанный отказ части запросов сохраняет полезную пропускную способность для остальных.
Проблема. Нагрузка превысила ёмкость. Очередь растёт, время ответа растёт вместе с ней, и в какой-то момент каждый ответ приходит позже, чем клиент готов ждать. Работа выполняется, ресурсы тратятся, полезный результат — нулевой: клиент уже отвалился по таймауту и, скорее всего, повторил запрос, добавив нагрузки. Полезная пропускная способность (goodput) падает до нуля, хотя формальная остаётся высокой: система занята производством просроченных ответов.
Чем отличается от rate limiting (ограничения частоты). Rate limiting (EDGE-06) — это контракт: он известен заранее, привязан к клиенту и действует независимо от состояния системы. Load shedding (сброс нагрузки) — реакция на текущее состояние: он включается только при перегрузке и не обещан никому. Оба нужны, и они не заменяют друг друга.
Механики.
- Мерить перегрузку правильно. Загрузка CPU запаздывает и плохо коррелирует с болью пользователя. Лучший сигнал — время ожидания в очереди: если запрос уже пролежал дольше половины бюджета, шансов уложиться нет.
- Отбрасывать просроченное, не начиная. Если дедлайн запроса истёк, результат уже никому не нужен. Проверка дедлайна перед началом работы и на границах этапов освобождает ресурсы бесплатно. Дедлайн, передаваемый по цепочке вызовов (deadline propagation, NET-10), делает это возможным во всей системе.
- LIFO вместо FIFO при перегрузке. Контринтуитивно, но эффективно: запросы в начале очереди пролежали дольше всех, и их авторы, вероятно, уже ушли. Обслуживание свежих запросов повышает долю ответов, дошедших вовремя.
- Классы приоритета. Интерактивное важнее фонового, оплата важнее рекомендаций, запись важнее аналитики. Сбрасывать снизу вверх. Иначе сброс бьёт случайно и с равной вероятностью попадает по критичному сценарию.
- Отклонять как можно раньше и дешевле. Отказ на границе (EDGE-03) стоит долей миллисекунды; отказ после похода в БД — уже потраченные ресурсы. Ответ
503сRetry-Afterдаёт клиенту инструкцию, а не повод сразу повторить запрос. - Ограничение параллелизма (concurrency limit) — держать число одновременно обрабатываемых запросов на уровне, где задержка ещё приемлема. Адаптивные лимиты подбирают его по наблюдаемому времени ответа.
Когда применять. Любой сервис, у которого нагрузка может превысить ёмкость, — то есть почти любой. Когда нет: если ёмкость с большим запасом и всплески невозможны, механизм добавит сложности без пользы; но проверять это стоит числами, а не ощущением.
Альтернативы
| Вариант | Что даёт | Чего не даёт |
|---|---|---|
| Rate limiting на клиента | Предсказуемый контракт, защита от одного шумного | Не реагирует на общую перегрузку |
| Load shedding | Сохраняет полезную пропускную способность | Часть пользователей получает отказ |
| Автоскейлинг | Увеличивает ёмкость | Минуты на реакцию; бесполезен при общей БД |
| Очередь с буфером | Сглаживает короткие всплески | При устойчивой перегрузке лишь откладывает отказ |
| Деградация (REL-04) | Ответ дешевле, но полезный | Не для всех операций применимо |
Trade-offs и режимы отказа. Отказ должен быть дешевле обслуживания, иначе сброс не спасает: если на отклонение тратится столько же, сколько на работу, перегрузка не рассасывается. Клиентские повторы превращают сброс в усиление нагрузки, поэтому нужны бюджет повторов и jitter (REL-01). Сброс без приоритетов статистически бьёт по самым частым запросам, а они не обязательно наименее важные.
Что мерить.
- goodput — ответы, доставленные до истечения клиентского дедлайна, — отдельно от общей пропускной способности;
- долю отброшенных запросов по классам;
- время ожидания в очереди;
- долю ответов, отданных после дедлайна, — это чистые потери.
Связано: REL-01 таймауты и повторы · REL-02 bulkhead · REL-04 деградация · MSG-09 backpressure
Источники: Google SRE Book, гл. 21 «Handling Overload» · AWS Builders' Library — Using load shedding to avoid overload · Fail at Scale (ACM Queue, Facebook) · Netflix — Performance Under Load
REL-04 · Graceful degradation, фича-флаги, kill switch#
Шаг W7
TL;DR. Заранее решите, какие функции отключаются первыми, и сделайте отключение управляемым — без выкатки кода.
Проблема. Под перегрузкой система обычно ведёт себя по принципу «всё или ничего»: ресурсов не хватает всем, и не работает ничего. Между тем ценность функций разная: оформление заказа критично, рекомендации на карточке товара — нет. Без заранее описанной иерархии деградация происходит случайно, а восстановление требует выкатки, то есть минут или часов.
Как работает.
- Классификация функциональности. Критичное ядро (оформление заказа, вход, оплата), важное (поиск, история), украшающее (рекомендации, персонализация, счётчики, аналитика в реальном времени).
- Заранее описанные режимы деградации для каждой функции: отдать закэшированное, отдать общее вместо персонального, отдать пустой блок, поставить в очередь на потом.
- Механизм переключения — фича-флаг или kill switch (аварийный выключатель): конфигурация, которая меняется на лету, без выкатки. Нужен аудит: кто и когда переключил.
- Приоритизация нагрузки. При исчерпании ресурсов отбрасываются запросы низкого приоритета (load shedding), а не случайные.
Когда применять. Как только у системы появились функции разной ценности. Когда нет: у операций, которые имеют смысл только целиком (перевод денег), промежуточного режима нет.
Альтернативы
| Вариант | Когда лучше | Цена |
|---|---|---|
| Фича-флаг / kill switch | Нужно мгновенное отключение | Флаги накапливаются и гниют; нужна уборка |
| Автоскейлинг | Нагрузка предсказуема и деньги есть | Минуты на подъём — медленнее инцидента |
| Очередь вместо отказа | Операция терпит отложенное выполнение | Меняется контракт и UX |
| Отдача устаревших данных (stale-while-revalidate) | Нагрузка на чтение, свежесть некритична | Пользователь видит прошлое |
Trade-offs и режимы отказа. Флаг — это ветвление, то есть непротестированный путь исполнения. Деградированный режим надо проверять регулярно, иначе в момент аварии он не заработает. Долгоживущие флаги превращаются в скрытую конфигурацию системы. Отключение «украшающей» функции может незаметно ломать критичную, если между ними есть неявная зависимость.
Что мерить.
- долю запросов, обслуженных в деградированном режиме;
- время между решением и фактическим отключением;
- количество активных флагов и их возраст.
Связано: REL-02 circuit breaker · OPS-02 стратегии выкатки · CACHE-04 патологии кэша
Источники: Google SRE Book, гл. 22 — Graceful Degradation · Martin Fowler — Feature Toggles · AWS Well-Architected — Reliability Pillar
REL-05 · Health checks: liveness vs readiness#
Шаг W9
TL;DR. Liveness (жизнеспособность) отвечает на вопрос «перезапустить ли меня», readiness (готовность) — «слать ли мне трафик». Смешивать их опасно.
Проблема. Балансировщику и оркестратору нужно знать, живой ли экземпляр. Проверка «процесс отвечает на /health» не отличает запускающийся сервис от сломанного. Проверка всех зависимостей опасна по-другому. Если общая БД ненадолго недоступна, все экземпляры разом объявляют себя нездоровыми. Оркестратор выводит их из ротации или перезапускает, и система падает целиком. А могла бы отдавать хотя бы кэш.
Как работает.
- Liveness — «процесс в рабочем состоянии; если нет, поможет перезапуск». Проверяет только сам процесс: не завис ли event loop, не исчерпана ли память безвозвратно. Не должен ходить в зависимости.
- Readiness — «готов принимать трафик». Проверяет прогрев: загружены конфиги, установлены пулы соединений, прогрет кэш. Может учитывать критичные зависимости — но осторожно.
- Startup — отдельная проверка для медленно стартующих приложений, чтобы liveness не убил процесс во время инициализации.
- Мягкая деградация вместо «неготов». Если зависимость общая для всех экземпляров, правильнее продолжать отвечать, пусть с ошибками по части запросов, чем выводить весь флот из ротации.
Когда применять. В любой среде с оркестратором или балансировщиком. Когда нет: отдельная readiness не нужна для stateless-сервиса (без состояния), стартующего мгновенно и не имеющего прогрева.
Альтернативы
| Вариант | Когда лучше | Цена |
|---|---|---|
| Активные пробы (оркестратор опрашивает) | Стандартный случай | Нагрузка от проб; риск ложных срабатываний |
| Пассивные проверки (балансировщик смотрит на реальные ответы) | Нужна реакция на настоящий трафик | Реагирует только после ошибок пользователей |
| Outlier detection (отсев выбивающихся экземпляров) в mesh | Много экземпляров, частичные деградации | Требуется service mesh |
Trade-offs и режимы отказа. Liveness, зависящий от БД, — классическая причина полного отказа при частичном сбое. Слишком агрессивные пороги перезапускают экземпляры под нагрузкой, снижая ёмкость как раз тогда, когда она нужна. Readiness, не учитывающий прогрев, отправляет трафик на холодный экземпляр — тот отвечает медленно и выглядит сбойным.
Что мерить.
- частоту перезапусков по liveness;
- время от старта до readiness;
- долю экземпляров вне ротации;
- корреляцию «перезапуски ↔ рост нагрузки».
Связано: OPS-05 Kubernetes · EDGE-01 балансировка · OPS-07 observability
Источники: Kubernetes — Configure Liveness, Readiness and Startup Probes · Google SRE Book, гл. 22 · AWS Builders' Library — Implementing health checks
REL-06 · Проектирование отказоустойчивости: обзор#
Шаг W7
TL;DR. Отказоустойчивость — не набор библиотек, а свойство, которое выводится из требований доступности и проверяется матрицей отказов.
Проблема. «Сделаем надёжно» без числа доступности не имеет содержания: 99 %, 99.9 % и 99.99 % — это три разные архитектуры и три разных бюджета. Без явного разбора «что откажет → что увидит пользователь» надёжность собирается из случайных механик, часть из которых не нужна, а часть отсутствует.
Как работает — порядок рассуждения.
- Из SLO (Service Level Objective — целевой уровень сервиса) выводится избыточность. 99.9 % (43 мин/мес) достигается резервированием внутри региона; 99.99 % (4 мин/мес) требует автоматического переключения и, как правило, нескольких зон; выше — нескольких регионов.
- Устранение единых точек отказа (SPOF, single point of failure). Каждый компонент в схеме W4 проверяется вопросом «что будет, если он исчезнет». Единственный экземпляр, единственная зона, единственный внешний провайдер — все SPOF.
- Изоляция отказов. Границы: пулы (bulkhead), зоны, шарды, ячейки (cell-based architecture). Цель — чтобы отказ затрагивал долю пользователей, а не всех.
- Механики уровня вызова. Таймауты, повторы, breaker, деградация (REL-01–REL-04).
- Восстановление. Автоматическое (failover — переключение на резерв, автоскейлинг, перезапуск) и ручное — runbook (пошаговая инструкция дежурному). Оба должны быть проверены.
- Матрица отказов как артефакт: компонент × последствие × реакция × обнаружение (шаблон — в W7).
Ключевой принцип — static stability (статическая стабильность). Система должна переживать отказ, не совершая новых действий. Заранее выделенная избыточная ёмкость надёжнее, чем расчёт на автоскейлинг в момент аварии: control plane (управляющий слой) в этот момент тоже может быть недоступен.
Альтернативы (как достигать доступности)
| Подход | Когда лучше | Цена |
|---|---|---|
| Избыточность N+1 в одной зоне | Умеренные требования | Не спасает от отказа зоны |
| Multi-AZ (несколько зон доступности) active-active | Стандарт для 99.95–99.99 % | ×2–3 инфраструктуры |
| Multi-region | Требование выше 99.99 % или регуляторика | Дорого и сложно; см. REL-08 |
| Деградация вместо избыточности | Функция терпит упрощённый режим | Хуже UX, зато дёшево |
Trade-offs и режимы отказа. Избыточность стоит денег и усложняет согласованность: две активные копии данных — это уже распределённая система со всеми вопросами блока B06. Автоматика восстановления сама становится источником отказов (ложное переключение, flapping — частые переключения туда и обратно). Надёжность, которую не проверяли, — предположение, а не свойство.
Что мерить.
- достижение SLO и остаток бюджета ошибок (error budget);
- MTTR (среднее время восстановления);
- долю инцидентов с автоматическим восстановлением;
- число обнаруженных SPOF.
Связано: REL-08 multi-region · REL-09 бэкапы и DR · OPS-09 SLO · REL-10 chaos engineering
Источники: AWS Well-Architected — Reliability Pillar · AWS Builders' Library — Static stability using Availability Zones · Michael Nygard, «Release It!» · ByteByteGo — 8 Most Important Tips for Designing Fault-Tolerant System
REL-07 · Ячеистая архитектура и радиус поражения#
Шаг W7
TL;DR. Резервирование защищает от отказа железа. От отказа состояния — плохой конфигурации, отравленной записи, исчерпания общего ресурса — защищает только разделение системы на независимые экземпляры.
Проблема. Система зарезервирована по всем правилам: несколько экземпляров, несколько зон, автоматическое переключение. И всё равно один неудачный конфиг, одна запись, роняющая обработчик, или исчерпание общего пула соединений выводят из строя всех пользователей сразу. Причина — все они обслуживаются одним логическим экземпляром системы. Избыточность здесь не помогает: она честно реплицирует проблему на все копии.
Как работает. Ячейка (cell) — самодостаточный экземпляр всей системы: свои сервисы, своя БД, свои очереди. Каждая обслуживает подмножество пользователей или tenants; тонкий маршрутизатор сопоставляет пользователя ячейке. Ячейки не общаются между собой.
Что это даёт:
- Радиус поражения ограничен долей 1/N. Инцидент в ячейке затрагивает её пользователей и никого больше.
- Выкатка по ячейкам. Это более сильная проверка, чем canary на уровне экземпляров. Обновляется весь стек целиком, включая схему данных и конфигурацию, и до распространения наблюдается на живом трафике одной ячейки.
- Известный потолок. У ячейки ёмкость, проверенная нагрузочными испытаниями. Рост обслуживается добавлением ячеек, а не увеличением одной, — то есть по многократно пройденному пути.
- Естественная изоляция tenants (DATA-18): крупный клиент может получить собственную ячейку.
Главное ограничение. Маршрутизатор ячеек — компонент, общий для всех ячеек, а значит потенциальная точка полного отказа. Поэтому он должен быть предельно простым, статически стабильным (REL-06) и меняться реже всего остального. Сопоставление «пользователь → ячейка» лучше держать почти неизменным и разрешать на границе.
Альтернативы
| Подход | Радиус поражения | Цена |
|---|---|---|
| Один экземпляр с избыточностью | Все пользователи | Минимальная |
| Shuffle sharding (перемешанное шардирование) | Малая доля пар пользователей пересекается | Требует пула изолируемых ресурсов |
| Ячейки | 1/N пользователей | N окружений, дороже эксплуатация |
| Экземпляр на клиента | Один клиент | Максимальная стоимость, только для крупных |
Shuffle sharding — промежуточный вариант: каждому клиенту выдаётся случайный набор из нескольких узлов. Вероятность полного совпадения наборов у двух клиентов мала, поэтому «отравленный» клиент задевает лишь небольшую долю остальных.
Trade-offs и режимы отказа. N ячеек — это N окружений, которые нужно обновлять, мигрировать и наблюдать: без автоматизации ячейки превращаются в N мест, где что-то настроено по-своему. Операции между ячейками (перенос пользователя, глобальный поиск, сводная аналитика) дороги, поэтому систему проектируют так, чтобы они были редкими и асинхронными. Неравномерная загрузка приводит к тому, что одни ячейки перегружены, другие простаивают. Экономии от масштаба нет — суммарная стоимость выше, чем у единого экземпляра, и это плата за ограниченный радиус.
Что мерить.
- долю пользователей, затронутых инцидентом, — это и есть прямая метрика радиуса поражения;
- загрузку ячеек и её равномерность;
- время полной выкатки по всем ячейкам;
- долю операций, пересекающих границы ячеек.
Связано: REL-06 обзор отказоустойчивости · REL-08 multi-region · DATA-18 мультитенантность · DATA-10 шардирование
Источники: AWS Well-Architected — Reducing the scope of impact with cell-based architecture · AWS Builders' Library — Workload isolation using shuffle-sharding · Google SRE Book, гл. 22
REL-08 · Multi-AZ, multi-region, failover#
Шаг W7
TL;DR. Несколько зон — почти обязательная гигиена; несколько регионов — дорогое решение, которое принимают под конкретное требование, а не «на всякий случай».
Проблема. Зона доступности (AZ, availability zone) и целый регион могут стать недоступны — это происходит регулярно. Но география стоит дорого не деньгами за железо, а физикой: между регионами десятки миллисекунд. Синхронная запись в два региона добавляет эту задержку к каждой операции. Отсюда весь набор компромиссов.
Как работает.
- Multi-AZ. Зоны в пределах региона связаны быстрой сетью (доли миллисекунды), поэтому синхронная репликация между ними приемлема. Стандартная конфигурация: экземпляры и реплики БД минимум в двух-трёх зонах, балансировщик распределяет трафик.
- Multi-region active-passive. Основной регион обслуживает трафик, резервный получает асинхронную репликацию. RPO (Recovery Point Objective) ≠ 0 — последние записи теряются; RTO (Recovery Time Objective) — минуты. Переключение обычно через DNS или глобальный балансировщик.
- Multi-region active-active. Оба региона принимают запись. Нужно разрешать конфликты, варианты:
- разделение по ключу: пользователь «принадлежит» региону;
- CRDT (conflict-free replicated data types) или LWW (last write wins);
- консенсус с географическим кворумом — тогда задержка записи включает межрегиональный round-trip.
- Правило разделения. Проще всего масштабируется схема, где данные шардированы по региону и межрегиональные операции — редкое исключение.
Когда применять. Multi-AZ — по умолчанию. Multi-region — когда этого требует SLO, регуляторика (хранение данных в юрисдикции) или задержка для географически распределённых пользователей. Когда нет: если RTO в несколько часов приемлем, дешевле бэкап и восстановление (REL-09).
Альтернативы
| Вариант | RPO / RTO | Цена |
|---|---|---|
| Бэкап + восстановление | Часы / часы | Минимальная |
| Pilot light (минимальная копия) | Минуты / десятки минут | Низкая |
| Warm standby (тёплый резерв) | Минуты / минуты | Средняя |
| Active-active | Секунды / секунды | Высокая: конфликты записи, двойная инфраструктура |
Trade-offs и режимы отказа. Active-active превращает задачу в распределённую БД с разрешением конфликтов — сложность растёт качественно, а не количественно. Переключение через DNS упирается в TTL и кэши резолверов у клиентов. Резервный регион, на который ни разу не переключались, почти наверняка не работает: конфигурация разъехалась, квоты не выданы, образы устарели. Split-brain (оба региона считают себя основными) при неверном определении отказа опаснее самого отказа.
Что мерить.
- фактические RPO/RTO по результатам учений;
- лаг репликации между регионами;
- долю трафика по регионам;
- время сходимости DNS при переключении.
Связано: DATA-09 репликация · DIST-10 разрешение конфликтов · EDGE-09 глобальная маршрутизация · REL-09 RPO/RTO
Источники: AWS — Disaster Recovery workloads: strategies · Google Cloud — DR planning guide · Kleppmann, «Designing Data-Intensive Applications», гл. 5
REL-09 · Бэкапы, DR, RPO и RTO#
Шаг W7
TL;DR. RPO — сколько данных допустимо потерять, RTO — сколько времени допустимо восстанавливаться. Бэкап, из которого ни разу не восстанавливались, — ещё не бэкап.
Проблема. Репликация защищает от отказа узла, но не от логической ошибки. Удаление таблицы, кривая миграция, повреждение данных багом или шифровальщик — всё это реплицируется мгновенно во все копии. Нужен независимый механизм отката во времени — часть DR (disaster recovery, восстановления после аварии). При этом сама возможность восстановления обычно не проверяется до момента, когда она понадобилась.
Как работает.
- RPO (Recovery Point Objective) — максимально допустимая потеря данных, выраженная во времени. RPO = 0 требует синхронной репликации; RPO = 15 мин допускает периодические снимки (snapshots).
- RTO (Recovery Time Objective) — максимально допустимое время до восстановления обслуживания. Определяет, нужен ли горячий резерв.
- Схема бэкапов: полный + инкрементальные + непрерывный архив журнала (WAL/binlog), дающий восстановление на момент времени (PITR, point-in-time recovery).
- Правило 3-2-1: три копии, на двух типах носителей, одна — вне площадки. Для защиты от шифровальщика добавляется неизменяемое хранение (immutable, WORM — write once, read many) и отдельные права доступа: учётка приложения не должна иметь права удалить бэкап.
- Регулярное восстановление. Автоматический прогон «восстановить в изолированное окружение и прогнать проверки» — единственное доказательство, что бэкап рабочий.
Когда применять. Для любого хранилища с данными, которые нельзя пересоздать. Когда нет: производные данные (кэш, поисковый индекс, витрины) бэкапить не нужно — их дешевле перестроить, но время перестроения должно входить в RTO.
Альтернативы
| Вариант | Защищает от | Не защищает от |
|---|---|---|
| Синхронная реплика | Отказа узла или зоны | Логической ошибки и удаления |
| Снимки по расписанию | Логической ошибки | Потери данных между снимками |
| PITR по журналу | Логической ошибки с точностью до секунд | Повреждения самого журнала |
| Immutable-хранилище | Шифровальщика, злоупотребления правами | Логической ошибки, попавшей в копию |
Trade-offs и режимы отказа. Частые снимки дают лучший RPO, но растут в цене и нагружают хранилище. Immutable-хранилище стоит дороже обычного, и восстановление из него медленнее. Восстановление большого объёма упирается в пропускную способность: RTO определяется не решением «восстанавливать», а скоростью чтения терабайтов. Бэкап без проверки схемы восстанавливается в формате, который текущее приложение уже не понимает. Незашифрованный бэкап — утечка, обходящая всю защиту прода.
Что мерить.
- возраст последнего успешного бэкапа;
- дату и результат последнего проверенного восстановления;
- фактическое время восстановления против заявленного RTO;
- объём и стоимость хранения.
Связано: REL-08 multi-region · DATA-17 миграции · DATA-19 retention · SEC-08 шифрование и секреты
Источники: AWS — Disaster recovery options in the cloud · PostgreSQL — Continuous Archiving and PITR · Google SRE Book, гл. 26 «Data Integrity»
REL-10 · Chaos engineering и game days#
Шаг W7
TL;DR. Проверка гипотез об отказах экспериментом — до того, как их проверит прод в неудобное время.
Проблема. Механики отказоустойчивости — код, который выполняется только в аварии, то есть код, который без специальных усилий не тестируется. Реальное поведение системы при отказе обычно отличается от ожидаемого:
- таймаут оказался не выставлен;
- breaker настроен на несуществующую метрику;
- у резервного региона нет актуальной конфигурации;
- runbook описывает интерфейс, которого больше нет.
Как работает.
- Гипотеза в терминах наблюдаемого поведения. Например: «при отключении одной из трёх реплик кэша доля ошибок останется ниже 0.1 %, p99 вырастет не более чем на 20 мс».
- Минимальный радиус поражения. Сначала тестовая среда, затем прод на малой доле трафика, в рабочее время, с готовой кнопкой остановки.
- Внесение отказа. Остановка экземпляра, задержка сети, потеря пакетов, исчерпание CPU или диска, отключение зависимости, отказ зоны.
- Сравнение с гипотезой. Расхождение — находка. Совпадение — подтверждение (тоже результат).
- Game day (учения) — то же самое, но с людьми: отрабатывается не только система, но и процесс дежурства, связь и runbook.
Когда применять. Когда есть observability (наблюдаемость) — иначе эксперимент ничего не покажет, — определены SLO и настроены базовые механики устойчивости. Когда нет: у системы заведомо нет резервирования — результат известен заранее и без эксперимента.
Альтернативы
| Вариант | Когда лучше | Цена |
|---|---|---|
| Chaos-эксперименты в проде | Зрелая система с observability | Риск влияния на пользователей |
| Game day по сценарию | Проверка людей и процессов | Требует времени команды |
| Отказоустойчивость в нагрузочном тесте | Нет готовности ломать прод | Среда отличается от прода |
| Разбор реальных инцидентов | Дёшево, доступно в любой момент | Учит только на уже случившемся |
Trade-offs и режимы отказа. Эксперимент без observability — просто авария. Эксперимент без ограничителя радиуса поражения превращается в инцидент. Регулярность важнее масштаба: разовый «день хаоса» устаревает вместе с системой.
Что мерить.
- число проведённых экспериментов и долю подтверждённых гипотез;
- найденные расхождения;
- MTTR (среднее время восстановления) в учениях против MTTR в реальных инцидентах;
- покрытие сценариев отказа из матрицы W7.
Связано: REL-06 обзор отказоустойчивости · OPS-10 постмортемы · OPS-12 тестирование
Источники: Principles of Chaos Engineering · Netflix Tech Blog — Chaos Engineering Upgraded · AWS Well-Architected — Reliability Pillar, тестирование устойчивости
REL-11 · Метастабильные отказы#
Шаг W7
TL;DR. Система не возвращается в норму после того, как причина устранена. Лечится load shedding (сбросом нагрузки), а не устранением причины — причины уже нет.
Проблема. Нагрузку сняли до уровня, который система держала вчера, а полезная работа не восстановилась. Триггер ушёл, отказ остался. Инцидент разбирают, находят триггер, чинят его — и не понимают, почему это не помогло: виноват не триггер, а контур, который отказ поддерживает.
Как работает. Открытая система живёт в одном из трёх состояний (Bronson et al., HotOS '21):
- Стабильное. Триггер вызывает лишь временную ошибку.
- Уязвимое. Система здорова, но работает так эффективно, что запаса не осталось; отсюда можно упасть.
- Метастабильное. Полезная пропускная способность (goodput) недопустимо низка и сохраняется после снятия триггера.
Держит систему в третьем состоянии поддерживающий эффект (sustaining effect), обычно — усиление работы (work amplification):
| Контур | Как усиливает |
|---|---|
| Повторы по таймауту | Каждый неуспех порождает новую попытку: поток растёт, когда должен падать |
| Потеря кэша | При hit ratio 90 % запросы к источнику растут в десять раз |
| Сборка мусора | Нехватка памяти удлиняет паузы, паузы копят очередь, очередь просит памяти |
| Медленный путь ошибки | Обработка ошибки дороже успеха: всплеск ошибок съедает ресурс быстрее нагрузки |
Когда применять. При проектировании любой системы с усиливающими контурами — то есть почти любой. Когда нет: там, где нагрузка жёстко ограничена извне и усиления не бывает.
Что делать
| Приём | Что даёт | Цена |
|---|---|---|
| Load shedding (REL-03) | Единственный надёжный выход из метастабильности | Часть запросов отброшена осознанно |
| Бюджет повторов (REL-01) | Убирает главный усиливающий контур | Меньше попыток пережить короткий сбой |
| Запас между уязвимым и стабильным режимом | Уменьшает вероятность падения | Мощность стоит денег |
| Прогрев и постепенный впуск после перезапуска | Не даёт холодному экземпляру получить полный поток | Медленнее возвращается в строй |
Trade-offs и режимы отказа. Работа на грани эффективности и есть уязвимое состояние: запас мощности покупает не производительность, а возможность вернуться. Перезапуск помогает не потому, что чинит, а потому, что сбрасывает нагрузку, — и это подсказка, а не решение.
Что мерить.
- goodput отдельно от общей пропускной способности;
- долю повторов в потоке;
- hit ratio кэша и время его прогрева;
- время возврата к норме после снятия нагрузки — главный показатель, и его почти никто не мерит.
Связано: REL-01 таймауты и повторы · REL-03 load shedding · REL-10 chaos engineering · PERF-07 очереди и насыщение
Источники: Bronson, Aghayev, Charapko, Zhu — Metastable Failures in Distributed Systems, HotOS '21 · Huang et al. — Metastable Failures in the Wild, OSDI '22 · Google SRE Book, гл. 22
REL-12 · Серый отказ и differential observability#
Шаг W7
TL;DR. Серый отказ (gray failure): зависимость отвечает на health check и не обслуживает потребителя. Автоматика не переключает, потому что не считает, что что-то сломалось.
Проблема. Дежурный видит зелёные панели, пользователи — ошибки. Переключение не срабатывает, алерт не приходит, а разбирательство начинается с вопроса «у кого сломалось» и продолжается перекладыванием между командами.
Как работает. Отказ не бинарен. Определяющее свойство — differential observability (дифференциальная наблюдаемость): «хотя бы одно приложение наблюдает систему нездоровой, тогда как наблюдатель видит её здоровой» (Huang et al., HotOS '17). Разрыв возникает потому, что проверяющий и потребитель смотрят на разное:
| Проверка | Что она видит | Чего не видит |
|---|---|---|
| Процесс отвечает на локальный вызов | Что процесс жив | Что диск отвечает за секунды |
| Утилизация в норме | Что ресурс не исчерпан | Что теряется каждый сотый пакет |
| Ошибок в логах нет | Что путь ошибки не исполнялся | Что запросы уходят в таймаут у клиента |
Парадокс избыточности. Чем больше параллельных путей, тем выше вероятность, что хотя бы один деградирует. И тем хуже сценариям с большим fan-out (разветвлением): запрос, идущий по всем путям сразу, ждёт худший из них.
Когда применять. При проектировании health checks и переключений — всюду, где автоматика принимает решение по сигналу о чужом состоянии. Когда нет: там, где потребитель и проверяющий — одно и то же.
Что делать
| Приём | Что даёт |
|---|---|
| Мерить здоровье глазами потребителя — по успешности его запросов | Сигнал совпадает с тем, что переживает пользователь |
| Сводить наблюдения нескольких независимых наблюдателей | Один наблюдатель ошибается, трое расходятся заметно |
| Не исполнять проверку в том же процессе, что и проверяемое | Проверка перестаёт быть заинтересованной стороной |
| Сравнивать долю успеха по данным сервиса и по данным клиента | Расхождение и есть серый отказ, измеренный числом |
Trade-offs и режимы отказа. Проверка глазами потребителя дороже, шумнее и требует данных клиента. Слишком чувствительная переключает из-за единичного сетевого сбоя, слишком грубая возвращает исходную проблему. Нужен гистерезис (EDGE-09).
Что мерить.
- расхождение между долей успешных запросов по данным сервиса и по данным потребителя — главный показатель;
- долю запросов, завершившихся таймаутом у клиента при успехе на сервере;
- время от появления расхождения до срабатывания автоматики.
Связано: REL-05 health checks · REL-11 метастабильные отказы · OPS-07 observability · ARCH-02 границы
Источники: Huang, Guo, Zhou, Lorch, Dang, Chintalapati, Yao — Gray Failure: The Achilles' Heel of Cloud-Scale Systems, HotOS '17 · Google SRE Book — Monitoring