Skip to Content
TroubleshootingМатрица режимов отказа

Матрица режимов отказа

В проде отказы редко изолированы: Redis, который «просто стал медленным», триггерит consumer lag, тот триггерит DB-пики на fail-open кэше, тот триггерит latency alerts. Эта страница — диагностическая матрица для случаев, когда симптомов несколько одновременно. Для изолированного отказа открывай соответствующий runbook — здесь только корреляции.

Содержание

Как пользоваться

При multi-failure инциденте типовая ошибка — кидаться чинить первое, что видно. Это приводит к двум сценариям: либо чинишь derived symptom (восстановил, через 30 секунд снова лёг), либо маскируешь root cause (включил cache-bypass, перегрузил БД, поставил второй инцидент поверх).

Правильный порядок:

  1. Сними симптомы, не пытаясь их чинить. Открой Grafana → dashboard «Service health» (high-level: все сервисы, все SLI-сигналы) и зафиксируй, что именно сломано. Скриншот, timestamp.
  2. Сопоставь с матрицей ниже. Primary symptom — то, что видно громче всего; secondary symptoms — что ты тоже наблюдаешь. Строка, где совпало больше всего, — твой первый кандидат в root cause.
  3. Иди в detail-runbook для этой корреляции и выполняй шаги по порядку. Если через 5 минут симптомы не меняются — вернись на шаг 1, возможно, первая гипотеза неверная.

Если симптомов ровно один — матрица не нужна, открывай runbook напрямую из index.

Матрица симптомов

Primary symptomSecondary symptomsLikely root causeПервый шагRunbook
Redis p99 > 500ms5xx rate ↑ на cache-heavy endpoint’ахRedis degradation (MEMORY/CPU/eviction)Перейти на fail-open dedupredis-unavailable
Kafka rebalance stormConsumer lag ↑ + heartbeat_failed_totalmax.poll.interval.ms exceeded / медленный handlerПоднять max.poll.interval.ms или вынести handler в workerkafka-rebalancing
DB replication lag > 30sStale reads, пустые list-endpoint’ы с replicaСлабая реплика или write-spike на primaryСнять нагрузку с read-replica (feature flag READ_FROM_PRIMARY)§5.2 ниже
Outbox lag > 5 minConsumer не видит новых событийForwarder stuck ИЛИ Kafka downПроверить forwarder pod + Kafka brokers health../how-to/debug-outbox-lag
GC pause > 500msp99 latency spike на всех endpoint’ахMemory pressure / heap leakGOGC=50 + heap profilememory-leak
Trace gaps в TempoTempo scrape errors в PrometheusCollector OOM / backpressureУвеличить память Tempo, чекнуть queue depth../how-to/read-traces
Auth failures ↑ на fresh tokenЛюбые 401 на только что выданный JWTJWT_SECRET rollback / KID mismatchСверить active KID в конфиге Gateway и issuer../how-to/rotate-jwt-key
Connection refused на downstreamClient-side timeout ↑, cb open stateDownstream down / network partitionCircuit breaker auto-open, дождаться half-open../patterns/retry-and-circuit-breaker
DLQ растёт на одном топикеmessages_processed_total{result="error"}Payload schema mismatch / bad deployОстановить producer, посмотреть payloadkafka-consumer-stuck
too many open files в логахPostgres dial fail ИЛИ HTTP 500FD leak (не закрытый resp.Body)Heap + goroutine profilememory-leak

Сценарии корреляции

5.1 Redis down + Kafka rebalance одновременно

Симптомы.

  • 5xx rate ↑ на endpoint’ах с idempotency-ключом.
  • Consumer lag ↑ на всех топиках.
  • Идут alerts «idempotency bypass» (dedup middleware логирует, что Redis недоступен и пропускает без проверки).

Root cause. В 9 случаях из 10 — общий сетевой инцидент: сеть на node / availability zone, DNS, service mesh. Два независимых отказа одновременно — редкость; скорее всего это один инфра-инцидент с двумя симптомами.

Порядок действий.

  1. kubectl get events --sort-by=.lastTimestamp + kubectl get nodes — ищем NodeNotReady, NetworkUnavailable, массовые pod evictions.
  2. Проверить AZ / zone availability: если Redis и Kafka brokers живут в одной AZ и та упала — это объясняет оба симптома.
  3. Если Redis up, но медленный — переключить dedup middleware на fail-open (см. handle-redis-outage и ../patterns/idempotent-consumer#fail-open-при-redis-down). Одновременно убедиться, что на uniqueness-constraint на БД включена — это second line of defense.
  4. Kafka rebalance — дождаться завершения. Нормальный rebalance < 5 минут. Если > 10 минут — поднять max.poll.interval.ms в конфигурации клиента или вынести тяжёлый handler в фоновый worker.
  5. Не рестарти consumer-pod’ы во время rebalance. Каждый рестарт триггерит новый rebalance, loop усугубляется.

Чего не делать.

  • Включать глобальный cache-bypass. Fail-open dedup — ок, полный bypass кэша перегрузит БД в ×5 и поставит третий инцидент.
  • Force-kill consumer’ов (kubectl delete pod --grace-period=0).

5.2 DB replication lag + cache stale

Симптомы.

  • Пользователь создал запись, через секунду GET /resource/:id возвращает 404 или старое значение.
  • pg_stat_replication.replay_lag на primary > 10 секунд.
  • На read-heavy endpoint’ах p99 нормальный (читает из кэша), но content stale.

Root cause. Cache-aside читает из replica, а replica отстаёт; либо cache invalidation зависит от Kafka-события, которое ещё не доехало через outbox.

Порядок действий.

  1. Проверить pg_stat_replication.replay_lag на primary. Если постоянно > 30s — replica физически не успевает.
  2. Для cache-heavy endpoint’ов на время инцидента включить feature flag READ_FROM_PRIMARY=true. Пропускная способность primary упадёт, но stale reads уйдут.
  3. Разобрать, почему replica отстаёт:
    • Long-running query на replica (pg_stat_activity WHERE state = 'active') — убить через pg_cancel_backend(pid).
    • Heavy write spike на primary — rate-limit writer’а на API-уровне.
    • Disk saturation на replica — iostat 1 на host’е реплики.
  4. Проверить cache invalidation: если инвалидация идёт через outbox-event (ReviewUpdated → drop cache key) — смотри outbox lag через ../how-to/debug-outbox-lag.

Чего не делать.

  • Увеличивать TTL кэша, чтобы «перекрыть» lag. Это маскирует симптом — stale reads станут дольше, root cause никуда не денется.

5.3 Deploy → 5xx spike → rollback → 5xx не уходит

Симптомы.

  • Деплой в T0.
  • 5xx spike в T0 + 30s.
  • Rollback в T0 + 3 мин.
  • Через 5 минут после rollback error rate не вернулся к baseline.

Root cause. Три типовых варианта:

  • Миграция в новом релизе не expand-contract: новый код писал в новую схему, старый код после rollback не умеет её читать.
  • Новый код успел положить в кэш значения в новом формате; старый код читает и падает на unmarshal.
  • Новый код опубликовал в Kafka события новой версии schema; consumer старого релиза падает на unknown field / missing field.

Порядок действий.

  1. Проверить migrate version на prod’е: применена ли schema version, с которой код post-rollback умеет работать. Если нет — либо forward-fix миграцию, либо rollback миграции (см. ../how-to/rollback-migration).
  2. FLUSHDB на dedicated cache prefix (не на весь Redis — там живут другие сервисы) для подозреваемых ключей. Конкретно для DTO-кэша — префикс <service>:dto:*.
  3. Проверить outbox: не застряли ли публикации с новой schema version, которые consumer не умеет парсить. В критичном случае — временно поднять consumer старой schema version в co-existence.
  4. Сверить Schema-Version header в Kafka messages на пиковом моменте (Kafka UI / kafka-console-consumer) против текущего consumer. Если расходятся — это твой источник.

Чего не делать.

  • Повторный deploy «новой версии с фиксом» без подтверждённого root cause. Если не понял, что сломало — повторный deploy сломает тем же способом.

5.4 Alert storm (> 20 alert’ов за минуту)

Симптомы.

  • PagerDuty / Alertmanager насылает > 20 уведомлений за минуту.
  • Alerts касаются нескольких сервисов сразу (user, review, media — все красные).
  • Сложно понять, где root cause, а где derived.

Root cause. В 95% случаев — upstream dependency: shared DB, shared Redis, Kafka, Cilium Gateway API / api-gateway, node-level network. Сервисы независимые, но зависят от одной и той же инфры.

Порядок действий.

  1. Сгруппировать alerts по service / severity в Alertmanager. Группировка [cluster, alertname] должна быть настроена в дефолте — если нет, это fix в конфиге Alertmanager’а, не в handbook’е.
  2. Игнорировать derived alerts. Найти alert, который сработал первым по времени — обычно это infra (DB down, Redis pool exhausted, Kafka broker unreachable). Дальше — производные.
  3. Объявить SEV-1 (см. принципы первого часа), вызвать infra on-call.
  4. Включить throttling на Alertmanager — не silence, а throttle: 5-минутный bucket, группа по service. Silence теряет историю и маскирует рецидив.

Чего не делать.

  • Silence всех алертов скопом. Истории потом не будет, postmortem не соберёшь.
  • Рестарить «все, что красное» без диагностики — усугубишь cascading failure.

5.5 Одновременно outbox lag + Kafka down

Симптомы.

  • outbox_rows_total{status="pending"} растёт линейно.
  • Forwarder логирует connection refused / i/o timeout на Kafka.
  • Producer-сервисы работают (БД пишется), но consumer’ы ничего не видят.

Root cause. Kafka real outage. Forwarder корректно не публикует — это штатное поведение outbox: падает Kafka → pending rows накапливаются → после восстановления forwarder догоняет.

Порядок действий.

  1. Первое: не рестарти forwarder. Outbox устойчив к backpressure by design. Рестарт не ускорит Kafka, зато потеряет in-flight prefetch.
  2. Проверить Kafka brokers health: kafka-topics.sh --list, kafka-broker-api-versions.sh, события в kubectl для Kafka pod’ов.
  3. Если Kafka восстанавливается сам (short outage, < 10 мин) — forwarder догонит. Следить за скоростью drain через rate(outbox_rows_forwarded_total[1m]).
  4. Если outbox > 1M pending rows — risk bloat: таблица выедает disk + autovacuum не справляется. Остановить пишущие handler’ы: rate-limit на API endpoint, 503 для mutating operations до catch-up.
  5. После восстановления — ожидать duplicates на consumer’ах (retry после partial publish). Dedup middleware должен быть включён — см. ../patterns/idempotent-consumer.

Чего не делать.

  • TRUNCATE outbox. Потеряешь события, которые сервис уже отдал клиенту как «принято».
  • Скипать pending messages (UPDATE status=‘skipped’). То же самое — потеря данных.

Red herrings

Симптомы, которые выглядят как root cause, но им не являются:

  • Scheduler pressure (CPU throttling). Pod throttling из-за CPU limits — частая причина p99 latency spike. Выглядит как «медленный handler» или «network issue»: сам handler простой, сеть живая, но ответ приходит на 2 секунды позже. Диагностика — container_cpu_cfs_throttled_seconds_total.
  • Clock skew между pod’ами. Redis token expiry, JWT exp-validation, outbox dedup by timestamp — всё ломается при сдвиге часов. Выглядит как auth/dedup bug, а причина — NTP dead на node. Диагностика — node_timex_offset_seconds.
  • too many open files. Обычно это file-descriptor leak в HTTP-client, где забыли defer resp.Body.Close(). Выглядит как «сеть отваливается» — dial tcp: too many open files, — но причина в собственном коде. ls /proc/<pid>/fd | wc -l растёт линейно.
  • «Kafka medленная». Чаще всего это consumer со slow handler, который не heartbeat’ит. Brokers живы, topic здоров, но группа в rebalance.
  • «БД переглужена». Часто это не throughput, а один long-running query, держащий lock. pg_stat_activity WHERE state = 'active' показывает виновника.

Принципы первого часа инцидента

  • Не чинить «первое попавшееся». Сначала корреляция симптомов, потом действие. Одно действие, дождаться эффекта, посмотреть метрики — следующее действие.
  • Делать snapshots. kubectl logs <pod> > incident-<ts>.log, heap profile через /debug/pprof/heap, скриншоты Grafana panel’ей. Retention в Loki — 7 дней, в Tempo — 3 дня; если postmortem через неделю, ты уже ничего не увидишь.
  • Коммуникация. Объявить severity (SEV-1 — влияет на всех пользователей; SEV-2 — частичная деградация; SEV-3 — мелкая неполадка). Завести incident thread в мессенджере команды. Один scribe, который пишет timeline — не все пишут одновременно.
  • Escalate, если > 15 минут без прогресса. Звать второго on-call, lead-инженера backend, SRE. Инцидент не становится проще от того, что ты дольше его держишь один.

Связанные runbook’и

Last updated on