0013. Базовые метрики приложения (/metrics) на едином реестре prom-client
Копия ADR из репозитория:
backend/docs/adr/0013-app-metrics-prometheus.md. Канонический текст — в репозитории; портал обновляется командойnpm run sync:adr.
- Статус: accepted
- Дата: 2026-06-23
- Связанные задачи: OP-46 (F6 · наблюдаемость)
Контекст и проблема
ТЗ 9.6/9.8 требует экспорт базовых метрик приложения в формате Prometheus для
наблюдаемости и будущих алертов (стек мониторинга — в F8/DevOps). OP-46 явно
называет инструмент prom-client/@willsoto/nestjs-prometheus и набор метрик:
HTTP, БД-пул, очереди, WB-квоты.
Ключевое ограничение из уже сделанного: эндпоинт /metrics уже существует —
QueueMetricsController (OP-43) отдаёт метрики очередей BullMQ в собранном
вручную exposition-формате (без prom-client). Авторы OP-43 явно оставили шов
под OP-46 (комментарий в контроллере: «метрики добавятся… либо контроллер
расширится через DI дополнительных сборщиков»). Два контроллера на одном пути
/metrics невозможны — нужно решение об интеграции.
Дополнительные опоры/ограничения:
- Контракт OpenAPI (
test/openapi.e2e-spec.ts) перечисляет/metricsсреди обязательных путей → эндпоинт должен оставаться Swagger-документированным. - e2e мокает
DataSource/Redis/очереди → сборщики метрик должны быть defensive. - Префикс
optima_*— соглашение проекта (OP-43) дляkeep-правила на scraper.
Рассмотренные варианты
- Единый реестр prom-client (выбран).
@willsoto/nestjs-prometheusвладеет/metrics(через кастомный документированный контроллер), метрики очередей OP-43 переносятся в общийRegistryкак gauges сcollect()на scrape.- Плюсы: один источник истины, идиоматичный prom-client (default process-метрики, гистограммы HTTP «из коробки»), соответствует формулировке тикета.
- Минусы: переписывается отгруженный код OP-43 (контроллер +
renderPrometheus); текст queue-метрик теперь рендерит prom-client (имена/значения сохранены).
- prom-client рядом, OP-43 как есть. Новые метрики — через prom-client, но
контроллер OP-43 склеивает свой текст с
register.metrics().- Минусы: два механизма метрик сосуществуют; хрупкая склейка двух форматов.
- Только hand-rolled (без новой зависимости): расширить существующий сервис
коллекторами HTTP/БД/WB вручную.
- Минусы: ручные гистограммы HTTP и process-метрики — много кода и риск ошибок; расходится с формулировкой тикета.
Решение принято человеком (Pavel, 2026-06-23) — вариант 1.
Решение
MetricsModule (src/core/metrics/) поверх @willsoto/nestjs-prometheus:
- Эндпоинт.
PrometheusModule.register({ controller: MetricsController })— кастомныйMetricsController extends PrometheusControllerс Swagger-декораторами. Путь/metricsбиблиотека проставляет на класс сама (Reflect.defineMetadata('path','/metrics',…)), поэтому метод —@Get()без сегмента. Content-Typetext/plain; version=0.0.4даёт базовый контроллер. - HTTP. Гистограмма
optima_http_request_duration_seconds(labelsmethod/route/status_code), наполняется глобальнымHttpMetricsInterceptor. Таймер закрывается по событиюfinishответа (послеGlobalExceptionFilter→ корректный статус).route= шаблон маршрута (req.route.path) для ограничения кардинальности; вне роутов —unmatched. - Очереди.
QueueMetricsCollectorрегистрируетoptima_queue_jobsиoptima_queue_job_duration_ms(имена сохранены из OP-43) с async-collect(), дёргающимQueueMetricsService.collectSnapshots().renderPrometheus()удалён. - Пул БД.
DbPoolMetricsCollector→optima_db_pool_connections{state}изDataSource.driver.master(pg Pool), defensive (нет пула → нет series). - default process-метрики prom-client включены в dev/prod, выключены под
NODE_ENV=test(их event-loop monitor держит активный handle, а в jest нет--forceExit).
Последствия
/metricsтеперь единый: default + HTTP + очереди + пул БД в одном реестре.- Добавлены зависимости
@willsoto/nestjs-prometheus,prom-client. - Контракт OpenAPI сохранён (
/metricsостаётся документированным). - WB-квоты — отложены (follow-up). Текущий
WbQuotaServiceработает per-account (getQuotaForAccount(accountId)); для глобальной метрики нужен cross-account scan ключейwb:rl:*или агрегатор по кабинетам — заводится отдельным тикетом, чтобы не раздувать scope OP-46 (AC покрывают HTTP/очереди). - Префикс default-метрик.
process_*/nodejs_*оставлены без префиксаoptima_— это конвенциональные имена node-exporter дашбордов;keep-правило на scraper при необходимости расширяется доoptima_*|process_*|nodejs_*.