feat(monitoring): алерты на недоступность БД и исчерпание пулов + дашборд

DatabaseUnavailable (vidconf_db_up == 0, for: 30s, critical) и
DbConnectionPoolNearExhaustion/RedisConnectionPoolNearExhaustion (занято
> 80% дольше минуты, warning) — сигнал оператору, не автолечение:
healthcheck backend'а по решению оператора остаётся мягким, рестарт при
недоступной БД оборвал бы WS у всех, кто в конференциях.

Дашборд Grafana «БД и пулы соединений» — занятость пулов на графике,
следующий нагрузочный тест будут смотреть глазами.
This commit is contained in:
2026-08-09 02:40:06 +03:00
parent 0e56960714
commit 3a290c7fc2
2 changed files with 256 additions and 0 deletions

View File

@@ -61,6 +61,89 @@ groups:
# см. также алерт QueueGrowing). Проверить
# `docker compose ps llm` / `llm-gpu` и `docker compose logs llm`.
# Состояние БД и пулов соединений (сессия 33, разбор инцидента 07.08 —
# `.forcc/session-results/32-loadtest-07-08-debug.md`). `/api/health`
# отдавал 200 с `db: false` во время отказа — Prometheus его не скрейпит и
# не умеет разобрать JSON-тело, поэтому оба сигнала строятся на метриках
# `backend/api/metrics.py`, которые читаются вне основного пула.
- name: vidconf-db
rules:
# `vidconf_db_up` — отдельное соединение вне основного пула
# (`core/db.py::check_db_up`), поэтому 0 означает именно «БД не
# отвечает», а не «пул занят» (для второго см. DbConnectionPoolNearExhaustion
# ниже — раздельные метрики нарочно, см. «Главное требование» промпта
# сессии 33). `for: 30s` — два цикла скрейпа (`scrape_interval: 15s`),
# чтобы не среагировать на одиночный неудачный `connect()` (сеть/GC-пауза),
# но не тянуть с сигналом дольше: это самый критичный алерт в проекте.
- alert: DatabaseUnavailable
expr: vidconf_db_up == 0
for: 30s
labels:
severity: critical
annotations:
summary: "БД недоступна"
description: >-
vidconf_db_up == 0 дольше 30 секунд — backend не может открыть
отдельное (вне основного пула) соединение с Postgres. НЕ значит
автоматически «нужен рестарт контейнера» — по решению оператора
от 09.08 healthcheck backend'а остаётся мягким (не хардфейлится
на недоступной БД — рестарт-петля в разгар инцидента оборвала бы
WS у всех, кто в конференциях), это сигнал оператору, не
автолечение. Смотреть
`docker compose ps postgres`, `docker compose logs postgres`,
`pg_isready`.
# Раннее предупреждение — тот самый сигнал, которого не хватило
# 07.08: пул заполнялся постепенно (idle in transaction 3→8→16→26→35→
# 39→40 участников комнаты, см. session 32), а `up{job="backend"}`
# ничего не показывал, потому что backend отвечал исправно вплоть до
# самого потолка. Порог 80% — предложение из промпта сессии 33,
# `for: 1m` — фильтр от секундных всплесков (короткий пик параллельных
# запросов рассасывается за секунды, устойчивый рост участников
# комнаты — нет). На нагрузочном тесте 07.08 от пересечения 80% до
# исчерпания пула прошло по грубой оценке меньше двух минут — порог
# НЕ даёт большого запаса и это осознанный компромисс, а не идеал:
# цель — успеть до 500-х у пользователей, а не за много минут
# заранее. Перепроверить оба числа на следующем нагрузочном тесте
# (см. .forcc/session-results/33-db-health-alert.md) и подстроить,
# если реальный запас окажется у́же ожидаемого.
- alert: DbConnectionPoolNearExhaustion
expr: >-
(vidconf_db_pool_checked_out
/ (vidconf_db_pool_size + vidconf_db_pool_max_overflow)) * 100 > 80
for: 1m
labels:
severity: warning
annotations:
summary: "Основной пул соединений с БД близок к исчерпанию"
description: >-
Занято {{ $value | printf "%.0f" }}% основного пула БД дольше
минуты (порог 80%). Частая причина в этом проекте — долгоживущие
WS-подключения комнат (`api/chat.py`) держат соединение на
каждого сидящего в конференции; смотреть
`vidconf_db_pool_checked_out` и число открытых WS чата в логах,
не только текущую HTTP-нагрузку.
# Тот же класс отказа, что у пула БД (см. выше), только пул Redis —
# закрыт в 0.0.31 (`451c18e`) заданием явного max_connections, но без
# метрики занятости прошлый потолок нашёлся только руками на
# нагрузочном тесте. Бонус к задаче сессии 33 («потолки в этом
# проекте стоят лесенкой»), не отдельно запрошен промптом — пороги
# взяты по аналогии с пулом БД, не проверялись отдельным нагрузочным
# тестом именно на Redis.
- alert: RedisConnectionPoolNearExhaustion
expr: (vidconf_redis_pool_in_use / vidconf_redis_pool_max_connections) * 100 > 80
for: 1m
labels:
severity: warning
annotations:
summary: "Пул соединений Redis близок к исчерпанию"
description: >-
Занято {{ $value | printf "%.0f" }}% пула Redis дольше минуты
(порог 80%). Каждое WS-подключение комнаты держит собственную
pub/sub-подписку из этого же пула — смотреть число открытых WS
чата, не только команды Celery/кэша.
# Железо хоста (job `node` — node-exporter). Пороги подобраны под
# конкретный сервер 1gb: 8 ГБ RAM, 4 CPU, 50 ГБ диска — если сервер
# сменится, пересчитать.