Skip to content

Runtime metrics for generated executors (design doc 18) - #22

Merged
moznion merged 8 commits into
mainfrom
runtime-metrics
Aug 19, 2026
Merged

Runtime metrics for generated executors (design doc 18)#22
moznion merged 8 commits into
mainfrom
runtime-metrics

Conversation

@moznion

@moznion moznion commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Implements design doc 18 — runtime metrics for generated executors (included as the first commit, D1–D8 settled with all recommendations adopted): operators can now answer, from a metrics endpoint, how the composed-SQL cache performs, which shapes traffic actually uses vs. what was verified, how long calls take, and what gets refused before any SQL is sent.

Layers

  1. runtime/ — neutral observation surface (no new dependencies)

    • Observer interface: ObserveCompose (from the cache, hit/miss + the retained ShapeKey), ObserveExec (ctx, canonical key encoding, duration, rows, error), ObserveReject (the sentinel refusals).
    • ComposedCache.Stats() (hits/misses/inserts/evictions/entries/capacity/SQLBytes) and TopShapes(n) — scrape-time snapshots under the mutex, deterministic ordering.
    • ShapeSpaceInfo — the shared struct behind the generated registry.
  2. codegen — generated packages wire it up

    • Queries.SetObserver (travels through WithTx, shared with the cache) + Cache() accessor.
    • Every method reports rejects on its validation branches and one exec event per DB call (guarded clock; per-annotation row counts; @filter-tree routes through observeExecTree so the call site never encodes the tree twice).
    • db.gen.go carries a ShapeSpace registry computed from shape.Count at generate time: enumerable count (uint64-saturating, Exact flag) + Unbounded flag (@filter-tree everywhere; @in arity on expanding dialects). Examples regenerated for all three dialects.
  3. contrib/otel — OpenTelemetry adapter, separate go.mod

    • Package otelsqletch: the doc-18 §6 instrument table, per-event series labeled by query/instance only, bounded exact used-shape tracking with a loud saturated flip (D5), scrape callbacks over Stats/TopShapes/ShapeSpace, and a TraceObserver decorator that puts full-cardinality shape keys where they belong — on spans, never on metric labels (§7, asserted negatively in tests).
    • OpenMetrics/Prometheus exposition comes from the standard OTel Prometheus exporter; the core module never sees the OTel graph.

Performance findings (measured, pinned in doc 18 §4/D4 and by benchmarks)

  • The ungated per-entry hit counter cost the parallel hit path ~2.5× (12.8→34 ns/op) — the D4 escalation clause fired: counting now gates behind a sticky flag set at first SetObserver/Stats/TopShapes. Unobserved traffic measures at baseline (12.0–12.3 ns/op, 1 alloc); BenchmarkGeneratedCallParallelObserved pins the metrics-on worst case (~130 ns/op).
  • Passing the caller's ShapeKey through the observer interface heap-allocated every call site's key slices (+1 alloc/op, observed or not). Fix: the cache passes its retained key (proven identical by keysEqual), and ObserveExec takes the canonical encoding, built inside the observer guard.

Testing

  • runtime/: scripted CLOCK-eviction stats pins, hits-survive-eviction fold, all three serve paths' hit bits, concurrent same-shape coherence, allocation-free hit path with and without an observer (AllocsPerRun), stats-under-churn cross-checks, race-detector scrape-during-churn.
  • codegen: observer-site and ShapeSpace golden assertions incl. uint64 saturation via a 17-key @order-by; TestComposeConformance passes unmodified.
  • contrib/otel: SDK manual-reader tests for every instrument, the no-shape-key-label rule, saturation flip, TraceObserver span attributes/events/forwarding.
  • E2E (devdb, all three dialects, run locally — full suite green): the generated-module fixtures install a recording observer and pin exec/compose 1:1, deliberate-misuse reject counts, cache-stats coherence, and the ShapeSpace flags.
  • golangci-lint --build-tags devdb, standalone staticcheck (main + contrib), goimports: clean. Fuzz smoke on both targets: pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U8QzqDQ6XJpN3hbD7y8nzi

moznion and others added 8 commits August 9, 2026 15:38
Neutral Observer/Stats surface in runtime/, codegen wiring with a
generate-time ShapeSpace registry, and an OTel adapter in a separate
contrib module (OpenMetrics obtained via the Prometheus exporter).
Decisions D1-D8 settled with all recommendations adopted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U8QzqDQ6XJpN3hbD7y8nzi
Observer (compose/exec/reject events), ComposedCache.SetObserver,
CacheStats/Stats, TopShapes, and ShapeSpaceInfo. Hits are counted on
the entry and folded on eviction so eviction never loses history;
counting is gated behind first observability use after the parallel
hit-path benchmark showed the ungated increment costing ~2.5x, and
observers receive the entry's retained key because threading the
caller's key through the interface call heap-allocated every call
site's key slices. Unobserved traffic measures at baseline;
BenchmarkGeneratedCallParallelObserved pins the enabled cost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U8QzqDQ6XJpN3hbD7y8nzi
Queries gains SetObserver (shared with the cache; WithTx carries it)
and a Cache() accessor for scrape-time Stats/TopShapes. Every method
reports ObserveReject on its validation branches, and ObserveExec
with a guarded clock and per-annotation row counts; @filter-tree exec
events route through observeExecTree so the call site never encodes
the tree twice. db.gen.go carries the generate-time ShapeSpace
registry from shape.Count, saturating to uint64 with explicit Exact
and Unbounded flags. Examples regenerated for all three dialects.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U8QzqDQ6XJpN3hbD7y8nzi
Trace correlation and metric exemplars need the request context, and
only generated code has it — ObserveExec/ObserveReject gain a ctx
first parameter (ObserveCompose stays context-free: the cache API
takes none, and the exec event repeats the key). Examples
regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U8QzqDQ6XJpN3hbD7y8nzi
Package otelsqletch in its own go.mod so the OTel graph never touches
runtime/ or consumers who skip it. Metrics/Bind wire a generated
Queries into the doc-17 instrument table: per-event counters and
histograms labeled by query/instance only, scrape-time callbacks over
Stats/TopShapes/ShapeSpace, bounded exact used-shape tracking with a
loud saturated flag, and rejectName classification via errors.Is.
TraceObserver decorates any observer with span attributes — the home
for full-cardinality shape keys, per the §7 rule the tests assert
negatively.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U8QzqDQ6XJpN3hbD7y8nzi
The three generated-module E2E fixtures install a recording observer
and pin the exec/compose 1:1 relationship, the deliberate-misuse
reject counts, cache-stats coherence, and the ShapeSpace flags
(@filter-tree unbounded everywhere; @in unbounded on the expanding
dialects). Manual gains the 'Runtime metrics' chapter — instrument
table, cardinality policy, and the prepared-statement join recipe —
and CI tests the contrib/otel module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U8QzqDQ6XJpN3hbD7y8nzi
Conflict resolution: generate.go combines the Option[T] scan
temporaries with the doc-17 observation sites, and the new :maybe-one
annotation gains exec observation (no-row branch reports rows 0 with
a nil error — a successful empty execution, not a failure). Examples
regenerated on the merged generator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U8QzqDQ6XJpN3hbD7y8nzi
Design number 17 was taken on main by the go-optional adoption doc;
all references (runtime, codegen templates, contrib/otel, manual,
CI) follow, and the examples are regenerated for the emitted-comment
change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U8QzqDQ6XJpN3hbD7y8nzi
@moznion moznion changed the title Runtime metrics for generated executors (design doc 17) Runtime metrics for generated executors (design doc 18) Aug 9, 2026
@moznion
moznion merged commit 5291be2 into main Aug 19, 2026
5 checks passed
@moznion
moznion deleted the runtime-metrics branch August 20, 2026 00:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant