Skip to content

feat(observability): per-request Server-Timing — auth/db/hooks/serialize spans (perf-tuning mode) - #3107

Merged
os-zhuang merged 1 commit into
mainfrom
claude/github-issue-2408-m2r3wo
Jul 17, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/github-issue-2408-m2r3wo

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Completes #2408.

What

Turns the opt-in Server-Timing header (perf-tuning mode) into a real per-request breakdown, so an operator can open DevTools → Network → Timing and see exactly where a request's server time went — no external tracing backend required.

Before, only total / parse / handler were emitted. A data request now also carries:

span recorded by meaning
auth dispatcher identity / session resolution (the prime suspect for unexplained data-API overhead)
db SQL driver total SQL time; desc is the query count (db;dur=210;desc="6 queries")
hooks ObjectQL engine total business-hook time with a hook count
serialize HTTP adapter response JSON encoding

Example:

Server-Timing: parse;dur=0.4, auth;dur=42, db;dur=210;desc="6 queries", hooks;dur=18;desc="3 hooks", serialize;dur=7, handler;dur=280, total;dur=355

How

  • Collector — PerfTiming.count() / countServerTiming(name, dur, unit) fold high-frequency events into one aggregate member (name;dur=<sum>;desc="<count> <unit>"), so per-query / per-hook timing doesn't flood the header.
  • db — SqlDriver wires knex query / query-response / query-error (keyed by __knexQueryUid) and attributes each query to the originating request via AsyncLocalStorage — correct under concurrency, never cross-attributing. SQL text is never emitted, only durations + a count.
  • auth — the dispatcher wraps identity/session resolution (both the REST and GraphQL paths).
  • hooks — fed through the engine's existing HookMetricsRecorder seam, wired from the runtime (AppPlugin) so @objectstack/objectql's lean core tier (ADR-0076) stays observability-free.
  • serialize — the HTTP adapter times c.json().

Bug fix (surfaced by the end-to-end test)

The ambient AsyncLocalStorage store was a plain module-level const. Because @objectstack/observability ships both an ESM and a CJS build (and may be inlined by a bundler), consumers could load separate copies, each with its own store — so a request scope opened by the HTTP server was invisible to the SQL driver / engine and the cross-layer spans silently never recorded. The store is now pinned to a Symbol.for(...) global-registry key so every copy shares one. A real-socket integration test now guards this.

Gating / overhead

  • Off by default; enabled via serverTiming: true on HonoServerPlugin or OS_SERVER_TIMING=true (satisfies the AC's "or env flag").
  • Every span is a no-op when off (a single AsyncLocalStorage lookup), so there is zero measurable overhead on the normal path.
  • Only durations and counts are exposed; SQL text is never surfaced, so the header is safe for non-admins. The optional admin-only richer JSON / X-OS-Debug-Timing per-request path from the issue is intentionally out of scope — the env-flag gate already meets the acceptance criteria.

Acceptance criteria

  • Data response carries Server-Timing decomposing total into ≥ auth, db (with query count), hooks, serialize
  • Per-query SQL timing attributed to the originating request (ALS, concurrency-safe)
  • Zero measurable overhead when off
  • Gated (env flag / option); SQL text never exposed to non-admins
  • Unit test (collector) + integration test (header on a real find, reflecting query count)

Tests

  • observability: 58 pass (collector aggregate, ambient scope, global-store invariant)
  • driver-sql: 279 pass (incl. new db-span concurrency/attribution test)
  • plugin-hono-server: 72 pass (incl. real serialize span + aggregate-through-header)
  • runtime: app-plugin + dispatcher suites (284) pass; new real-HTTP server-timing.integration.test.ts proves db;desc="N queries" end-to-end over a socket
  • full workspace pnpm build: 71/71 green

🤖 Generated with Claude Code

https://claude.ai/code/session_01VoJNgMJRhgqm78xF7MxFhX


Generated by Claude Code

…lize spans (perf-tuning mode)

Completes #2408. The opt-in Server-Timing header now breaks a request's server
time into the phases that actually explain it, so an operator can read the
breakdown in DevTools -> Network -> Timing without standing up an external
tracing backend.

- collector: PerfTiming.count() / countServerTiming() fold high-frequency
  events (per query, per hook) into one aggregate member
  (name;dur=<sum>;desc="<count> <unit>").
- db: SqlDriver wires knex query / query-response events (keyed by
  __knexQueryUid), attributing per-query time to the originating request via
  AsyncLocalStorage (correct under concurrency; SQL text is never emitted).
- auth: the dispatcher times identity/session resolution.
- hooks: fed through the engine's existing HookMetricsRecorder seam, wired from
  the runtime so objectql's lean core tier stays observability-free.
- serialize: the HTTP adapter times response JSON encoding.

Fix a latent dual-instance bug found by a real end-to-end HTTP test: the ambient
AsyncLocalStorage store was a plain module-level const, so the ESM and CJS builds
(or an inlined bundle) each got their own store. A request scope opened by the
HTTP server was then invisible to the SQL driver / engine, silently dropping the
cross-layer spans. The store is now pinned to a global-registry symbol so every
copy shares one.

Every span is a no-op when perf-tuning is off (serverTiming / OS_SERVER_TIMING),
so there is zero measurable overhead on the normal path. Gating is unchanged
(env flag / option); only durations and counts are exposed, never SQL text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VoJNgMJRhgqm78xF7MxFhX
@vercel

vercel Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 17, 2026 9:30am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tests tooling size/l labels Jul 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/observability, @objectstack/driver-sql, @objectstack/plugin-hono-server, @objectstack/runtime.

19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-sql, @objectstack/runtime)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-sql)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/permissions/authentication.mdx (via @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/plugins/anatomy.mdx (via @objectstack/driver-sql)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-hono-server)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-sql, @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/driver-sql, @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/driver-sql, @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx (via @objectstack/observability, @objectstack/driver-sql, @objectstack/plugin-hono-server, @objectstack/runtime)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

This branch was successfully deployed

1 active deployment
Preview — 7623d7d8 Deployed Jul 17, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants