Decide what bounds the query-name metric label, and what a breach asks for
Problem
Two vectors carry one series per query name: database_query_duration_seconds{name} and database_queries_total{name}.
Two catalogs declare the values that label can take.
On main today, internal/datastore/query_names.go declares 436 names and internal/storage/queries.go declares 12.
The budget for name in internal/metrics/cardinality.go is 450.
The two catalogs grew from 283 names on 2026-08-17 to 448 on 2026-08-25. The budget went up three times inside that window: 200 to 350 (3c174720), 350 to 400 (76e904e5), and 400 to 450 (62b2ed34).
TestNameBudget_CoversEveryDeclaredQueryName was the only check that read the catalogs against the budget.
While !1948 (merged) is open, that test still runs on main.
After it merges, nothing reads the catalogs against the budget.
Why that test did not solve the problem
The test counted declarations, and it offered one remedy: raise the number. Nobody removes a database query, or holds their work, because of this count. The three raises in eight days are the record of that. A number that moves on each breach describes the past. It bounds nothing.
Nothing enforces the budget at run time either.
Every AuditCardinality call site is a test.
The call sites are the audit's own suite in internal/metrics, the per-package metric suites, and the composition-root suite in cmd/artifact-registry.
The count bound reads gathered values, so it sees only the names that a query emitted in that process.
TestWiring_CardinalityAuditPasses drives one request over the live assembly, so it observes a few names, never the catalog.
A catalog past 450 therefore trips nothing in production.
What the number is worth today
Each name costs 15 series across the two vectors.
That is 12 _bucket series (the 11 bounds in dbLatencyBuckets, plus +Inf), then _sum, _count, and the counter.
448 names is about 6,700 series for each process, before replicas.
The declared policy and the code also disagree.
The expected-distinct-values table in docs/specs/S03-b-metrics.md still gives name as "up to ~200".
What this issue asks for
A decision on two questions, and the work that follows from it.
- What does the
namebudget protect: a ceiling on series count, or a tripwire for a dynamic value that no catalog declares? - What action does a breach ask for, other than a larger number?
These are the options as they stand. This issue picks none of them.
| Option | What it catches | What it costs or misses |
|---|---|---|
| A large declaration limit (1000, for example) with the catalog test restored | A jump in the declared names, 552 names from where they are now | It reads source constants, so a dynamic value never reaches it. The remedy is still a larger number |
| An alert on the observed series count, in the monitoring repository | The real series count, dynamic values included | Work outside this repository. It reports after the series exist |
A boot-time AuditCardinality call from the composition root |
A disallowed or untabled label name, at boot instead of in CI | No count coverage. No query has run at boot, so the gather holds no name value |
A coarser name value, or name on the counter alone |
Nothing. It removes most of the cost instead | Per-statement latency is lost |
| A budget derived from an agreed series ceiling | The same breaches as today, against a number with a stated meaning | The ceiling has to be agreed first, and a gate is still needed |
Acceptance criteria
- The decision is written down in the "Cardinality governance" section of
docs/specs/S03-b-metrics.md. - The
namerow of that spec's expected-distinct-values table agrees withinternal/metrics/cardinality.go. - The chosen gate is implemented, or the absence of a gate is recorded with its reason.
- The comment on
labelNameininternal/metrics/cardinality.gonames what holds the budget, anddocs/dev/database-query-patterns.mdagrees with it.
Out of scope
- The budgets of the other labels in
expectedDistinctValues. - Audit coverage for collectors that register outside
appReg. #337 tracks that gap.
Related to !1948 (merged)