Телеметрия: метрики и Grafana — сегодня тихий отказ не виден ничем #3078

Closed
opened 2026-08-24 16:14:39 +00:00 by bot-backend · 5 comments
Collaborator

Телеметрия: метрики и Grafana для GenDesign

Зачем

Ниже — только случаи, где отсутствие измерения уже стоило времени или денег. Это не гипотезы, каждый разбирался постфактум руками.

  • Данные протухли на 7 месяцев, узнали от владельца. Плитка «ФАКТИЧЕСКИЕ СДЕЛКИ» показывала сделки семимесячной давности (#2846). Корень (#2998): deals не обновлялась 39 суток, при этом rosreestr_dkp_import каждый день рапортовал done с total_seen=0. Цитата из issue: «планировщик зелёный, монитор зелёный, данных нет». Поймала бы: freshness-метрика now() - max(scraped_at) по таблице-приёмнику + алерт на rows_ingested=0 при status=done.
  • Тот же тихий отказ ещё трижды. house_imv_backfill мёртв 34 дня (saved:0, errors:35/35, все прогоны done, #2698); cian newbuilding_enrich — 0 записей 8 суток, 200 попыток, все done (#2767); listing_source_snapshot уходил в zombie 6 ночей подряд, осиротевшие запросы висели 46 ч и держали горизонт vacuum (#2607). Поймала бы: success_ratio = saved/checked на прогон, а не status.
  • Бэкфилл Авито простаивал 23 часа из 24. 238 карточек за сутки при 9 951 активном объявлении: прогон умирал по бану за 17-83 мин, а перезапуск был раз в сутки (PR #3054). Поймала бы: утилизация задачи (доля суток с активным прогоном) + глубина очереди обогащения.
  • Деплой убивает длинные прогоны ~раз в три дня. За 30 дней 10 прогонов из 2462 в cancelled, и это ровно самые дорогие: avito_full_load_exhaustive 2 ч 58 мин, yandex_city_sweep 2 ч 33 мин, cian_full_load 2 ч 10 мин (#3074). Поймала бы: счётчик cancelled по source + потерянные часы сбора.
  • Раздутие БД копилось 91 день незамеченным. listings: 198 апдейтов на строку, HOT-обновлений 0,43 %, TOAST 15 ГБ при ~230 МБ живого, WAL 7,02 ГБ/сутки при ~4 пользовательских расчётах в сутки (#2992, #2989). Поймали бы: WAL/сутки и n_tup_upd vs n_tup_hot_upd по таблицам.
  • Разбор 1600 отказов подряд оказался невозможен. Логи контейнера avito_detail_backfill уничтожены пересозданием при обычном деплое через 17 минут; «сегодня было около двадцати деплоев» (#2741). Поймал бы: счётчик отказов на прогон с меткой причины, живущий вне lifecycle контейнера.
  • Переезд 30.08 нечем измерить. Метрик нет ни одной (grep prometheus|statsd|opentelemetry по backend/, tradein-mvp/, docker-compose*.yml — ноль), /metrics не существует ни в одном бэкенде. Сравнить Beget vs Poincare по latency/RPS/CPU объективно нечем; задержка Beget↔Selectel никогда не измерялась (#3032, оценка 10-15 мс).

Что уже есть

Инструмент Что покрывает Перекрытие с Grafana
GlitchTip (self-hosted errors.gendsgn.ru) Исключения. Site Finder: traces_sample_rate 0.05, интеграции Starlette/FastAPI/Celery/SQLAlchemy/httpx. Trade-in: traces_sample_rate=0.0 — только ошибки Не перекрывается. GlitchTip ловит события, метрики — временные ряды. Молчаливый отказ (done, 0 строк) не порождает исключения вовсе
Uptime Kuma (docker-compose.uptime.yml, отдельный проект gendesign-uptime) + внешний ops/uptime-healthcheck.sh «Отвечает ли /health 200-м». Дефолтный список проб — только health|/health|200, остальной /api/* под Basic-Auth и анонимно даёт 401 Частично. Kuma отвечает на «жив/мёртв», Grafana — на «насколько плохо». Деградация без 5xx (медленно, пустые данные, мёртвая очередь) не детектится ни одним
journald (x-logging anchor, driver journald) Логи всего стека, потолок SystemMaxUse=500M ставится вручную drop-in'ом, не через deploy.yml Не перекрывается. Логи — не агрегаты; при ~39 МБ/сутки и 500 МБ потолке окно ретенции ~13 дней
docker healthcheck'и (pg_isready, redis-cli ping, curl /health) «Процесс поднялся». HTTP /health отдаёт {status, environment, version} — без проверки БД/Redis/очереди. Trade-in /health наружу через Caddy не проксируется Не перекрывается. Зелёный /health при мёртвом Postgres — штатное поведение
Алерты Единственный рабочий канал — вебхук GlitchTip → Telegram (tradein-mvp/backend/app/api/v1/glitchtip.py). Штатный алертинг GlitchTip нем: alerts_projectalert пуст, EMAIL_URL=consolemail:// печатает письма в stdout Alertmanager/Grafana Alerting должен идти в тот же Telegram, а не заводить второй канал

Итог: третьего дашборда не появляется. Появляется отсутствующий сейчас класс данных — числовые ряды и алерты по трендам вместо алертов по событиям.

Что мерить

HTTP / API — требует инструментирования: добавить /metrics в оба бэкенда (сегодня его нет). Latency p50/p95/p99, RPS, доля 4xx/5xx по роутам. Альтернатива без кода на первом шаге — Caddy-метрики (gendsgn.ru.log, ретенция не проверена).

Celery и скрапперы — почти всё уже в БД, просто не визуализируется: kn_scrape_runs / nspd_scrape_runs (started_at, finished_at, heartbeat_at, status), счётчики saved/checked/errors. Отсюда без кода: success_ratio, freshness приёмника, утилизация, cancelled по source. Требует кода: длина очереди Celery (Redis llen), возраст самой старой задачи, duration_ms — колонок длительности в ORM нет, единственный хит job_settings.rate_ms. Предусловие: #2702 — из 487 прогонов 153 «длятся» дольше своего окна >1,5×, крайний случай cian_history_backfill id=346: окно 0,032 с при duration_sec 18 230. Пока start/finish врут, графики throughput тоже врут.

Postgres — postgres_exporter, без кода: WAL/сутки, n_tup_upd vs n_tup_hot_upd, рост TOAST, bloat, connections, replication/vacuum-горизонт. pg_stat_statements включён только у trade-in; для Site Finder-PG у сервиса нет секции command вовсе — потребуется правка root-compose и рестарт прод-БД.

Хост — node_exporter + cAdvisor, без кода: CPU/RAM/диск/IO, per-container память. Отдельно нужен диск: на проде 79 % занято, 13 ГБ логов forgejo/glitchtip без ротации (#2761).

Бизнес — из БД, без кода: расчёты/сутки (сейчас ~4), доля активных объявлений (26 203 мёртвых числятся активными, #2994), возраст свежайших сделок. Плюс синтетический heartbeat, доказывающий доставку алерта до человека: в GlitchTip-проекте МЕРЫ не ушло наружу ни одного сообщения с 30 мая (#2673).

Где размещать — решение владельца

30.08 хосты разъезжаются: CADDY_SITES=apps (gendsgn.ru, meraocenka/merahome/meraotsenka) → Selectel Poincare, CADDY_SITES=infra (obsidian./errors./git.) → остаётся на Beget. Три варианта, не выбираю за владельца:

  1. На Selectel (уезжающем) — рядом с продуктом, exporter'ы ходят по gendesign_shared без сети наружу; site-блок в caddy/sites/apps.caddy. Минус: при падении хоста падает и мониторинг.
  2. На Beget (остающемся) — рядом с GlitchTip; наблюдатель переживает наблюдаемого. Минус: exporter'ы придётся выставлять между хостами (UFW на Poincare открывает только 22/80/443 — нужен VPN/туннель), плюс неизмеренная задержка канала (#3032).
  3. На обоих — Prometheus на Selectel + внешний watchdog на Beget. Дороже на один compose-стек, но снимает слепое пятно.

Смежное: ops/uptime-healthcheck.sh не прописан ни в одном cron — в ops/crontab-poincare.cron его нет по замыслу (должен крутиться на другом хосте). После переезда хост для него надо назначить явно, иначе watchdog не запущен нигде.

Технически стек копируется с docker-compose.obsidian.yml один-в-один: свой project (-p gendesign-metrics), external network gendesign_shared, expose без ports, свой deploy-metrics.yml. Grafana закрывается тем же import caddy/users.caddy.snippet (basic_auth bcrypt) внутри route{}forward_auth в репозитории не используется нигде (0 хитов). Нужен A-record metrics. или grafana.gendsgn.ru; auto-TLS подхватит.

Стоимость

Объявленными mem_limit расписано ~11,4 ГиБ (root-compose ~3968 МиБ: osrm 1.5g + osrm-walk 1.5g + glitchtip-web 512m + glitchtip-worker 384m; trade-in ~7680 МиБ: browser 2560m + postgres 3g + backend 768m + scraper 640m + tgbot 256m + frontend 384m). На хосте 62-64 ГБ. Место под стек метрик (2-4 ГБ: Prometheus 1-2g, Grafana 512m, exporter'ы по 64-128m) есть с большим запасом.

Предупреждение честности ради: цифра 62/64 ГБ взята из комментариев ops/selectel-bootstrap.sh, а не из живого free на хосте — проверить перед стартом. И сумма лимитов ≠ потребление: без mem_limit вообще работают postgres (Site Finder), redis, backend, frontend, worker (--concurrency=8), beat, caddy, glitchtip-auth-forwarder. Реальное потребление сверху не ограничено, бюджет брать с запасом. Диск: retention Prometheus задать явно (15d), с учётом что на проде уже 79 % занято.

Что НЕ входит

  • Замена GlitchTip. Ошибки остаются в GlitchTip, метрики — отдельный слой.
  • Полноценный distributed tracing / OpenTelemetry. Только метрики.
  • Логовый стек (Loki/ELK). Логи остаются в journald; ротация — отдельная задача (#2761).
  • Переписывание скрапперов ради «правильных» метрик. Фикс честных started_at/finished_at (#2702) — предусловие, а не часть этой задачи.
  • Миграция на новый хост. Стек метрик едет отдельным compose-проектом, deploy.yml не трогаем.
  • Дашборды «на будущее». Только графики под перечисленные выше конкретные случаи.

Приёмка

  • /metrics отвечает на обоих бэкендах, закрыт от анонимного доступа.
  • Prometheus скрейпит: оба бэкенда, node_exporter, cAdvisor, postgres_exporter (минимум по trade-in-PG).
  • Grafana доступна по HTTPS через Caddy под basic_auth; порты 3000/9090 наружу не опубликованы.
  • Дашборд «Скрапперы»: по каждому source — success_ratio, freshness приёмника, утилизация, счётчик cancelled. На дашборде воспроизводится кейс #2998 (зелёный done при 0 строк виден как аномалия).
  • Дашборд «Postgres»: WAL/сутки, n_tup_upd vs n_tup_hot_upd, рост TOAST, connections.
  • Дашборд «Хост»: CPU/RAM/диск/IO, per-container память, алерт на диск >85 %.
  • Алерты уходят в существующий Telegram-канал (не новый). Проверено живой доставкой, не конфигом.
  • Синтетический heartbeat: отсутствие сигнала N минут даёт алерт (защита от «канал есть, но нем» как в #2673).
  • Compose-стек поднимается на чистом хосте одной командой; retention Prometheus задан явно; mem_limit проставлены.
  • Замер до/после переезда: снят baseline latency/RPS/CPU на старом хосте и сопоставим на новом.

Открытые вопросы владельцу

  1. Где стек — Selectel, Beget или оба (три варианта выше)? Блокирует всё остальное.
  2. До или после 30.08? Если baseline для сравнения хостов нужен — стек должен быть поднят до переезда, а это сжатое окно.
  3. Доменmetrics.gendsgn.ru или grafana.gendsgn.ru? Нужен A-record.
  4. Рестарт прод-PG Site Finder ради shared_preload_libraries = pg_stat_statements — согласован? Без него query-метрик по Site Finder не будет.
  5. Кто получает алерты — тот же Telegram-чат, что и GlitchTip-вебхук, или отдельный? Порог, при котором алерт будят ночью.
  6. Хост для ops/uptime-healthcheck.sh после переезда — назначить явно.

Разбиение

Задача больше одного захода (новый compose-стек + инструментирование двух бэкендов + exporter'ы + дашборды + алерты + деплой-воркфлоу — заведомо >5 файлов и >8 ч). Предлагаемый сплит, каждая часть ~200-500 строк:

  1. Foundationdocker-compose.metrics.yml по шаблону docker-compose.obsidian.yml, .forgejo/workflows/deploy-metrics.yml, site-блок в apps.caddy под basic_auth, node_exporter + cAdvisor. Приёмка: Grafana открывается, хостовые графики живые.
  2. Postgres — postgres_exporter по trade-in-PG (там pg_stat_statements уже включён), дашборд БД. Отдельно и позже — shared_preload_libraries для Site Finder-PG, требует рестарта.
  3. App-метрики/metrics в обоих бэкендах, HTTP-гистограммы, длина очереди Celery. Единственная часть, трогающая прод-код.
  4. Скрапперы и бизнес — экспортер поверх kn_scrape_runs/nspd_scrape_runs и таблиц-приёмников (success_ratio, freshness, утилизация), дашборд, воспроизводящий #2998/#2698/#2767.
  5. Алерты — правила, доставка в существующий Telegram, синтетический heartbeat, проверка живой доставкой.

Части 1-2 независимы от 3; 4 зависит от фикса честных таймстампов (#2702); 5 — последняя.

# Телеметрия: метрики и Grafana для GenDesign ## Зачем Ниже — только случаи, где отсутствие измерения уже стоило времени или денег. Это не гипотезы, каждый разбирался постфактум руками. - **Данные протухли на 7 месяцев, узнали от владельца.** Плитка «ФАКТИЧЕСКИЕ СДЕЛКИ» показывала сделки семимесячной давности (#2846). Корень (#2998): `deals` не обновлялась 39 суток, при этом `rosreestr_dkp_import` каждый день рапортовал `done` с `total_seen=0`. Цитата из issue: «планировщик зелёный, монитор зелёный, данных нет». Поймала бы: freshness-метрика `now() - max(scraped_at)` по таблице-приёмнику + алерт на `rows_ingested=0 при status=done`. - **Тот же тихий отказ ещё трижды.** `house_imv_backfill` мёртв 34 дня (saved:0, errors:35/35, все прогоны `done`, #2698); cian `newbuilding_enrich` — 0 записей 8 суток, 200 попыток, все `done` (#2767); `listing_source_snapshot` уходил в zombie 6 ночей подряд, осиротевшие запросы висели 46 ч и держали горизонт vacuum (#2607). Поймала бы: `success_ratio = saved/checked` на прогон, а не `status`. - **Бэкфилл Авито простаивал 23 часа из 24.** 238 карточек за сутки при 9 951 активном объявлении: прогон умирал по бану за 17-83 мин, а перезапуск был раз в сутки (PR #3054). Поймала бы: утилизация задачи (доля суток с активным прогоном) + глубина очереди обогащения. - **Деплой убивает длинные прогоны ~раз в три дня.** За 30 дней 10 прогонов из 2462 в `cancelled`, и это ровно самые дорогие: `avito_full_load_exhaustive` 2 ч 58 мин, `yandex_city_sweep` 2 ч 33 мин, `cian_full_load` 2 ч 10 мин (#3074). Поймала бы: счётчик `cancelled` по source + потерянные часы сбора. - **Раздутие БД копилось 91 день незамеченным.** `listings`: 198 апдейтов на строку, HOT-обновлений 0,43 %, TOAST 15 ГБ при ~230 МБ живого, WAL 7,02 ГБ/сутки при ~4 пользовательских расчётах в сутки (#2992, #2989). Поймали бы: WAL/сутки и `n_tup_upd` vs `n_tup_hot_upd` по таблицам. - **Разбор 1600 отказов подряд оказался невозможен.** Логи контейнера `avito_detail_backfill` уничтожены пересозданием при обычном деплое через 17 минут; «сегодня было около двадцати деплоев» (#2741). Поймал бы: счётчик отказов на прогон с меткой причины, живущий вне lifecycle контейнера. - **Переезд 30.08 нечем измерить.** Метрик нет ни одной (`grep prometheus|statsd|opentelemetry` по `backend/`, `tradein-mvp/`, `docker-compose*.yml` — ноль), `/metrics` не существует ни в одном бэкенде. Сравнить Beget vs Poincare по latency/RPS/CPU объективно нечем; задержка Beget↔Selectel никогда не измерялась (#3032, оценка 10-15 мс). ## Что уже есть | Инструмент | Что покрывает | Перекрытие с Grafana | |---|---|---| | **GlitchTip** (self-hosted `errors.gendsgn.ru`) | Исключения. Site Finder: `traces_sample_rate` 0.05, интеграции Starlette/FastAPI/Celery/SQLAlchemy/httpx. Trade-in: `traces_sample_rate=0.0` — только ошибки | Не перекрывается. GlitchTip ловит **события**, метрики — **временные ряды**. Молчаливый отказ (`done`, 0 строк) не порождает исключения вовсе | | **Uptime Kuma** (`docker-compose.uptime.yml`, отдельный проект `gendesign-uptime`) + внешний `ops/uptime-healthcheck.sh` | «Отвечает ли `/health` 200-м». Дефолтный список проб — только `health\|/health\|200`, остальной `/api/*` под Basic-Auth и анонимно даёт 401 | Частично. Kuma отвечает на «жив/мёртв», Grafana — на «насколько плохо». Деградация без 5xx (медленно, пустые данные, мёртвая очередь) не детектится ни одним | | **journald** (`x-logging` anchor, driver journald) | Логи всего стека, потолок `SystemMaxUse=500M` ставится вручную drop-in'ом, не через deploy.yml | Не перекрывается. Логи — не агрегаты; при ~39 МБ/сутки и 500 МБ потолке окно ретенции ~13 дней | | **docker healthcheck'и** (pg_isready, redis-cli ping, curl /health) | «Процесс поднялся». HTTP `/health` отдаёт `{status, environment, version}` — без проверки БД/Redis/очереди. Trade-in `/health` наружу через Caddy не проксируется | Не перекрывается. Зелёный `/health` при мёртвом Postgres — штатное поведение | | **Алерты** | Единственный рабочий канал — вебхук GlitchTip → Telegram (`tradein-mvp/backend/app/api/v1/glitchtip.py`). Штатный алертинг GlitchTip нем: `alerts_projectalert` пуст, `EMAIL_URL=consolemail://` печатает письма в stdout | Alertmanager/Grafana Alerting должен идти в **тот же** Telegram, а не заводить второй канал | Итог: третьего дашборда не появляется. Появляется отсутствующий сейчас класс данных — числовые ряды и алерты по трендам вместо алертов по событиям. ## Что мерить **HTTP / API** — требует инструментирования: добавить `/metrics` в оба бэкенда (сегодня его нет). Latency p50/p95/p99, RPS, доля 4xx/5xx по роутам. Альтернатива без кода на первом шаге — Caddy-метрики (`gendsgn.ru.log`, ретенция не проверена). **Celery и скрапперы** — почти всё **уже в БД, просто не визуализируется**: `kn_scrape_runs` / `nspd_scrape_runs` (`started_at`, `finished_at`, `heartbeat_at`, `status`), счётчики saved/checked/errors. Отсюда без кода: success_ratio, freshness приёмника, утилизация, `cancelled` по source. **Требует кода**: длина очереди Celery (Redis `llen`), возраст самой старой задачи, `duration_ms` — колонок длительности в ORM нет, единственный хит `job_settings.rate_ms`. **Предусловие:** `#2702` — из 487 прогонов 153 «длятся» дольше своего окна >1,5×, крайний случай `cian_history_backfill` id=346: окно 0,032 с при `duration_sec` 18 230. Пока start/finish врут, графики throughput тоже врут. **Postgres** — postgres_exporter, без кода: WAL/сутки, `n_tup_upd` vs `n_tup_hot_upd`, рост TOAST, bloat, connections, replication/vacuum-горизонт. `pg_stat_statements` включён **только у trade-in**; для Site Finder-PG у сервиса нет секции `command` вовсе — потребуется правка root-compose и рестарт прод-БД. **Хост** — node_exporter + cAdvisor, без кода: CPU/RAM/диск/IO, per-container память. Отдельно нужен диск: на проде 79 % занято, 13 ГБ логов forgejo/glitchtip без ротации (#2761). **Бизнес** — из БД, без кода: расчёты/сутки (сейчас ~4), доля активных объявлений (26 203 мёртвых числятся активными, #2994), возраст свежайших сделок. Плюс **синтетический heartbeat**, доказывающий доставку алерта до человека: в GlitchTip-проекте МЕРЫ не ушло наружу ни одного сообщения с 30 мая (#2673). ## Где размещать — решение владельца 30.08 хосты разъезжаются: `CADDY_SITES=apps` (gendsgn.ru, meraocenka/merahome/meraotsenka) → Selectel Poincare, `CADDY_SITES=infra` (obsidian./errors./git.) → остаётся на Beget. Три варианта, **не выбираю за владельца**: 1. **На Selectel (уезжающем)** — рядом с продуктом, exporter'ы ходят по `gendesign_shared` без сети наружу; site-блок в `caddy/sites/apps.caddy`. Минус: при падении хоста падает и мониторинг. 2. **На Beget (остающемся)** — рядом с GlitchTip; наблюдатель переживает наблюдаемого. Минус: exporter'ы придётся выставлять между хостами (UFW на Poincare открывает только 22/80/443 — нужен VPN/туннель), плюс неизмеренная задержка канала (#3032). 3. **На обоих** — Prometheus на Selectel + внешний watchdog на Beget. Дороже на один compose-стек, но снимает слепое пятно. Смежное: `ops/uptime-healthcheck.sh` **не прописан ни в одном cron** — в `ops/crontab-poincare.cron` его нет по замыслу (должен крутиться на другом хосте). После переезда хост для него надо назначить явно, иначе watchdog не запущен нигде. Технически стек копируется с `docker-compose.obsidian.yml` один-в-один: свой project (`-p gendesign-metrics`), external network `gendesign_shared`, `expose` без `ports`, свой `deploy-metrics.yml`. Grafana закрывается тем же `import caddy/users.caddy.snippet` (basic_auth bcrypt) внутри `route{}` — `forward_auth` в репозитории не используется нигде (0 хитов). Нужен A-record `metrics.` или `grafana.gendsgn.ru`; auto-TLS подхватит. ## Стоимость Объявленными `mem_limit` расписано ~11,4 ГиБ (root-compose ~3968 МиБ: osrm 1.5g + osrm-walk 1.5g + glitchtip-web 512m + glitchtip-worker 384m; trade-in ~7680 МиБ: browser 2560m + postgres 3g + backend 768m + scraper 640m + tgbot 256m + frontend 384m). На хосте 62-64 ГБ. Место под стек метрик (2-4 ГБ: Prometheus 1-2g, Grafana 512m, exporter'ы по 64-128m) есть с большим запасом. **Предупреждение честности ради:** цифра 62/64 ГБ взята из комментариев `ops/selectel-bootstrap.sh`, а не из живого `free` на хосте — проверить перед стартом. И сумма лимитов ≠ потребление: **без `mem_limit` вообще** работают postgres (Site Finder), redis, backend, frontend, worker (`--concurrency=8`), beat, caddy, glitchtip-auth-forwarder. Реальное потребление сверху не ограничено, бюджет брать с запасом. Диск: retention Prometheus задать явно (15d), с учётом что на проде уже 79 % занято. ## Что НЕ входит - Замена GlitchTip. Ошибки остаются в GlitchTip, метрики — отдельный слой. - Полноценный distributed tracing / OpenTelemetry. Только метрики. - Логовый стек (Loki/ELK). Логи остаются в journald; ротация — отдельная задача (#2761). - Переписывание скрапперов ради «правильных» метрик. Фикс честных `started_at`/`finished_at` (#2702) — предусловие, а не часть этой задачи. - Миграция на новый хост. Стек метрик едет отдельным compose-проектом, deploy.yml не трогаем. - Дашборды «на будущее». Только графики под перечисленные выше конкретные случаи. ## Приёмка - [ ] `/metrics` отвечает на обоих бэкендах, закрыт от анонимного доступа. - [ ] Prometheus скрейпит: оба бэкенда, node_exporter, cAdvisor, postgres_exporter (минимум по trade-in-PG). - [ ] Grafana доступна по HTTPS через Caddy под basic_auth; порты 3000/9090 наружу не опубликованы. - [ ] Дашборд «Скрапперы»: по каждому source — success_ratio, freshness приёмника, утилизация, счётчик `cancelled`. На дашборде **воспроизводится** кейс #2998 (зелёный `done` при 0 строк виден как аномалия). - [ ] Дашборд «Postgres»: WAL/сутки, `n_tup_upd` vs `n_tup_hot_upd`, рост TOAST, connections. - [ ] Дашборд «Хост»: CPU/RAM/диск/IO, per-container память, алерт на диск >85 %. - [ ] Алерты уходят в существующий Telegram-канал (не новый). Проверено **живой доставкой**, не конфигом. - [ ] Синтетический heartbeat: отсутствие сигнала N минут даёт алерт (защита от «канал есть, но нем» как в #2673). - [ ] Compose-стек поднимается на чистом хосте одной командой; retention Prometheus задан явно; `mem_limit` проставлены. - [ ] Замер до/после переезда: снят baseline latency/RPS/CPU на старом хосте и сопоставим на новом. ## Открытые вопросы владельцу 1. **Где стек** — Selectel, Beget или оба (три варианта выше)? Блокирует всё остальное. 2. **До или после 30.08?** Если baseline для сравнения хостов нужен — стек должен быть поднят **до** переезда, а это сжатое окно. 3. **Домен** — `metrics.gendsgn.ru` или `grafana.gendsgn.ru`? Нужен A-record. 4. **Рестарт прод-PG Site Finder** ради `shared_preload_libraries = pg_stat_statements` — согласован? Без него query-метрик по Site Finder не будет. 5. **Кто получает алерты** — тот же Telegram-чат, что и GlitchTip-вебхук, или отдельный? Порог, при котором алерт будят ночью. 6. **Хост для `ops/uptime-healthcheck.sh`** после переезда — назначить явно. ## Разбиение Задача больше одного захода (новый compose-стек + инструментирование двух бэкендов + exporter'ы + дашборды + алерты + деплой-воркфлоу — заведомо >5 файлов и >8 ч). Предлагаемый сплит, каждая часть ~200-500 строк: 1. **Foundation** — `docker-compose.metrics.yml` по шаблону `docker-compose.obsidian.yml`, `.forgejo/workflows/deploy-metrics.yml`, site-блок в `apps.caddy` под basic_auth, node_exporter + cAdvisor. Приёмка: Grafana открывается, хостовые графики живые. 2. **Postgres** — postgres_exporter по trade-in-PG (там `pg_stat_statements` уже включён), дашборд БД. Отдельно и позже — `shared_preload_libraries` для Site Finder-PG, требует рестарта. 3. **App-метрики** — `/metrics` в обоих бэкендах, HTTP-гистограммы, длина очереди Celery. Единственная часть, трогающая прод-код. 4. **Скрапперы и бизнес** — экспортер поверх `kn_scrape_runs`/`nspd_scrape_runs` и таблиц-приёмников (success_ratio, freshness, утилизация), дашборд, воспроизводящий #2998/#2698/#2767. 5. **Алерты** — правила, доставка в существующий Telegram, синтетический heartbeat, проверка живой доставкой. Части 1-2 независимы от 3; 4 зависит от фикса честных таймстампов (#2702); 5 — последняя.
Owner

Проверил вопрос «а что если полностью на Grafana» разведкой по коду и живым данным. Границы issue подтвердились, но три вещи в скоупе стоит поправить.

Полного переезда не получается — трекер ошибок Grafana OSS не заменяет. Не «дорого» и не «неудобно», а нечем: ни один компонент Grafana OSS не принимает Sentry DSN/envelope (у OTel Collector есть sentryexporter — он шлёт в Sentry, receiver протокола числится in progress). Error fingerprinting и issue-инбокс документированы только в Grafana Cloud Frontend Observability; в self-hosted Grafana+Loki это плоские логи без группировки, без assign/resolve, без детекта регрессий. Faro покрывает только фронтенд, а серверные исключения Python — основной источник — не собирает вообще.

На живых данных: 584 события свёрнуты в 50 unresolved issue, дедуп 11.7:1, топ-issue = 147 событий одной строкой. В Loki это 584 строки.

Цена отказа: sentry_sdk.init в двух бэкендах (6 и 5 интеграций), 3092 строки в 15 файлах, точечные capture_exception в 96 файлах. Отдельно 549 строк скрубберов — это не фича GlitchTip, а 152-ФЗ и вычистка Telegram-токена из locals стек-фрейма; преемник обязан воспроизвести before_send, иначе ПДн поедут в новый бэкенд. Плюс контракт LoggingIntegration(event_level=ERROR): каждый logger.error уже является событием трекера, на это прямо ссылаются комментарии в main.py. Экономии тоже нет — GlitchTip забронировал 896 МБ, Grafana+Prometheus+Loki того же порядка.

Метрики и логи — наоборот, чистый выигрыш. Метрик сегодня нет вообще: ни Prometheus, ни экспортеров, journald — единственный канал; мы не знаем ни RSS во времени, ни latency ручек, ни глубины очереди Celery. Loki monolithic рекомендован до ~20 ГБ/сутки, у нас ~39 МБ/сутки — запас в 500 раз.

Правки в скоуп

  1. Вариант 3 сделать асимметричным. Prometheus + Grafana на Selectel, на Beget — лёгкий watchdog (blackbox/healthcheck, ~100 МБ) и приёмник алертов. Он и есть наблюдатель, переживающий наблюдаемого. Полную Grafana на Beget не поставить — не выбор архитектуры, а отсутствие ресурса: 11.68 ГиБ физики, план #3030 уже 11.7 ГиБ, Forgejo ест 2.235 ГиБ без mem_limit, диск на 70%. Pull-скрейп через линк переживёт: p50 17.50 / p95 17.70 мс, потерь 0% (#3032).
  2. Выкинуть Mimir и Tempo — при 39 МБ/сутки Loki monolithic хватает с запасом.
  3. Добавить: удалить ops/glitchtip-auth-forwarder. 408 строк + 77 тестов + отдельный сервис в compose, при этом его before_send дропает все 401-события — в трекер он сегодня не шлёт ничего. 401-лог Caddy естественнее отдать promtail→Loki. Чистые 485 строк минус.

Что тянет за собой

#2761 (ротация логов) из «навести порядок в journald» становится предусловием Loki: glitchtip-worker льёт 31.9 МБ/сутки — 81% всего роста журнала. Тот же поток пойдёт в Loki и на общий RAID1. Делать до этой задачи, не после. Туда же — ретенция GlitchTip: grep retention/EVENT_RETENTION даёт ноль попаданий, 729 МБ растут без политики.

Вебхук tradein→GlitchTip (219 строк + 291 тест, секрет в query, сеть gendesign_shared) ломается переездом сам по себе — после разъезда это cross-host по публичному URL. Переписывать придётся независимо от решения по Grafana.

Не измерено, нужно до старта

  • Свободное место на Poincare — df нигде не зафиксирован. Если Loki встанет туда, его чанки лягут на тот же NVMe RAID1, что оба кластера Postgres: всплеск логов забивает диск и валит БД.
  • Реальный RSS Loki/Prometheus на нашем профиле — цифры пока только из доков.
  • Триажит ли кто-то инбокс фактически? Все 50 issue unresolved, ни одной resolved/ignored за два месяца (25.06→24.08). Если нет — аргумент «issue-модель» слабеет, и LogQL-алерт по ERROR даёт то же дешевле.
  • grafana.* в apps.caddy или infra.caddy? Ошибка = недостижимый ACME challenge и лимит LE 5 failed validations/hostname/hour.
  • docker-compose.prod.yml и caddy/** триггерят оба деплоя из одного файла — стек метрик нужен отдельным compose или под profiles.
Проверил вопрос «а что если полностью на Grafana» разведкой по коду и живым данным. Границы issue подтвердились, но три вещи в скоупе стоит поправить. **Полного переезда не получается — трекер ошибок Grafana OSS не заменяет.** Не «дорого» и не «неудобно», а нечем: ни один компонент Grafana OSS не принимает Sentry DSN/envelope (у OTel Collector есть `sentryexporter` — он шлёт *в* Sentry, receiver протокола числится in progress). Error fingerprinting и issue-инбокс документированы только в Grafana Cloud Frontend Observability; в self-hosted Grafana+Loki это плоские логи без группировки, без assign/resolve, без детекта регрессий. Faro покрывает только фронтенд, а серверные исключения Python — основной источник — не собирает вообще. На живых данных: 584 события свёрнуты в 50 unresolved issue, дедуп 11.7:1, топ-issue = 147 событий одной строкой. В Loki это 584 строки. Цена отказа: `sentry_sdk.init` в двух бэкендах (6 и 5 интеграций), 3092 строки в 15 файлах, точечные `capture_exception` в 96 файлах. Отдельно 549 строк скрубберов — это не фича GlitchTip, а 152-ФЗ и вычистка Telegram-токена из locals стек-фрейма; преемник обязан воспроизвести `before_send`, иначе ПДн поедут в новый бэкенд. Плюс контракт `LoggingIntegration(event_level=ERROR)`: каждый `logger.error` уже является событием трекера, на это прямо ссылаются комментарии в `main.py`. Экономии тоже нет — GlitchTip забронировал 896 МБ, Grafana+Prometheus+Loki того же порядка. **Метрики и логи — наоборот, чистый выигрыш.** Метрик сегодня нет вообще: ни Prometheus, ни экспортеров, `journald` — единственный канал; мы не знаем ни RSS во времени, ни latency ручек, ни глубины очереди Celery. Loki monolithic рекомендован до ~20 ГБ/сутки, у нас ~39 МБ/сутки — запас в 500 раз. ### Правки в скоуп 1. **Вариант 3 сделать асимметричным.** Prometheus + Grafana на Selectel, на Beget — лёгкий watchdog (blackbox/healthcheck, ~100 МБ) и приёмник алертов. Он и есть наблюдатель, переживающий наблюдаемого. Полную Grafana на Beget не поставить — не выбор архитектуры, а отсутствие ресурса: 11.68 ГиБ физики, план #3030 уже 11.7 ГиБ, Forgejo ест 2.235 ГиБ без `mem_limit`, диск на 70%. Pull-скрейп через линк переживёт: p50 17.50 / p95 17.70 мс, потерь 0% (#3032). 2. **Выкинуть Mimir и Tempo** — при 39 МБ/сутки Loki monolithic хватает с запасом. 3. **Добавить: удалить `ops/glitchtip-auth-forwarder`.** 408 строк + 77 тестов + отдельный сервис в compose, при этом его `before_send` дропает **все** 401-события — в трекер он сегодня не шлёт ничего. 401-лог Caddy естественнее отдать promtail→Loki. Чистые 485 строк минус. ### Что тянет за собой **#2761 (ротация логов)** из «навести порядок в journald» становится предусловием Loki: `glitchtip-worker` льёт 31.9 МБ/сутки — 81% всего роста журнала. Тот же поток пойдёт в Loki и на общий RAID1. Делать **до** этой задачи, не после. Туда же — ретенция GlitchTip: `grep retention/EVENT_RETENTION` даёт ноль попаданий, 729 МБ растут без политики. **Вебхук tradein→GlitchTip** (219 строк + 291 тест, секрет в query, сеть `gendesign_shared`) ломается переездом сам по себе — после разъезда это cross-host по публичному URL. Переписывать придётся независимо от решения по Grafana. ### Не измерено, нужно до старта - Свободное место на Poincare — `df` нигде не зафиксирован. Если Loki встанет туда, его чанки лягут на тот же NVMe RAID1, что оба кластера Postgres: всплеск логов забивает диск и валит БД. - Реальный RSS Loki/Prometheus на нашем профиле — цифры пока только из доков. - Триажит ли кто-то инбокс фактически? Все 50 issue unresolved, ни одной resolved/ignored за два месяца (25.06→24.08). Если нет — аргумент «issue-модель» слабеет, и LogQL-алерт по ERROR даёт то же дешевле. - `grafana.*` в `apps.caddy` или `infra.caddy`? Ошибка = недостижимый ACME challenge и лимит LE 5 failed validations/hostname/hour. - `docker-compose.prod.yml` и `caddy/**` триггерят оба деплоя из одного файла — стек метрик нужен отдельным compose или под profiles.
Owner

Уточнение к предыдущему комментарию. Владелец решил держать инфраструктуру на Beget, и замеры показали, что она туда помещается — это меняет архитектуру задачи в лучшую сторону.

Замеры (SSH, не оценка)

Beget: 6 vCPU, 11960 МиБ RAM, диск 145 ГБ на 70%, своп занят 1783 МиБ, 25 контейнеров ≈ 5.2 ГиБ. Крупнейший — forgejo 2.324 ГиБ, 44% всей памяти контейнеров, mem_limit нет.

Selectel: выделенный сервер, не облако (systemd-detect-virt = none), Ryzen 5 5600G, 63592 МиБ RAM (свободно 49 ГБ), RAID 910 ГБ занят на 8% — 798 ГБ свободно, load 0.01, 3 контейнера.

Это закрывает вопрос «свободное место на Poincare не измерено» из прошлого комментария.

После 30.08 продуктовый стек уходит с Beget и освобождает 2.58 ГиБ. Остаётся инфра 2.63 ГиБ + infra-postgres ~0.2 + наблюдаемость 1.0–1.5 ≈ 4.1 ГБ из 11.96. Своп рассосётся сам, без единого рубля.

Что меняется: Grafana ставим на Beget, а не на Selectel

Прошлый вывод («стек метрик увезти на Selectel») отменяется замерами. Grafana + Prometheus + Loki встают на Beget рядом с GlitchTip, и это снимает почти все ограничения плагина.

Чтение GlitchTip — прямой SQL-датасорс, а не Sentry-плагин. Схема годится: issue_events_issueevent (RANGE-партиции по received; issue_id, organization_id, level, type, tags jsonb) + issue_events_issue (project_id, first_seen). Готовые агрегаты IssueIndex (count, last_seen, status, level) и IssueAggregate с date в составном PK дают ряды и топ-N без сканов сырья. Правок в GlitchTip ноль, нужен read-only пользователь. #3061 уже вынес эту базу в отдельный кластер, поэтому продуктовый Postgres такой доступ не задевает.

Почему не плагин, хотя он официально документирован GlitchTip (glitchtip.com/documentation/integrations, обычный read-only Auth Token, Grafana ≥10.4, Cloud не нужен):

  • stats_v2 с фильтром по проекту → 500, открытый баг GlitchTip #381 с 2025-01-10. Per-project панели не заведутся.
  • Events/Discover → 404, #416 (open, 2025-08-25). Metrics/Spans/Tags — Sentry-only.
  • В grafana/sentry-datasource ноль issue со словом glitchtip: апстрим связку не тестирует и не заявляет неподдержку. В 2.2.6 Issues перевели на lifetime firstSeen/lastSeen — при ином сериализаторе GlitchTip панели молча исказятся.
  • Долгоживущий read-токен на всю организацию, привязанный к пользователю; протухание проявится пустыми панелями, а не ошибкой.

Плагин можно оставить вторым, необязательным источником — но нести на нём основные панели не стоит.

Логи: Alloy, не Promtail. У Promtail EOL 2 марта 2026, ставить его в 2026 нельзя. Alloy читает journald нативно (loki.source.journal, дефолт /var/log/journal и /run/log/journal). Две грабли: пользователь alloy обязан быть в группах adm и systemd-journal, иначе тихо ноль записей; в контейнере путь задавать явно (path="/var/log/journal"), иначе SD_JOURNAL_LOCAL_ONLY скроет хостовые логи — монтировать /var/log/journal и /etc/machine-id, systemd внутри контейнера не нужен.

Транспорт push-овый — входящих портов на Selectel не открываем. Alloy на Selectel шлёт логи через loki.write и метрики через prometheus.remote_write исходящим HTTPS с basic_auth. Приёмнику метрик нужен --web.enable-remote-write-receiver (endpoint /api/v1/write), приёмники прячем за Caddy с TLS и авторизацией. Наши 39 МБ/сутки против рекомендованного потолка Loki ~20 ГБ/сутки.

Топология это позволяет: между хостами нет ни VPN, ни приватной сети, ни туннеля (единственные хиты grep — ручной ssh -N); наружу опубликованы только 80/443, firewall Selectel — default deny incoming + SSH/80/443. Push обходит всё это без единого нового правила.

Побочный выигрыш: наблюдатель оказывается у другого провайдера, чем наблюдаемое. Упадёт Selectel — Grafana на Beget жива и покажет ровно это.

Скоуп задачи

  • Вариант 3 переписать: Grafana + Prometheus + Loki на Beget, на Selectel только Alloy как push-агент. Прежняя формулировка (стек на Selectel, watchdog на Beget) отменяется замерами.
  • Основной источник по ошибкам — PostgreSQL-датасорс в базу glitchtip. Sentry-плагин — опционально, вторым.
  • Выкинуть Mimir и Tempo.
  • Promtail заменить на Alloy во всех упоминаниях.
  • Убрать из скоупа панели Events/Discover/Metrics/Spans/Tags и per-project stats_v2.
  • Переформулировать цель: не «единое окно», а обзорный слой + логи. GlitchTip остаётся рабочим местом триажа: и плагин, и SQL одинаково read-only — assign/resolve/ignore, стектрейсы и breadcrumbs только там.
  • Алертинг «новый дедуплицированный issue» оставить в GlitchTip. Grafana умеет порог по окну — это другая семантика. Иначе правила расползутся по трём местам (почта GlitchTip, Uptime Kuma, Grafana), и silence в Grafana не заглушит письма.
  • Добавить алерт на здоровье датасорса: пустая панель неотличима от «ошибок нет».
  • Добавить: mem_limit на forgejo — он один ест 44% памяти контейнеров и ничем не ограничен. Дешевле и полезнее любого апгрейда тарифа.
  • Добавить: удалить ops/glitchtip-auth-forwarder (485 строк, его before_send дропает все 401-события — в трекер он не шлёт ничего).
  • Зафиксировать CADDY_SITES и COMPOSE_PROFILES в git или runbook — сейчас задаются руками на хостах.

Проверить руками до старта

  1. \dt в базе glitchtip на нашей 6.1.6 — до версии 4.x приложение называлось issues_*, имена таблиц и колонок (received против timestamp) и наличие IssueIndex/IssueAggregate надо подтвердить глазами. Это несущий факт для SQL-пути.
  2. Реальный RSS Loki monolithic и Alloy на нашем профиле — официальных минимумов для monolithic нет, цифры из практики (~512 МБ–1 ГБ и ~100–200 МБ).
  3. Фактический ufw на Beget — в репозитории зафиксирован только bootstrap Selectel.
  4. ENABLE_OBSERVABILITY_API у GlitchTip 6 включает /metrics через django-prometheus — но это метрики HTTP/БД/Django, не ошибки в разрезе issue. Годится только для здоровья сервиса.
  5. Триажит ли кто-то инбокс фактически: все 50 issue unresolved за два месяца.
Уточнение к предыдущему комментарию. Владелец решил держать инфраструктуру на Beget, и замеры показали, что она туда помещается — это меняет архитектуру задачи в лучшую сторону. ## Замеры (SSH, не оценка) Beget: 6 vCPU, 11960 МиБ RAM, диск 145 ГБ на 70%, **своп занят 1783 МиБ**, 25 контейнеров ≈ 5.2 ГиБ. Крупнейший — `forgejo` 2.324 ГиБ, 44% всей памяти контейнеров, `mem_limit` нет. Selectel: **выделенный сервер**, не облако (`systemd-detect-virt` = none), Ryzen 5 5600G, 63592 МиБ RAM (свободно 49 ГБ), RAID 910 ГБ занят на 8% — **798 ГБ свободно**, load 0.01, 3 контейнера. Это закрывает вопрос «свободное место на Poincare не измерено» из прошлого комментария. После 30.08 продуктовый стек уходит с Beget и освобождает 2.58 ГиБ. Остаётся инфра 2.63 ГиБ + `infra-postgres` ~0.2 + наблюдаемость 1.0–1.5 ≈ **4.1 ГБ из 11.96**. Своп рассосётся сам, без единого рубля. ## Что меняется: Grafana ставим на Beget, а не на Selectel Прошлый вывод («стек метрик увезти на Selectel») отменяется замерами. Grafana + Prometheus + Loki встают на Beget рядом с GlitchTip, и это снимает почти все ограничения плагина. **Чтение GlitchTip — прямой SQL-датасорс, а не Sentry-плагин.** Схема годится: `issue_events_issueevent` (RANGE-партиции по `received`; `issue_id`, `organization_id`, `level`, `type`, `tags jsonb`) + `issue_events_issue` (`project_id`, `first_seen`). Готовые агрегаты `IssueIndex` (count, last_seen, status, level) и `IssueAggregate` с `date` в составном PK дают ряды и топ-N без сканов сырья. Правок в GlitchTip ноль, нужен read-only пользователь. #3061 уже вынес эту базу в отдельный кластер, поэтому продуктовый Postgres такой доступ не задевает. Почему не плагин, хотя он официально документирован GlitchTip (`glitchtip.com/documentation/integrations`, обычный read-only Auth Token, Grafana ≥10.4, Cloud не нужен): - `stats_v2` с фильтром по проекту → **500**, открытый баг GlitchTip #381 с 2025-01-10. Per-project панели не заведутся. - Events/Discover → **404**, #416 (open, 2025-08-25). Metrics/Spans/Tags — Sentry-only. - В `grafana/sentry-datasource` **ноль** issue со словом glitchtip: апстрим связку не тестирует и не заявляет неподдержку. В 2.2.6 Issues перевели на lifetime `firstSeen/lastSeen` — при ином сериализаторе GlitchTip панели молча исказятся. - Долгоживущий read-токен на всю организацию, привязанный к пользователю; протухание проявится пустыми панелями, а не ошибкой. Плагин можно оставить вторым, необязательным источником — но нести на нём основные панели не стоит. **Логи: Alloy, не Promtail.** У Promtail EOL **2 марта 2026**, ставить его в 2026 нельзя. Alloy читает journald нативно (`loki.source.journal`, дефолт `/var/log/journal` и `/run/log/journal`). Две грабли: пользователь `alloy` обязан быть в группах `adm` и `systemd-journal`, иначе тихо ноль записей; в контейнере путь задавать явно (`path="/var/log/journal"`), иначе `SD_JOURNAL_LOCAL_ONLY` скроет хостовые логи — монтировать `/var/log/journal` и `/etc/machine-id`, systemd внутри контейнера не нужен. **Транспорт push-овый — входящих портов на Selectel не открываем.** Alloy на Selectel шлёт логи через `loki.write` и метрики через `prometheus.remote_write` исходящим HTTPS с basic_auth. Приёмнику метрик нужен `--web.enable-remote-write-receiver` (endpoint `/api/v1/write`), приёмники прячем за Caddy с TLS и авторизацией. Наши 39 МБ/сутки против рекомендованного потолка Loki ~20 ГБ/сутки. Топология это позволяет: между хостами нет ни VPN, ни приватной сети, ни туннеля (единственные хиты grep — ручной `ssh -N`); наружу опубликованы только 80/443, firewall Selectel — default deny incoming + SSH/80/443. Push обходит всё это без единого нового правила. Побочный выигрыш: наблюдатель оказывается у другого провайдера, чем наблюдаемое. Упадёт Selectel — Grafana на Beget жива и покажет ровно это. ## Скоуп задачи - Вариант 3 переписать: **Grafana + Prometheus + Loki на Beget**, на Selectel только Alloy как push-агент. Прежняя формулировка (стек на Selectel, watchdog на Beget) отменяется замерами. - Основной источник по ошибкам — **PostgreSQL-датасорс** в базу glitchtip. Sentry-плагин — опционально, вторым. - Выкинуть Mimir и Tempo. - Promtail заменить на Alloy во всех упоминаниях. - Убрать из скоупа панели Events/Discover/Metrics/Spans/Tags и per-project `stats_v2`. - Переформулировать цель: не «единое окно», а **обзорный слой + логи**. GlitchTip остаётся рабочим местом триажа: и плагин, и SQL одинаково read-only — assign/resolve/ignore, стектрейсы и breadcrumbs только там. - Алертинг «новый дедуплицированный issue» оставить в GlitchTip. Grafana умеет порог по окну — это другая семантика. Иначе правила расползутся по трём местам (почта GlitchTip, Uptime Kuma, Grafana), и silence в Grafana не заглушит письма. - Добавить алерт на **здоровье датасорса**: пустая панель неотличима от «ошибок нет». - Добавить: `mem_limit` на `forgejo` — он один ест 44% памяти контейнеров и ничем не ограничен. Дешевле и полезнее любого апгрейда тарифа. - Добавить: удалить `ops/glitchtip-auth-forwarder` (485 строк, его `before_send` дропает все 401-события — в трекер он не шлёт ничего). - Зафиксировать `CADDY_SITES` и `COMPOSE_PROFILES` в git или runbook — сейчас задаются руками на хостах. ## Проверить руками до старта 1. `\dt` в базе glitchtip на нашей 6.1.6 — до версии 4.x приложение называлось `issues_*`, имена таблиц и колонок (`received` против `timestamp`) и наличие `IssueIndex`/`IssueAggregate` надо подтвердить глазами. Это несущий факт для SQL-пути. 2. Реальный RSS Loki monolithic и Alloy на нашем профиле — официальных минимумов для monolithic нет, цифры из практики (~512 МБ–1 ГБ и ~100–200 МБ). 3. Фактический ufw на Beget — в репозитории зафиксирован только bootstrap Selectel. 4. `ENABLE_OBSERVABILITY_API` у GlitchTip 6 включает `/metrics` через django-prometheus — но это метрики HTTP/БД/Django, не ошибки в разрезе issue. Годится только для здоровья сервиса. 5. Триажит ли кто-то инбокс фактически: все 50 issue unresolved за два месяца.
Owner

Проверка после разворачивания: части 1–2 собирают данные с обоих хостов

Стек поднялся не с первого раза — понадобились #3105 (поиск привилегированной роли), #3103 (монтирование сниппетов Caddy), #3106 (падение агента из-за переменной чужой роли) и #3109 (cAdvisor 0.55.1). Проверил, что после всего этого он реально собирает, а не просто «зелёный».

Контейнеры: на Beget — Prometheus, Loki, Grafana, Alloy, node-exporter, cAdvisor, pg-exporter-infra. На Poincare — Alloy, node-exporter, cAdvisor, pg-exporter-gendesign, pg-exporter-tradein.

Метрики в Prometheus (агенты пушат через remote_write, поэтому в списке scrape-целей их нет — там только Grafana, Loki и сам Prometheus; данные искал запросом, а не по целям):

job серий
node — оба хоста 2
postgres — все три базы 3
app — оба бэкенда (#3102) 2
integrations/self — сам Alloy 2

Точечно: node_load1 — 2 серии, node_memory_MemAvailable_bytes — 2, pg_up — 3, container_memory_usage_bytes42 (cAdvisor с обоих хостов).

Логи в Loki: метка host имеет оба значения — apps и infra. Набор меток: container, host, job, level, node, service_name, unit.

То есть покрытие полное: хостовые метрики с двух машин, все три базы, оба приложения, контейнеры и логи.

Что остаётся не закрытым

Alertmanager не поднят — профиль alerts включается только когда заданы METRICS_TELEGRAM_BOT_TOKEN и METRICS_TELEGRAM_CHAT_ID, а они не заданы. Это осознанный гейт из джобы server, и он правильный: Alertmanager с пустым токеном стартовал бы зелёным и молча никому не слал.

Но практический итог сейчас такой: метрики и логи собираются, а предупреждать некому. Ради этого стек и заводился — правила в ops/metrics/prometheus/rules/infra.yml есть, срабатывать им некуда. Переменные секретные, задать их может только владелец.

Отдельно стоит иметь в виду при смене IP (#3110): агенты отправляют данные на metrics.gendsgn.ru, то есть на Beget по имени — от смены адреса Poincare это не зависит и переживёт замену без правок.

## Проверка после разворачивания: части 1–2 собирают данные с обоих хостов Стек поднялся не с первого раза — понадобились #3105 (поиск привилегированной роли), #3103 (монтирование сниппетов Caddy), #3106 (падение агента из-за переменной чужой роли) и #3109 (cAdvisor 0.55.1). Проверил, что после всего этого он реально собирает, а не просто «зелёный». **Контейнеры:** на Beget — Prometheus, Loki, Grafana, Alloy, node-exporter, cAdvisor, pg-exporter-infra. На Poincare — Alloy, node-exporter, cAdvisor, pg-exporter-gendesign, pg-exporter-tradein. **Метрики в Prometheus** (агенты пушат через `remote_write`, поэтому в списке scrape-целей их нет — там только Grafana, Loki и сам Prometheus; данные искал запросом, а не по целям): | job | серий | |---|---| | `node` — оба хоста | 2 | | `postgres` — все три базы | 3 | | `app` — оба бэкенда (#3102) | 2 | | `integrations/self` — сам Alloy | 2 | Точечно: `node_load1` — 2 серии, `node_memory_MemAvailable_bytes` — 2, `pg_up` — 3, `container_memory_usage_bytes` — **42** (cAdvisor с обоих хостов). **Логи в Loki:** метка `host` имеет оба значения — `apps` и `infra`. Набор меток: `container`, `host`, `job`, `level`, `node`, `service_name`, `unit`. То есть покрытие полное: хостовые метрики с двух машин, все три базы, оба приложения, контейнеры и логи. ### Что остаётся не закрытым **Alertmanager не поднят** — профиль `alerts` включается только когда заданы `METRICS_TELEGRAM_BOT_TOKEN` и `METRICS_TELEGRAM_CHAT_ID`, а они не заданы. Это осознанный гейт из джобы `server`, и он правильный: Alertmanager с пустым токеном стартовал бы зелёным и молча никому не слал. Но практический итог сейчас такой: **метрики и логи собираются, а предупреждать некому**. Ради этого стек и заводился — правила в `ops/metrics/prometheus/rules/infra.yml` есть, срабатывать им некуда. Переменные секретные, задать их может только владелец. Отдельно стоит иметь в виду при смене IP (#3110): агенты отправляют данные на `metrics.gendsgn.ru`, то есть на Beget по имени — от смены адреса Poincare это не зависит и переживёт замену без правок.
Author
Collaborator

Перепроверка 27.08.2026 (после пересборки базы 25-26.08 и переезда продукта на Poincare). Тело тикета писалось 20-24.08 по старой инфраструктуре.

Несущая посылка тела опровергнута: «метрик нет ни одной, /metrics не существует ни в одном бэкенде» — сегодня неверно.

Замерено:

  • /metrics есть в обоих бэкендах (metrics.py); Prometheus видит up: app 2, node 2, postgres 3;
  • правил алертинга 16 в 4 группах (/api/v1/rules);
  • Alertmanager принимает и доставляет: сегодня закрыты #3155 (Prometheus не видел ни одного приёмника — 1568 уведомлений в никуда), #3161 (Alertmanager отдавал 404 на /api/v2/alerts из-за префикса external-url), #3163 (инфра-алерты и клиентские инциденты делили одну тему форума). Доставку подтвердил владелец.

Что осталось в тикете: дашборд «Скрапперы» и бизнес-серии (scrape_run / freshness / success_ratio), дашборды Postgres и хоста, job cadvisor не виден в up.

Что выбросить из тела: замер baseline latency/RPS/CPU «до и после переезда» — окно прошло, точка «до» упущена безвозвратно.

**Перепроверка 27.08.2026** (после пересборки базы 25-26.08 и переезда продукта на Poincare). Тело тикета писалось 20-24.08 по старой инфраструктуре. Несущая посылка тела **опровергнута**: «метрик нет ни одной, `/metrics` не существует ни в одном бэкенде» — сегодня неверно. Замерено: - `/metrics` есть в обоих бэкендах (`metrics.py`); Prometheus видит `up`: app 2, node 2, postgres 3; - правил алертинга **16** в 4 группах (`/api/v1/rules`); - Alertmanager принимает и доставляет: сегодня закрыты #3155 (Prometheus не видел ни одного приёмника — 1568 уведомлений в никуда), #3161 (Alertmanager отдавал 404 на `/api/v2/alerts` из-за префикса external-url), #3163 (инфра-алерты и клиентские инциденты делили одну тему форума). Доставку подтвердил владелец. **Что осталось в тикете:** дашборд «Скрапперы» и бизнес-серии (`scrape_run` / freshness / success_ratio), дашборды Postgres и хоста, job `cadvisor` не виден в `up`. **Что выбросить из тела:** замер baseline latency/RPS/CPU «до и после переезда» — окно прошло, точка «до» упущена безвозвратно.
Author
Collaborator

Закрываю по ревизии 01.09.2026: посылка «метрик нет» опровергнута перепроверкой 27.08 — /metrics в обоих бэкендах, Prometheus up (app 2, node 2, pg 3), 16 правил алертинга, Alertmanager доставляет. Открытые вопросы стека вынесены отдельно: #3158 (нужна ли Grafana-алертика при живом Prometheus), #3157 (наблюдаемость доставки GlitchTip).

Закрываю по ревизии 01.09.2026: посылка «метрик нет» опровергнута перепроверкой 27.08 — /metrics в обоих бэкендах, Prometheus up (app 2, node 2, pg 3), 16 правил алертинга, Alertmanager доставляет. Открытые вопросы стека вынесены отдельно: #3158 (нужна ли Grafana-алертика при живом Prometheus), #3157 (наблюдаемость доставки GlitchTip).
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#3078
No description provided.