# Правила алертинга Prometheus. Подключены через # `rule_files` в prometheus.yml. Метрики — из `backend/api/metrics.py` # (см. комментарий в prometheus.yml про контракт имён). # # Проверка: алерт на искусственно заваленном пайплайне — остановить `llm` # при активном `summarize_session` (см. `docs/deploy/monitoring.md`). groups: - name: vidconf-pipeline rules: # Растёт число сеансов, застрявших в статусе `failed` # (`conference_sessions.pipeline_status`). # `delta()` — корректная функция PromQL именно для gauge (не # `increase()`, которая рассчитана на монотонные counter'ы). - alert: PipelineFailed expr: delta(vidconf_pipeline_sessions{status="failed"}[15m]) > 0 for: 5m labels: severity: critical annotations: summary: "Растёт число упавших сеансов пайплайна пост-обработки" description: >- За последние 15 минут число сеансов в статусе failed выросло на {{ $value }}. Смотреть логи worker/worker-transcriber и таблицу conference_sessions (pipeline_status). # Глубина хотя бы одной из очередей Celery устойчиво растёт 15 минут # подряд — воркеры не успевают за потоком задач (см. # docs/deploy/scaling.md — вынос очереди в отдельную реплику). - alert: QueueGrowing expr: delta(vidconf_celery_queue_depth[15m]) > 0 and vidconf_celery_queue_depth > 10 for: 15m labels: severity: warning annotations: summary: "Очередь Celery «{{ $labels.queue }}» растёт 15 минут подряд" description: >- Текущая глубина очереди {{ $labels.queue }}: {{ $value }} задач, рост не прекращается 15 минут — см. docs/deploy/scaling.md (вынос очереди в отдельную реплику/масштабирование). # LLM-сервер (llama.cpp, job `llm` — см. комментарий в prometheus.yml # про сетевой алиас `llm`/`llm-gpu`) недоступен. Актуально ТОЛЬКО на # инсталляциях с включённым профилем `llm`/`llm-gpu` (пресеты 3–5, # `install.sh`) — целевой набор профилей по умолчанию (`media,monitoring`, # см. .env.example) их не включает, поэтому правило закомментировано # вместе с job `llm` в prometheus.yml (иначе `up{job="llm"}` не находит # ни одной серии — сам по себе закомментированный job уже не даёт этому # алерту сработать, но держать активное правило на несуществующую # метрику вводит в заблуждение). Раскомментируйте оба вместе на # инсталляциях с профилем `llm`/`llm-gpu`. # - alert: LlmDown # expr: up{job="llm"} == 0 # for: 2m # labels: # severity: critical # annotations: # summary: "LLM-сервер (llama.cpp) недоступен" # description: >- # Prometheus не может достучаться до llm:8080 дольше 2 минут — # суммаризация встанет (задачи будут копиться в очереди summarize, # см. также алерт 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 ГБ диска — если сервер # сменится, пересчитать. - name: vidconf-host rules: # MemAvailable — уже честная оценка Linux с учётом того, что легко # освобождаемый page cache/buffers не в счёт (в отличие от naive # used = total - free). 10% от 8 ГБ ≈ 800 МБ — `for: 10m`, чтобы не # дёргать на кратковременный всплеск (например, разовый всплеск # transcribe/summarize), но успеть среагировать до OOM killer. - alert: HostMemoryLow expr: (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 < 10 for: 10m labels: severity: warning annotations: summary: "Мало свободной памяти на хосте" description: >- Свободно {{ $value | printf "%.1f" }}% RAM дольше 10 минут (порог 10% ≈ 800 МБ из 8 ГБ). Смотреть, какой контейнер ест память — дашборд «Хост и контейнеры», топ по памяти (container-exporter, см. deploy/monitoring/container-exporter/). # 50 ГБ диска — 10% ≈ 5 ГБ. `for: 15m` (не мгновенно): диск не растёт # так же резко, как память, ложные срабатывания на всплеск не грозят, # но и разовая проверка на границе некритична — 15 минут отсекает шум. - alert: HostDiskLow expr: (node_filesystem_avail_bytes{mountpoint="/",fstype!="tmpfs"} / node_filesystem_size_bytes{mountpoint="/",fstype!="tmpfs"}) * 100 < 10 for: 15m labels: severity: warning annotations: summary: "Мало места на диске хоста" description: >- Свободно {{ $value | printf "%.1f" }}% диска дольше 15 минут (порог 10% ≈ 5 ГБ из 50 ГБ). Частые причины на этом проекте — записи транскрибации (`recordings`), логи docker, образы/слои после пересборки — проверить `docker system df`. # 4 CPU. Порог 90% и `for: 15m` — сознательно строже по времени, чем # у памяти/диска: кратковременные пики от пайплайна пост-обработки # (транскрибация/суммаризация) — это ожидаемая, не аварийная нагрузка, # алерт должен ловить именно устойчивую перегрузку, а не обычный всплеск. - alert: HostCpuHigh expr: 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90 for: 15m labels: severity: warning annotations: summary: "Устойчиво высокая загрузка CPU хоста" description: >- Загрузка CPU {{ $value | printf "%.1f" }}% дольше 15 минут подряд (порог 90% из 4 ядер). Смотреть топ контейнеров по CPU (дашборд «Хост и контейнеры») и латентность API — возможно, не хватает уровня AI/ресурсов под нагрузку.