ADR-0056: Admin connectors KPI — on-demand SQL, без CQRS layer’а
Status: accepted Date: 2026-05-14 Deciders: belkanov, agent-claude
Plan указывал ADR-0051; номер 0051 уже занят (
0051-public-api-auth-and-api-key-rbac.md). Phase 2 ADR получает следующий свободный номер 0056.
Контекст
Admin connectors page (/suppliers в web/admin) требует агрегаций
ingestion_job_runs за 24 часа: top KPI tiles (коннекторов · циклов 24ч ·
ошибок · stale · expired · rate-limited), sidebar per-supplier metrics
(account_count, runs, error_rate, lag, sparkline 24 hourly buckets),
detail KPI (uptime, throughput, top error codes) и per-credential usage.
Объём данных за 24ч ≤ 1500 строк через VIEW admin_connector_runs_24h
(migration 0132): ~24 buckets × ≤10 suppliers × ≤5 credentials × 3
status_class. Это тривиально для on-demand SQL запросов; admin
page открывается оператором ≤10 раз в день, не RPS-нагрузка.
Cabinet Stats (cycle 2026-05-06, ADR не выделен) пошёл по пути
pre-aggregated tables (cabinet_stats_minute/hour/day) + rollup worker.
Это правильно для public-API observability, где per-tenant RPS-level
нагрузка и многомесячные timeseries.
Решение
Admin connectors KPI — thin read-model без CQRS layer’а:
- Один VIEW
admin_connector_runs_24h(migration 0132) группирует runs за 24ч поsupplier_ref/credential_id/bucket_hour/status_class/error_code. VIEW делает rename (supplier→supplier_ref,credential_ref→credential_id) и derivedstatus_class/error_code(см. Schema Errata в plan2026-05-14-admin-connectors-phase2-analytics.md). - Repository
connectorkpi/infra/postgres/repo.goчерезpgxpool.Poolнапрямую. Нет агрегата, нет outbox event’ов, нет materialized state. - Handler
backend/internal/platform/di/connectors_kpi.goсинхронно запрашивает Service → Repository → SQL → DTO. - Кеш на стороне фронта: TanStack staleTime 30s, refetchInterval 60s.
- AdminPathGate (
role=adminJWT) — единственный auth check.
Альтернативы
A. Pre-aggregated tables (как cabinet_stats) — overkill для 24ч окна и ≤10 коннекторов. Rollup worker, миграции hour/day, eventual consistency, тестовая обвязка — все дорого для read pattern «оператор смотрит несколько раз в день».
B. Materialized VIEW + REFRESH — дополнительный operational toil (когда refresh? как реагировать на stale data?) без существенной выгоды при текущем объёме.
C. Prometheus + Grafana iframe в admin UI — потеря интеграции с UI (отдельная auth, отдельный layout), невозможность click-through из supplier row в /customers/{id}, требует отдельного pipeline export.
Последствия
- Phase 2 admin KPI добавляет ровно: один VIEW (миграция 0132) + одну
колонку (migration 0133 для
stale_threshold_seconds) + пакетconnectorkpi+ handler вplatform/di. Никаких новых background-процессов. - Если число коннекторов вырастет >50 или admin начнёт open’ить страницу minute-level (например при alert’ах) — пересматриваем в follow-up cycle (materialized VIEW + REFRESH, или partitioned table). Trigger criteria: p95 query latency > 500ms ИЛИ /suppliers RPS > 10.
- Для public-API style observability (per-tenant analytics) ADR-0056 НЕ применяется — там pre-aggregated подход с rollup worker’ом остаётся authoritative.
- Schedules-admin handler НЕ возвращает
stale_threshold_secondsв payload (поле существует с migration 0133, ноscheduleToAdminJSONне включает его). Phase 3 follow-up — расширить admin payload; до этого ConnectorRulesPanel показывает default 3600s.