# Observability — Read-Model / Projector (RFC-003 Phase 0) Grafana dashboards and wiring notes for the read-model observability stack introduced in [GOO-192](/GOO/issues/GOO-192) under [GOO-94](/GOO/issues/GOO-94) §6 Phase 0. ## Metrics All metrics live in the existing NestJS `metrics/` module (`apps/api/src/modules/metrics/`) and are scraped via the standard `/metrics` endpoint. | Metric | Type | Labels | Purpose | | --------------------------------------- | --------- | --------- | --------------------------------------------------------- | | `read_model_projector_lag_seconds` | Gauge | `handler` | Seconds between latest source event and projector cursor. | | `read_model_refresh_duration_seconds` | Histogram | `view` | Duration of read-model / materialised view refreshes. | | `read_model_reconciliation_drift_total` | Counter | `model` | Count of drift discrepancies found during reconciliation. | ### Emit points Inject `MetricsService` and call: ```ts metrics.setProjectorLag(handler, lagSeconds); metrics.recordReadModelRefresh(view, durationSeconds); metrics.recordReconciliationDrift(model, count?); ``` ## Dashboard - File: `read-models-dashboard.json` (Grafana schema v38). - Import into Grafana (`Dashboards → Import → Upload JSON`), pick the Prometheus data source. - Variables: `handler`, `view`, `model` — derived from Prometheus label values. - Panels: 1. Projector lag by handler (time series + thresholded) 2. Max projector lag (stat, RAG 30s / 120s) 3. Refresh duration p50/p95 by view 4. Refresh throughput (refreshes/sec) by view 5. Reconciliation drift rate by model (15m rate) 6. Total drift events in last 24h (stat, RAG 1 / 10) ## Local verification ```bash pnpm --filter @goodgo/api dev curl -s http://localhost:3001/metrics | grep read_model_ ``` All three metric families should appear with `# HELP` / `# TYPE` headers even before any samples are recorded.