Матрица режимов отказа
В проде отказы редко изолированы: Redis, который «просто стал медленным», триггерит consumer lag, тот триггерит DB-пики на fail-open кэше, тот триггерит latency alerts. Эта страница — диагностическая матрица для случаев, когда симптомов несколько одновременно. Для изолированного отказа открывай соответствующий runbook — здесь только корреляции.
Содержание
- Как пользоваться
- Матрица симптомов
- Сценарии корреляции
- Red herrings
- Принципы первого часа инцидента
- Связанные runbook’и
Как пользоваться
При multi-failure инциденте типовая ошибка — кидаться чинить первое, что видно. Это приводит к двум сценариям: либо чинишь derived symptom (восстановил, через 30 секунд снова лёг), либо маскируешь root cause (включил cache-bypass, перегрузил БД, поставил второй инцидент поверх).
Правильный порядок:
- Сними симптомы, не пытаясь их чинить. Открой Grafana → dashboard «Service health» (high-level: все сервисы, все SLI-сигналы) и зафиксируй, что именно сломано. Скриншот, timestamp.
- Сопоставь с матрицей ниже. Primary symptom — то, что видно громче всего; secondary symptoms — что ты тоже наблюдаешь. Строка, где совпало больше всего, — твой первый кандидат в root cause.
- Иди в detail-runbook для этой корреляции и выполняй шаги по порядку. Если через 5 минут симптомы не меняются — вернись на шаг 1, возможно, первая гипотеза неверная.
Если симптомов ровно один — матрица не нужна, открывай runbook
напрямую из index.
Матрица симптомов
| Primary symptom | Secondary symptoms | Likely root cause | Первый шаг | Runbook |
|---|---|---|---|---|
| Redis p99 > 500ms | 5xx rate ↑ на cache-heavy endpoint’ах | Redis degradation (MEMORY/CPU/eviction) | Перейти на fail-open dedup | redis-unavailable |
| Kafka rebalance storm | Consumer lag ↑ + heartbeat_failed_total ↑ | max.poll.interval.ms exceeded / медленный handler | Поднять max.poll.interval.ms или вынести handler в worker | kafka-rebalancing |
| DB replication lag > 30s | Stale reads, пустые list-endpoint’ы с replica | Слабая реплика или write-spike на primary | Снять нагрузку с read-replica (feature flag READ_FROM_PRIMARY) | §5.2 ниже |
| Outbox lag > 5 min | Consumer не видит новых событий | Forwarder stuck ИЛИ Kafka down | Проверить forwarder pod + Kafka brokers health | ../how-to/debug-outbox-lag |
| GC pause > 500ms | p99 latency spike на всех endpoint’ах | Memory pressure / heap leak | GOGC=50 + heap profile | memory-leak |
| Trace gaps в Tempo | Tempo scrape errors в Prometheus | Collector OOM / backpressure | Увеличить память Tempo, чекнуть queue depth | ../how-to/read-traces |
| Auth failures ↑ на fresh token | Любые 401 на только что выданный JWT | JWT_SECRET rollback / KID mismatch | Сверить active KID в конфиге Gateway и issuer | ../how-to/rotate-jwt-key |
| Connection refused на downstream | Client-side timeout ↑, cb open state | Downstream down / network partition | Circuit breaker auto-open, дождаться half-open | ../patterns/retry-and-circuit-breaker |
| DLQ растёт на одном топике | messages_processed_total{result="error"} ↑ | Payload schema mismatch / bad deploy | Остановить producer, посмотреть payload | kafka-consumer-stuck |
too many open files в логах | Postgres dial fail ИЛИ HTTP 500 | FD leak (не закрытый resp.Body) | Heap + goroutine profile | memory-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. Два независимых отказа одновременно — редкость; скорее всего это один инфра-инцидент с двумя симптомами.
Порядок действий.
kubectl get events --sort-by=.lastTimestamp+kubectl get nodes— ищемNodeNotReady,NetworkUnavailable, массовые pod evictions.- Проверить AZ / zone availability: если Redis и Kafka brokers живут в одной AZ и та упала — это объясняет оба симптома.
- Если Redis up, но медленный — переключить dedup middleware на
fail-open (см.
handle-redis-outageи../patterns/idempotent-consumer#fail-open-при-redis-down). Одновременно убедиться, что на uniqueness-constraint на БД включена — это second line of defense. - Kafka rebalance — дождаться завершения. Нормальный rebalance
< 5 минут. Если > 10 минут — поднять
max.poll.interval.msв конфигурации клиента или вынести тяжёлый handler в фоновый worker. - Не рестарти 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.
Порядок действий.
- Проверить
pg_stat_replication.replay_lagна primary. Если постоянно > 30s — replica физически не успевает. - Для cache-heavy endpoint’ов на время инцидента включить feature
flag
READ_FROM_PRIMARY=true. Пропускная способность primary упадёт, но stale reads уйдут. - Разобрать, почему 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’е реплики.
- Long-running query на replica (
- Проверить 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.
Порядок действий.
- Проверить
migrate versionна prod’е: применена ли schema version, с которой код post-rollback умеет работать. Если нет — либо forward-fix миграцию, либо rollback миграции (см.../how-to/rollback-migration). FLUSHDBна dedicated cache prefix (не на весь Redis — там живут другие сервисы) для подозреваемых ключей. Конкретно для DTO-кэша — префикс<service>:dto:*.- Проверить outbox: не застряли ли публикации с новой schema version, которые consumer не умеет парсить. В критичном случае — временно поднять consumer старой schema version в co-existence.
- Сверить
Schema-Versionheader в 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. Сервисы независимые,
но зависят от одной и той же инфры.
Порядок действий.
- Сгруппировать alerts по
service/severityв Alertmanager. Группировка[cluster, alertname]должна быть настроена в дефолте — если нет, это fix в конфиге Alertmanager’а, не в handbook’е. - Игнорировать derived alerts. Найти alert, который сработал первым по времени — обычно это infra (DB down, Redis pool exhausted, Kafka broker unreachable). Дальше — производные.
- Объявить SEV-1 (см. принципы первого часа), вызвать infra on-call.
- Включить 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 догоняет.
Порядок действий.
- Первое: не рестарти forwarder. Outbox устойчив к backpressure by design. Рестарт не ускорит Kafka, зато потеряет in-flight prefetch.
- Проверить Kafka brokers health:
kafka-topics.sh --list,kafka-broker-api-versions.sh, события вkubectlдля Kafka pod’ов. - Если Kafka восстанавливается сам (short outage, < 10 мин) —
forwarder догонит. Следить за скоростью drain через
rate(outbox_rows_forwarded_total[1m]). - Если outbox > 1M pending rows — risk bloat: таблица выедает disk + autovacuum не справляется. Остановить пишущие handler’ы: rate-limit на API endpoint, 503 для mutating operations до catch-up.
- После восстановления — ожидать 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’и
kafka-consumer-stuck— consumer не потребляет, лаг растёт.kafka-rebalancing— rebalance loop, session timeout.migration-fails—make migrate-upпадает, dirty state, advisory lock.db-slow-query— медленный SQL, индексы, bloat.memory-leak— heap / goroutine leak, pprof.redis-unavailable— Redis таймауты, pool exhausted, READONLY.test-hangs— тест не завершается, testcontainers.../how-to/debug-outbox-lag— publisher-сторона: pending rows накапливаются.../how-to/rollback-migration— откат миграции / forward-fix.../how-to/rotate-jwt-key— ротация JWT-ключа, KID mismatch.../how-to/handle-redis-outage— проектирование сервиса под Redis outage.../conventions/observability— общий debugging flow через Grafana → Tempo → Loki.