Метрики: экспортёр Celery наконец поднимается, тревоги про воркеров и память перестают врать #3547

Merged
bot-backend merged 1 commit from fix/3493-celery-exporter-memory-alert into main 2026-09-17 07:05:42 +00:00
Collaborator

Closes #3493

Что было

Две тревоги шумели в канале метрик и обе врали текстом, а не условием.

NoActiveCeleryWorkers горела с 12.09 11:31 UTC без перерыва, повтор каждые ~3 ч — около 40 одинаковых сообщений. Воркер при этом жив: gendesign-worker-1 Up 29 hours (healthy), 15 задач за 10 минут. Метрик celery_* в Prometheus не было вовсе, правило горело по ветке absent().

ContainerNearMemoryLimit · tradein-postgres приходила каждую ночь (91–99.8 %) с текстом «дальше OOM-kill».

Почему

Экспортёр Celery не поднимался ни разу

Лог джобы Deploy Metrics / agent-apps с диска Forgejo, одинаковый 12.09 и 16.09:

err:  Image danihodovic/celery-exporter:0.13.0 Error failed to resolve reference
      "docker.io/danihodovic/celery-exporter:0.13.0": not found
Process exited with status 1
Job 'agent-apps' failed

Тега 0.13.0 не существует (последний — 0.12.2, 15.07.2025). Под set -e упавший pull обрывал всю джобу — с 12.09 на Poincare не доезжало ничего из агента метрик: ни этот экспортёр, ни redis-exporter, ни новый конфиг Alloy со скрейпом очереди.

За первым дефектом стоял второй: флага --queue в 0.12.2 нет, опция называется --queues (src/cli.py). С одним исправленным тегом экспортёр упал бы при старте.

Проверено на боевом брокере до мержа — временный контейнер 0.12.2 --queues=celery на сети gendesign_shared (удалён):

celery_worker_up{hostname="687f9212bebb"} 1.0
celery_queue_length{queue_name="celery"} 0.0
celery_active_worker_count{queue_name="celery"} 1.0

Адрес брокера не тронут: алиас gendesign-redis на gendesign_shared существует (aliases=[gendesign-redis-1 redis gendesign-redis]) и резолвится.

Прежнее правило пропустило бы настоящую смерть воркера

count(celery_worker_up == 1) == 0 — если все серии в 0, == 1 даёт пустой вектор, count() от пустого вектора тоже пуст, и == 0 сравнивать не с чем. Ветка «воркер мёртв» не сработала бы никогда; горела только absent() — под текстом про мёртвого воркера.

Память: working set включает кэш страниц

Живой контейнер 17.09 06:10 UTC: лимит 3 ГБ, anon 38 МБ, shmem 841 МБ (shared_buffers), inactive_file 2.1 ГБ. Анонимная память за 7 суток 11–170 МБ без тренда, oom_kill = 0 за 21 сутки, своп хоста свободен. Метрика working_set = usage − inactive_file оставляет активный кэш, ночные сканы поднимают его к потолку.

Что изменено

  1. docker-compose.metrics-agent.yml — тег 0.12.2, флаг --queues=celery, комментарий с тем, чем сверено.
  2. rules/infra.yml:
    • NoActiveCeleryWorkers — только когда экспортёр жив (up{job="celery"} == 1), а живых воркеров нет (серии в 0 или вычищены).
    • QueueExporterDown (warning, новое) — экспортёр redis/celery не отдаёт метрики; текст прямо говорит «авария наблюдаемости, не продукта».
    • ContainerNearMemoryLimit — второе условие: анонимная память > 50 % лимита. Левая часть и $value прежние (доля рабочего набора).
  3. ops/metrics/prometheus/tests/infra_test.yml — юнит-тесты правил, 6 случаев, каждый уже случался на проде. Исполняются деплоем метрик перед reload одноразовым контейнером того же образа; упавший тест оставляет Prometheus на прежних правилах.

Калибровка правила памяти — на истории, этим самым выражением

14 суток, шаг 5 мин, эпизоды ≥ 15 мин:

контейнер было станет что это
tradein-postgres 5 0 все 01:09–03:44 UTC; rss ≤ 11.4 %
tradein-browser 2 2 06.09, 12.09; rss до 95 % — настоящие
остальные 0 0

Метки working_set и rss совпадают у 913 из 915 серий — and без on() работает, это подтверждено ненулевым результатом на браузере.

Проверки

результат
promtool check rules SUCCESS: 24 rules found, rc=0
promtool test rules на новых правилах rc=0
promtool test rules на старых правилах из main (фальсификация) rc=1: 2 × ContainerNearMemoryLimit, 3 × NoActiveCeleryWorkers, 1 × QueueExporterDown
pytest backend/tests/ops 131 passed, 4 failed — все 4 test_2203_* (BSD mktemp --suffix на macOS, на CI под Linux зелёные)

Попутно: гейт test_3467_…::test_failed_validation_skips_reload_and_fails_the_step ищет конец else подстрокой fi — первая версия моего сообщения об ошибке содержала слово config и роняла его. Текст сообщения переписан кириллицей; хрупкость гейта не трогал.

Приёмка после мержа (2026-09-17)

  • docker ps на Poincare: gendesign-celery-exporter и gendesign-redis-exporterUp.
  • Prometheus: celery_worker_up == 1, up{job="celery"} == 1, up{job="redis"} == 1.
  • NoActiveCeleryWorkers и QueueExporterDown — не firing; в канал приходит RESOLVED.
  • GET /api/v1/rules отдаёт новое выражение ContainerNearMemoryLimit; ночь на 18.09 проходит без тревоги по tradein-postgres.

Не сделано, отмечаю

Экспортёр увидел три очереди (celery, geo, scrape_kn), а CeleryQueueGrowing смотрит только celery — комментарий в compose «все таски в один queue» неверен. Отдельной задачей.

🤖 Generated with Claude Code

Closes #3493 ## Что было Две тревоги шумели в канале метрик и обе врали **текстом**, а не условием. **NoActiveCeleryWorkers** горела с 12.09 11:31 UTC без перерыва, повтор каждые ~3 ч — около 40 одинаковых сообщений. Воркер при этом жив: `gendesign-worker-1 Up 29 hours (healthy)`, 15 задач за 10 минут. Метрик `celery_*` в Prometheus не было вовсе, правило горело по ветке `absent()`. **ContainerNearMemoryLimit · tradein-postgres** приходила каждую ночь (91–99.8 %) с текстом «дальше OOM-kill». ## Почему ### Экспортёр Celery не поднимался ни разу Лог джобы `Deploy Metrics / agent-apps` с диска Forgejo, одинаковый 12.09 и 16.09: ``` err: Image danihodovic/celery-exporter:0.13.0 Error failed to resolve reference "docker.io/danihodovic/celery-exporter:0.13.0": not found Process exited with status 1 Job 'agent-apps' failed ``` Тега `0.13.0` не существует (последний — `0.12.2`, 15.07.2025). Под `set -e` упавший `pull` обрывал **всю** джобу — с 12.09 на Poincare не доезжало ничего из агента метрик: ни этот экспортёр, ни `redis-exporter`, ни новый конфиг Alloy со скрейпом очереди. За первым дефектом стоял второй: флага `--queue` в 0.12.2 нет, опция называется `--queues` (`src/cli.py`). С одним исправленным тегом экспортёр упал бы при старте. **Проверено на боевом брокере до мержа** — временный контейнер `0.12.2 --queues=celery` на сети `gendesign_shared` (удалён): ``` celery_worker_up{hostname="687f9212bebb"} 1.0 celery_queue_length{queue_name="celery"} 0.0 celery_active_worker_count{queue_name="celery"} 1.0 ``` Адрес брокера **не тронут**: алиас `gendesign-redis` на `gendesign_shared` существует (`aliases=[gendesign-redis-1 redis gendesign-redis]`) и резолвится. ### Прежнее правило пропустило бы настоящую смерть воркера `count(celery_worker_up == 1) == 0` — если все серии в 0, `== 1` даёт пустой вектор, `count()` от пустого вектора тоже пуст, и `== 0` сравнивать не с чем. Ветка «воркер мёртв» не сработала бы никогда; горела только `absent()` — под текстом про мёртвого воркера. ### Память: working set включает кэш страниц Живой контейнер 17.09 06:10 UTC: лимит 3 ГБ, `anon 38 МБ`, `shmem 841 МБ` (shared_buffers), `inactive_file 2.1 ГБ`. Анонимная память за 7 суток 11–170 МБ без тренда, `oom_kill = 0` за 21 сутки, своп хоста свободен. Метрика `working_set` = usage − inactive_file оставляет **активный** кэш, ночные сканы поднимают его к потолку. ## Что изменено 1. `docker-compose.metrics-agent.yml` — тег `0.12.2`, флаг `--queues=celery`, комментарий с тем, чем сверено. 2. `rules/infra.yml`: - **NoActiveCeleryWorkers** — только когда экспортёр жив (`up{job="celery"} == 1`), а живых воркеров нет (серии в 0 или вычищены). - **QueueExporterDown** (warning, новое) — экспортёр redis/celery не отдаёт метрики; текст прямо говорит «авария наблюдаемости, не продукта». - **ContainerNearMemoryLimit** — второе условие: анонимная память > 50 % лимита. Левая часть и `$value` прежние (доля рабочего набора). 3. `ops/metrics/prometheus/tests/infra_test.yml` — юнит-тесты правил, 6 случаев, каждый уже случался на проде. Исполняются деплоем метрик **перед** reload одноразовым контейнером того же образа; упавший тест оставляет Prometheus на прежних правилах. ## Калибровка правила памяти — на истории, этим самым выражением 14 суток, шаг 5 мин, эпизоды ≥ 15 мин: | контейнер | было | станет | что это | |---|---|---|---| | tradein-postgres | 5 | **0** | все 01:09–03:44 UTC; rss ≤ 11.4 % | | tradein-browser | 2 | **2** | 06.09, 12.09; rss до 95 % — настоящие | | остальные | 0 | 0 | | Метки `working_set` и `rss` совпадают у 913 из 915 серий — `and` без `on()` работает, это подтверждено ненулевым результатом на браузере. ## Проверки | | результат | |---|---| | `promtool check rules` | `SUCCESS: 24 rules found`, rc=0 | | `promtool test rules` на **новых** правилах | rc=0 | | `promtool test rules` на **старых** правилах из main (фальсификация) | **rc=1**: 2 × ContainerNearMemoryLimit, 3 × NoActiveCeleryWorkers, 1 × QueueExporterDown | | `pytest backend/tests/ops` | 131 passed, 4 failed — все 4 `test_2203_*` (BSD `mktemp --suffix` на macOS, на CI под Linux зелёные) | Попутно: гейт `test_3467_…::test_failed_validation_skips_reload_and_fails_the_step` ищет конец `else` подстрокой `fi` — первая версия моего сообщения об ошибке содержала слово `config` и роняла его. Текст сообщения переписан кириллицей; хрупкость гейта не трогал. ## Приёмка после мержа (2026-09-17) - `docker ps` на Poincare: `gendesign-celery-exporter` и `gendesign-redis-exporter` — `Up`. - Prometheus: `celery_worker_up == 1`, `up{job="celery"} == 1`, `up{job="redis"} == 1`. - `NoActiveCeleryWorkers` и `QueueExporterDown` — не firing; в канал приходит RESOLVED. - `GET /api/v1/rules` отдаёт новое выражение ContainerNearMemoryLimit; ночь на 18.09 проходит без тревоги по tradein-postgres. ## Не сделано, отмечаю Экспортёр увидел **три** очереди (`celery`, `geo`, `scrape_kn`), а `CeleryQueueGrowing` смотрит только `celery` — комментарий в compose «все таски в один queue» неверен. Отдельной задачей. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bot-backend added 1 commit 2026-09-17 06:22:52 +00:00
fix(metrics): экспортёр Celery поднимается, тревоги про воркеров и память говорят правду (#3493)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 13s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m37s
CI / backend-tests (pull_request) Successful in 7m8s
251ca98c5c
Две тревоги шумели в канале каждую ночь и каждые три часа, обе врали текстом.

NoActiveCeleryWorkers горела с 12.09 без перерыва при живом воркере
(`Up 29 hours`, 15 задач за 10 минут). Экспортёр не поднимался ни разу:
тега danihodovic/celery-exporter:0.13.0 на Docker Hub не существует
(`pull` → `not found`, логи джобы agent-apps 12.09 и 16.09), а под
`set -e` упавший pull обрывал ВСЮ джобу — с 12.09 на Poincare не доезжало
ничего из агента метрик: ни этот экспортёр, ни redis-exporter, ни новый
конфиг Alloy. Второй дефект за первым: флага `--queue` в 0.12.2 нет,
опция зовётся `--queues` — с одним исправленным тегом экспортёр упал бы
при старте. Тег и флаг сверены с исходником (src/cli.py, src/exporter.py)
и пробой на боевом брокере: временный контейнер 0.12.2 отдал
`celery_worker_up{hostname="687f9212bebb"} 1.0`. Адрес брокера не тронут —
алиас `gendesign-redis` на сети gendesign_shared существует и резолвится.

Правило разделено на две новости. NoActiveCeleryWorkers — только когда
экспортёр жив, а воркеров нет. QueueExporterDown (warning) — когда не
отвечает сам экспортёр, с текстом «авария наблюдаемости, не продукта».
Заодно прежняя ветка `count(celery_worker_up == 1) == 0` не сработала бы
никогда: count() от пустого вектора пуст, мёртвый воркер она пропускала.

ContainerNearMemoryLimit на tradein-postgres: working_set включает активный
кэш страниц, ночные сканы поднимают его к потолку. Анонимная память за
неделю 11–170 МБ из 3 ГБ, oom_kill = 0 за 21 сутки — «дальше OOM-kill» был
ложью. Добавлено второе условие: анонимная память > 50 % лимита.
Калибровка ЭТИМ выражением по 14 суткам истории: tradein-postgres 5 → 0
эпизодов, tradein-browser 2 → 2 (06.09 и 12.09, rss до 95 % — настоящие).

Юнит-тесты правил (ops/metrics/prometheus/tests/infra_test.yml, 6 случаев,
каждый уже случался на проде) исполняются деплоем перед reload. На старых
правилах promtool test rules → rc=1 (6 расхождений), на новых → rc=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bot-backend merged commit 34642e1dd5 into main 2026-09-17 07:05:42 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3547
No description provided.