Ctrl K
Урок

Отказоустойчивость и наблюдаемость

Всё ломается — и это нормально

Представь кафе. Повар заболел, кофемашина сломалась, курьер застрял в пробке. Хорошее кафе от этого не закрывается: кофе варят в турке, у второго повара есть смена, а гостю честно говорят «десерт будет через 20 минут». В плохом кафе один сбой останавливает всё.

С системами то же самое. Сеть теряет пакеты, диски умирают, соседний сервис зависает. На собесе от тебя не ждут системы, которая никогда не падает. Ждут, что ты заранее знаешь, как она будет падать, и не дашь одной поломке утащить за собой всё остальное.

Схема: две зоны доступности

HTTPSзаписьзаписьрепликациячтениеКлиентклиентБалансировщик (health checks)LBAPI — зона AсервисAPI — зона BсервисRedis — зона AкэшRedis — зона BкэшPostgreSQL primary (A)БДPostgreSQL replica (B)БД
Зона A упала — балансировщик уводит трафик в B, реплика повышается до primary

Зона доступности — это отдельный дата-центр со своим питанием и сетью. Балансировщик постоянно опрашивает сервисы (/health) и перестаёт слать трафик туда, где не отвечают. База реплицируется во вторую зону, и при аварии реплика становится главной (failover).

Почему это работает, видно из расчёта (в идеале, при независимых отказах):

Конфигурация Доступность Простой в месяц
Одна зона, 99.9% 99.9% ~43 мин
Две зоны по 99.9% 1 − 0.001² = 99.9999% ~3 сек

На собесе добавь честную оговорку: отказы зон не полностью независимы (общий деплой, общий DNS), так что реальная цифра ниже. За такую оговорку тебе добавят балл.

Четыре приёма защиты

1. Таймауты. Без таймаута запрос к зависшему сервису висит вечно и держит поток. Через минуту потоки кончаются, и твой сервис тоже «умер». Правило простое: у каждого сетевого вызова есть таймаут, и он короче таймаута того, кто вызывает тебя.

2. Ретраи с экспонентой и джиттером. Временные сбои лечатся повтором, но повторять надо аккуратно:

  • паузы растут: 100 мс, 200, 400… (экспоненциальный backoff);
  • к паузе добавляется случайность (jitter), иначе тысяча клиентов ударит повторно в одну и ту же миллисекунду;
  • повторяем только идемпотентные операции или операции с ключом идемпотентности;
  • лимит попыток обязателен.

Опасность здесь в шторме ретраев. Если три слоя сервисов делают по 3 попытки, нижний слой получает 3 × 3 × 3 = 27× нагрузки ровно тогда, когда ему и так плохо. Решение: ретраить в одном слое и вводить retry budget (например, не больше 10% от трафика).

client:
  timeout: 300ms
  retries: 3
  backoff: exponential   # 100ms, 200ms, 400ms
  jitter: full           # пауза = random(0, backoff)
  circuit_breaker:
    error_threshold: 50%
    open_duration: 30s

3. Circuit breaker (автомат-предохранитель). Работает как пробки в квартире. Если зависимость ошибается слишком часто, «автомат» размыкается, и запросы к ней сразу получают отказ, не дожидаясь таймаута. Через паузу автомат пропускает пару пробных запросов (half-open). Если те прошли успешно, цепь снова замыкается. Так больной сервис получает время прийти в себя.

4. Деградация функциональности. Лучше отдать часть, чем ничего. Если упал сервис рекомендаций, показываем «популярное» из кэша. Если упали отзывы, карточка товара всё равно открывается. Заранее раздели фичи на критичные (оплата, вход) и приятные (рекомендации, счётчики лайков). Тяжёлые несрочные операции уводи в очередь.

Приём Защищает от Цена
Таймаут зависаний ложные отказы, если выставлен слишком коротким
Ретрай + jitter временных сбоев рост нагрузки, нужна идемпотентность
Circuit breaker каскадных падений часть запросов отклоняется сразу
Деградация полного отказа пользователь видит урезанную версию
Вторая зона падения ДЦ ×2 стоимость, задержка репликации

Наблюдаемость: как понять, что сломалось

Защита бесполезна, если ты не видишь проблему. Наблюдаемость стоит на трёх опорах:

  • Метрики — числа во времени, дешёвые и быстрые. Отвечают на вопрос «что-то не так?».
  • Логи — подробные события. Отвечают на вопрос «что именно случилось?». Пиши их структурированно (JSON) и с request_id.
  • Трассировка — путь одного запроса через все сервисы с временем на каждом шаге. Отвечает на вопрос «где тормозит?». Сквозной trace_id передаётся в заголовках.

Какие метрики снимать, подскажут две памятки:

Метод Для чего Что смотреть
RED сервисы (запросы) Rate — запросов/с, Errors — доля ошибок, Duration — латентность (p50/p99)
USE ресурсы (CPU, диск, пул соединений) Utilization — загрузка, Saturation — очередь ожидания, Errors — ошибки

Смотри на p99, а не на среднее. Среднее 50 мс может прятать 1% пользователей, которые ждут по 3 секунды.

SLI и SLO простыми словами

  • SLI — что меряем. Например, «доля запросов, ответивших успешно быстрее 300 мс».
  • SLO — какую цель ставим. Например, «99.9% за 30 дней».
  • Error budget — сколько можно «сломаться»: 100% − SLO.

Пример расчёта при 10 млн запросов в месяц и SLO 99.9%:

  • бюджет ошибок 0.1% → 10 000 плохих запросов;
  • по времени это ~43 минуты простоя.

Пока бюджет есть, команда смело релизит. Когда бюджет съеден, релизы замораживаются и команда занимается надёжностью. Так SLO превращает спор «скорость против стабильности» в арифметику.

Как это звучит на собесе

«Разнесу сервис по двум зонам за балансировщиком с health checks, база — primary плюс реплика в другой зоне с автоматическим failover. У всех вызовов таймауты, ретраи с экспонентой и джиттером только для идемпотентных операций, на внешних зависимостях — circuit breaker. Если упадут рекомендации, покажу кэшированный топ. Мониторинг — RED-метрики по сервисам, USE по ресурсам, трассировка через trace_id. SLO — 99.9% успешных запросов быстрее 300 мс, алерты по скорости сжигания error budget».

Это 30 секунд, и в них уже есть все ключевые слова.

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

  • Ретраи без джиттера и лимита. Так ты сам организуешь DDoS на свою систему.
  • Ретрай неидемпотентной операции. Например, повторное списание денег.
  • Таймауты по умолчанию. Часто это 30–60 с или вообще бесконечность.
  • «Добавим реплику, и всё». Не упомянуты лаг репликации и split-brain при failover.
  • Обещание SLO 100%. Такая цель недостижима и запрещает любые изменения.
  • Алерты на CPU вместо симптомов пользователя. Будить человека стоит из-за ошибок и латентности, а не из-за загрузки процессора.

Ты только что собрал набор, которым пользуются реальные продакшн-команды: изоляция поломок, умные повторы, запасная зона и глаза на всё это. Если назовёшь эти приёмы и их цену, интервьюер увидит человека, который думает о системе в 3 часа ночи, а не только на доске.