feat(server,types): stage-timing observability — queue-wait metric, self-describing completions, identified spans#71
Open
chetanyb wants to merge 3 commits into
Open
Conversation
…to the slot budget
The prove duration histogram starts only after a worker dequeues the job, so
time spent waiting in the worker channel between dispatch and dequeue is
invisible in every metric. Under sustained load that wait dominates: with
arrivals fixed at one block per slot and heavy blocks proving slower than a
slot, multi-minute backlogs were observed in traces as unexplained gaps
inside request_proof (witness fetched at T0, proving starting minutes later).
Add zkboost_queue_wait_duration_seconds{proof_type}, stamped at dispatch
(WorkerInput.queued_at) and observed at dequeue, with long-tail buckets
(0.05s..900s) — backlogs of minutes are exactly what this metric exists to
expose, so a 12s ceiling would defeat it.
Also give zkboost_witness_fetch_duration_seconds the same 0.5s-step bounds
up to 12.0 as the prove histogram: the default buckets end at 10.0, hiding
successful 10-12s fetches in +Inf even though the slot-aligned witness
timeout makes that range meaningful.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…age spans Completion events become self-describing: ProofComplete gains optional witness_ms / queue_wait_ms / prove_ms fields (serde(default), so producers and consumers upgrade in either order — absent fields read as None, meaning unknown rather than zero). The three stages partition the request lifecycle exactly: request admission -> witness available -> worker dequeue -> proof done. Replayed completions from the proof cache send None — the cache holds only proof bytes, so replay timings are honestly unknown. Consumers today must either poll the dashboard state (bounded retention, lost on restart) or subtract span timestamps in a trace backend to attribute stage time; both are lossy reconstructions of numbers the server already holds at completion time. Also stamp identity attributes on the stage spans so each is self-describing in trace UIs: new_payload_request_root on request_proof and prove, block_hash on fetch_witness (the witness service is keyed by execution block hash and serves all requests for that block, so the hash is its identity). Serde-compat tests cover both upgrade directions plus a populated round trip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Failures get the same optional witness_ms / queue_wait_ms / prove_ms fields as completions, with one semantic addition: on failures, None also means the stage never ran for this request — so the populated/absent shape locates how far a request got before dying. A witness-timeout failure carries only witness_ms; a proving-timeout failure carries all three, with prove_ms the exhausted budget; an admission-time rejection carries none. Timings known at each failure site are threaded via a small StageTimings carrier defaulting to all-None. Replayed failures keep their timings for free: the failure cache stores the whole event, unlike the proof cache. Without this, per-request stage data would exist only for successes — while failed requests, the primary subjects of stage-attribution analysis, would be the one population without it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Per-stage timing of a proof request is only partially observable:
zkboost_prove_duration_secondsstarts after a worker dequeues the job — time waiting in the worker channel between dispatch and dequeue appears in no metric. Under sustained load that wait dominates: we observed traces whererequest_proofspanned 5m16s containing only ~6.6s of witness fetch and ~21s of proving, the rest being invisible queue wait./dashboard/state(bounded retention, lost on restart) or subtract span timestamps in a trace backend — both lossy reconstructions of numbers the server already holds at completion time.request_proof,fetch_witness,prove/*) carry no identifying attributes, so span rows in trace UIs can't be related to a block without walking the parent chain.Changes
Commit 1 — metrics:
zkboost_queue_wait_duration_seconds{proof_type}— anInstantstamped intoWorkerInputat dispatch, observed at dequeue. Long-tail buckets (0.05s … 900s): backlogs of minutes are exactly what this metric exists to expose.zkboost_witness_fetch_duration_secondsbucket fix — the default buckets end at 10.0, hiding successful 10–12s fetches in+Infdespite the slot-aligned witness timeout; now the same 0.5s-step bounds to 12.0 as the prove histogram.Commit 2 — self-describing completions + span identity:
ProofCompletegains optionalwitness_ms/queue_wait_ms/prove_ms(Option<u64>+#[serde(default)]). The three stages exactly partition the request lifecycle: admission → witness available → dequeue → done. Absence means unknown, not zero — old producers and replayed cache entries (which hold only proof bytes) sendNone. Producers and consumers can upgrade in either order; serde-compat tests cover both directions plus a populated round trip.new_payload_request_rootonrequest_proofandprove;block_hashonfetch_witness(the witness service is keyed by execution block hash and serves all requests for that block, so the hash is its identity).Commit 3 — the same fields on failures:
ProofFailuregains the same optional trio, with one semantic addition: on failures,Nonealso means the stage never ran, so the populated/absent shape locates how far a request got before dying — a witness-timeout failure carries onlywitness_ms; a proving-timeout carries all three withprove_ms= the exhausted budget; an admission-time rejection carries none. Timings are threaded to each failure site via a small all-None-defaultStageTimingscarrier. Replayed failures keep their timings for free (the failure cache stores the whole event). Without this, stage data would exist only for successes — while failures are the primary subjects of stage-attribution analysis.Notes
deny_unknown_fieldsanywhere on this path).cargo fmt/clippy --all-targets --locked -- -D warnings/cargo test --locked(80 passed) all clean.witness + queue_wait + prove ≈ request_proofcloses arithmetically, per-request stage data arrives with completion and failure events instead of being polled, and every stage span self-identifies in trace UIs.