Mark Phase 5 done

RED per service, the service map and exemplar click-through are verified
live. Build step 3's second metrics path and step 6's TraceQL test
migration are not done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Movses Aslanyan
2026-10-25 02:04:54 +03:30
co-authored by Claude Opus 5.5
parent c2bd962597
commit b50f8c3b24
2 changed files with 47 additions and 4 deletions
+44 -2
View File
@@ -2,9 +2,51 @@
*Part of `docs/roadmap/` — see [`intro.md`](intro.md) for platform/background context and [`summary.md`](summary.md) for how every phase fits together. Prev: [`phase-4.md`](phase-4.md) · Next: [`phase-6.md`](phase-6.md)* *Part of `docs/roadmap/` — see [`intro.md`](intro.md) for platform/background context and [`summary.md`](summary.md) for how every phase fits together. Prev: [`phase-4.md`](phase-4.md) · Next: [`phase-6.md`](phase-6.md)*
**Status:** In progress **Status:** Done
**Message:** Started 2026-10-13. **Message:** Done 2026-10-25. All three Done-when criteria verified live: RED per
service and the service map (7 edges, including the two stream edges
gateway → notifier and gateway → audit) from Tempo's span metrics, and a Grafana
exemplar click that opens the trace.
- **Build steps done:** 1, 2, 3 (first path only), 4 and 5. Not done: 3's second
path and 6 (see below).
- **Metrics path:** each service exposes OTel metrics through
`PrometheusMetricReader` on its own `:9464` (`common/telemetry.py`), not an OTLP
exporter, because Alloy 404s OTLP metrics. Prometheus's `ledgerline` job scrapes
them through czy's Traefik at `https://ledgerline.vione.cloud/metrics/<service>`
behind basic auth.
- **Custom metrics:** the five from Build step 2, in `services/*/app.py` and
`common/metrics.py`. `ledgerline.db.pool.in_use` is labelled `pool`
(`normal`/`starved`/`default`), not `service`; the scrape adds `service`.
`currency` outside EUR/USD/GBP is recorded as `other`.
- **Cardinality experiment (Build step 4, done):** `account_id` on the transaction counter turned 75
transactions into 68 series across 60 accounts, and `account_id` entered the
TSDB Status top-10. After the revert (`d54ea2b`) the series went stale, but head
series stayed up until compaction.
- **Hand-written vs span-metrics histogram** (same 75 requests): counts equal,
`_sum` 400.85 s vs 401.39 s (the span adds ~7 ms per request of middleware and
serialization). p95 was 9.5 s vs 14.6 s. The gap comes only from Tempo's wide
8.192–16.384 s bucket.
- **Exemplar fix:** `trace_id_label_name: trace_id` in `k8s/tempo/tempo.yml`.
Tempo's code default is `traceID`; its docs wrongly say `trace_id`.
- **Not done:** Build step 3's second metrics path and its write-up (a Phase 10
artefact), and Build step 6 (migrating the call-counting tests to TraceQL).
- **Carry forward:**
- RED must count `SPAN_KIND_SERVER|SPAN_KIND_CONSUMER`. Notifier and audit only
have CONSUMER spans.
- An error-ratio query needs `(errors or total * 0) / total`, or services with
zero errors drop out.
- For gateway's latency tail, use `ledgerline_transaction_duration_seconds`.
The span-metrics p95/p99 there is an interpolation artifact.
- The Python SDK has no cardinality limit, and the Python Prometheus exporter
emits no exemplars, so only Tempo's span metrics carry them.
- Built-in HTTP histograms are most of the app's series. The body-size ones
(~⅓) can be dropped with a View or `metric_relabel_configs` if needed.
- Exemplars stored before 2026-10-24 19:10 UTC are labelled `traceID` and don't
link. Exemplar trace IDs drop leading zeros.
- Tempo stops sending idle service-graph and span-metric series after a few
minutes, so "now" queries come back empty without traffic.
**Time-box:** 2 evenings. **Time-box:** 2 evenings.
+3 -2
View File
@@ -20,7 +20,7 @@ warnings), **Time-box**, **Concepts**, **Resources**, **Build**, **Done when**.
| 2 | [`phase-2.md`](phase-2.md) | Done | Concepts + first trace | 1 evening | One trace in Tempo | | 2 | [`phase-2.md`](phase-2.md) | Done | Concepts + first trace | 1 evening | One trace in Tempo |
| 3 | [`phase-3.md`](phase-3.md) | Done | Distributed tracing | 2–3 evenings | Connected trace across 7 services incl. the async hop | | 3 | [`phase-3.md`](phase-3.md) | Done | Distributed tracing | 2–3 evenings | Connected trace across 7 services incl. the async hop |
| 4 | [`phase-4.md`](phase-4.md) | Done | Structured logs | 1–2 evenings | Two-way trace↔log correlation | | 4 | [`phase-4.md`](phase-4.md) | Done | Structured logs | 1–2 evenings | Two-way trace↔log correlation |
| 5 | [`phase-5.md`](phase-5.md) | In progress | Metrics | 2 evenings | RED per service, service map, exemplars | | 5 | [`phase-5.md`](phase-5.md) | Done | Metrics | 2 evenings | RED per service, service map, exemplars |
| 6 | [`phase-6.md`](phase-6.md) | Not started | Dashboards as code | 2 evenings | `kubectl apply -k k8s/` restores all dashboards | | 6 | [`phase-6.md`](phase-6.md) | Not started | Dashboards as code | 2 evenings | `kubectl apply -k k8s/` restores all dashboards |
| 7 | [`phase-7.md`](phase-7.md) | Not started | k6 + percentiles | 1–2 evenings | Baseline vs degraded load profiles | | 7 | [`phase-7.md`](phase-7.md) | Not started | k6 + percentiles | 1–2 evenings | Baseline vs degraded load profiles |
| 8 | [`phase-8.md`](phase-8.md) | Not started | SLO / error budget / RTO / RPO | 1–2 evenings | `docs/SLO.md` + burn-rate alerts | | 8 | [`phase-8.md`](phase-8.md) | Not started | SLO / error budget / RTO / RPO | 1–2 evenings | `docs/SLO.md` + burn-rate alerts |
@@ -52,7 +52,8 @@ warnings), **Time-box**, **Concepts**, **Resources**, **Build**, **Done when**.
- **5 — Metrics.** Instrument RED metrics per service, understand cardinality - **5 — Metrics.** Instrument RED metrics per service, understand cardinality
limits, and route app metrics to Prometheus given Alloy rejects OTLP metrics limits, and route app metrics to Prometheus given Alloy rejects OTLP metrics
(404 on `/v1/metrics`). Tempo's metrics-generator already produces span metrics and the (404 on `/v1/metrics`). Tempo's metrics-generator already produces span metrics and the
service graph — don't duplicate it in the collector. service graph — don't duplicate it in the collector. **Done** — see
`phase-5.md` (second metrics path and the TraceQL test migration not done).
- **6 — Dashboards and alerts as code.** Generate dashboard JSON with the - **6 — Dashboards and alerts as code.** Generate dashboard JSON with the
Grafana Foundation SDK and ship it with the platform (`k8s/grafana/`), so Grafana Foundation SDK and ship it with the platform (`k8s/grafana/`), so
"destroy and recreate in seconds" is a real, repeatable command. "destroy and recreate in seconds" is a real, repeatable command.