Runbook: Event store / outbox projection lag
Severity (default): P1 — пишущий путь работает, но проекции (ES/CH/in-PG read models) отстают. Owner: core team / on-call. Связанные алерты:
outbox_unpublished_count > 10000за 5 мин.outbox_lag_seconds p95 > 5за 5 мин.outbox_publish_attempts_total{outcome="failed", attempts>=5} > 0.- ES alias не свежее последнего event_store записи.
Симптом
- Search / админка показывают устаревшие данные.
event_storeрастёт, но в ES/CH ничего не приходит.outboxнакапливает unpublished строки.
Диагностика
-- 1. Размер unpublished outbox
SELECT count(*) FROM outbox WHERE published_at IS NULL;
-- 2. Самая старая unpublished
SELECT id, topic, enqueued_at, attempts, now() - enqueued_at AS age
FROM outbox WHERE published_at IS NULL ORDER BY enqueued_at LIMIT 10;
-- 3. Топики с проблемой
SELECT topic, count(*), min(enqueued_at), max(attempts)
FROM outbox WHERE published_at IS NULL GROUP BY topic ORDER BY count(*) DESC;
-- 4. Застрявшие (attempts >= 5)
SELECT id, topic, attempts, enqueued_at, headers->>'last_error' AS last_error
FROM outbox WHERE attempts >= 5 AND published_at IS NULL LIMIT 20;# 5. Лидер outbox relay живой?
kubectl -n tracium exec deploy/redis -- \
redis-cli GET "tracium:outbox-relay:leader"
# 6. Логи outbox-relay
kubectl -n tracium logs deploy/outbox-relay --since=15m | grep -E "ERROR|WARN"Возможные причины:
- A: Outbox relay не работает (нет лидера / упал).
- B: Kafka недоступен / запись отвергается.
- C: Один топик со сломанной схемой → все его события копят attempts.
- D: ES indexer (downstream consumer) умер → каскадный lag.
Смягчение
A: Перезапуск relay
kubectl -n tracium rollout restart deploy/outbox-relay
# Проверить, что лидерство восстановилось
sleep 15 && kubectl -n tracium exec deploy/redis -- redis-cli GET "tracium:outbox-relay:leader"B: Kafka недоступна
См. отдельный runbook kafka-cluster-down.md (TODO). Outbox продолжит копить — данные не теряются. После восстановления Kafka — relay сам догонит.
C: Застрявший топик
-- Найти broken записи
SELECT id, topic, payload->>'event_type' AS et, attempts
FROM outbox WHERE attempts >= 10 AND published_at IS NULL;Опции:
- Если payload broken (после code rollback вышла невалидная схема) —
UPDATE outbox SET payload = ... WHERE id = ...(с явным аудитом, только через runbook). - Если broker отвергает (например, сообщение слишком большое) — увеличить
max.message.bytesили сжать. - Окончательный skip —
UPDATE outbox SET published_at = now(), headers = headers || '{"manual_skip":true,"by":"<operator>","reason":"<>"}'::jsonb WHERE id = .... Только если consumer не пострадает.
D: Downstream consumer умер
См. kafka-consumer-lag.md.
Устранение root cause
- A: HA outbox-relay (несколько реплик; лидерство стабильнее).
- B: Kafka SLO + chaos test.
- C: валидация схем в outbox writer (проверка до публикации).
- D: лучшие алерты на здоровье consumer’ов (RED + auto-restart).
Эскалация
- 15 мин без mitigation →
#oncall-data. - При риске потери данных (
outboxрядом с лимитом размера таблицы) →#oncall-prod+ DBA.
Связано
- ADR-0020 (Outbox pattern).
20-architecture/event-sourcing.md§“Outbox publication SLA”.kafka-consumer-lag.md