This folder contains the full observability stack for the CAFA-5 MLOps services. It is designed to be reproducible and version-controlled (no click-only drift).
- Metrics collection with Prometheus.
- Dashboard provisioning with Grafana.
- Basic operational alerting.
- Dashboard JSON versioning workflow in git.
monitoring/
├── prometheus.yml
├── alerts.yml
└── grafana/
├── dashboards/
│ ├── cafa5-service-health.json
│ └── cafa5-domain-pipelines.json
└── provisioning/
├── datasources/
│ └── prometheus.yml
└── dashboards/
└── providers.yml
- Scrapes only Prometheus-compatible
/metricsendpoints for service availability:prometheusembedding_api_metricsgo_prediction_api_metricstrainer_api_metrics
- Loads alert rules from
alerts.yml.
Why this matters:
- JSON health endpoints are not Prometheus exposition format.
- Using
/metricsjobs forupavoids scrape-format false alerts.
- Prometheus datasource is auto-provisioned:
- UID:
prometheus - URL:
http://prometheus:9090
- UID:
- Dashboards are file-provisioned from
/etc/grafana/dashboards. - Dashboard provider auto-refreshes periodically.
-
cafa5-service-health.json- target up/down
- request rate
- 5xx ratio
- p95 latency
- in-flight requests
-
cafa5-domain-pipelines.json- embedding queue/outcomes/duration/sequence-length signals
- training queue/failure reasons/duration by mode
- inference latency by model version / input validation failures / top_k usage
Current minimal alert set:
-
Cafa5ServiceMetricsTargetDown- Trigger: metrics scrape target down for >2m.
- Goal: detect service unavailability.
-
Cafa5HighHttp5xxRatio- Trigger: sustained high 5xx ratio with minimum traffic.
- Goal: detect user-visible API quality regressions.
-
Cafa5EmbeddingQueueBacklogHigh- Trigger: queued embedding jobs above threshold for >10m.
- Goal: detect pipeline saturation.
From repo root:
make monitoring-upEquivalent:
docker compose --profile monitoring up -ddocker compose psExpected:
prometheusrunninggrafanarunning
curl -s http://127.0.0.1:9090/-/readyExpected: Prometheus is Ready.
Open Prometheus targets page:
Or query:
curl -s "http://127.0.0.1:9090/api/v1/query?query=up"Expected:
up=1forprometheus,embedding_api_metrics,go_prediction_api_metricstrainer_api_metricsis1only when training profile service is running.
curl -s http://127.0.0.1:9090/api/v1/rules
curl -s http://127.0.0.1:9090/api/v1/alerts- URL: http://127.0.0.1:3000
- Default credentials are set via
docker-compose.yml. - Confirm:
- Prometheus datasource exists and is healthy.
CAFA5 Service Healthdashboard loads.CAFA5 Domain Pipelinesdashboard loads.
If you edit monitoring files:
-
Prometheus config/rules:
- soft reload:
curl -X POST http://127.0.0.1:9090/-/reload
- if needed, restart Prometheus container:
docker compose restart prometheus
- soft reload:
-
Grafana provisioning/dashboard JSON:
- provider auto-refresh is enabled.
- if changes do not appear quickly, restart Grafana:
docker compose restart grafana
Use this workflow to keep dashboards merge-friendly and reproducible:
-
Edit source of truth in repo
- Update JSON in
monitoring/grafana/dashboards/*.json. - Prefer editing files directly instead of ad-hoc UI edits.
- Update JSON in
-
If UI edits were made, export and normalize
- Export dashboard JSON from Grafana UI.
- Replace file in
monitoring/grafana/dashboards/. - Keep stable fields:
- set
"id": null - keep fixed
"uid"per dashboard - increment
"version"only when needed
- set
-
Validate dashboard JSON
- Open in Grafana and ensure no panel query errors.
- Confirm variables resolve and panels render with data.
-
Commit with intent
- Commit dashboard changes with a clear message:
- what signal changed
- why threshold/query/panel was updated
- Commit dashboard changes with a clear message:
-
Review checklist before merge
- Queries align with available labels.
- No accidental high-cardinality labels introduced.
- Panel titles/units are explicit.
- Alerts still align with dashboard logic.
Symptoms:
up == 0Cafa5ServiceMetricsTargetDownfiring
Checks:
docker compose ps
docker compose logs --tail=200 embedding-api go-prediction-api trainer-api prometheusAlso verify endpoint from Prometheus container network perspective:
- service name and port are correct (
<service>:8000). /metricsendpoint responds and is not protected by gateway auth internally.
Checks:
- Datasource UID matches dashboard datasource UID (
prometheus). - Time range is not too narrow.
- Prometheus query works directly in Prometheus UI.
- Target labels in panel query match actual labels.
Checks:
- Rule expression in Prometheus graph first.
- Ensure
for:duration is appropriate. - Validate denominator guards (
clamp_min) for ratio expressions. - Confirm traffic floor conditions to avoid low-traffic noise.
Problem:
- Dynamic path segments (job IDs, UUIDs) can explode time-series cardinality.
Current mitigation:
embedding-apinormalizes route labels in middleware (_route_label) before metric labeling.
Recommendation:
- Keep route labels templated/static across services.
- Avoid raw identifiers in labels.
When rolling new model versions or changing inference behavior:
- Confirm inference
model_versionappears in metrics. - Compare p95 latency by
model_version. - Check validation failure reasons for schema drift.
- Watch embedding queue depth for upstream pressure.
- Confirm 5xx ratio remains under threshold.
Service availability:
up{job=~"prometheus|embedding_api_metrics|go_prediction_api_metrics|trainer_api_metrics"}
HTTP 5xx ratio by service:
sum by (service) (rate(cafa5_http_requests_total{status_code=~"5.."}[5m]))
/
clamp_min(sum by (service) (rate(cafa5_http_requests_total[5m])), 0.001)
Embedding queue depth:
cafa5_embedding_queue_jobs{status="queued"}
Inference p95 latency by model version:
histogram_quantile(
0.95,
sum by (le, model_version) (rate(cafa5_inference_duration_seconds_bucket[5m]))
)
make monitoring-down