Skip to content

perf(debug-trace-server): serve cache hits as raw JSON bytes - #167

Open
flyq wants to merge 3 commits into
mainfrom
liquan/perf/raw-json-cache-hits
Open

perf(debug-trace-server): serve cache hits as raw JSON bytes#167
flyq wants to merge 3 commits into
mainfrom
liquan/perf/raw-json-cache-hits

Conversation

@flyq

@flyq flyq commented Aug 1, 2026

Copy link
Copy Markdown
Member

Summary

Handlers now return RawJson (Arc<RawValue>, response_cache.rs:299) instead of serde_json::Value: the trace output is serialized exactly once (to_raw_value, rpc_service.rs:395), the response-size metric reads the byte length, the response cache shares the same bytes by Arc, and jsonrpsee splices them verbatim into the reply. A cache hit becomes an Arc clone — zero parsing, zero copies, zero re-serialization.

Root cause

The cache stored pre-serialized strings but its interface to jsonrpsee was serde_json::Value, so both sides of the cache paid full tree work: a hit re-parsed the stored string into a Value and jsonrpsee re-serialized it (measured ≈7 ms CPU per MB of response — a ~20 MB block-level callTracer hit burns ~140 ms server CPU, see the baseline below; 93.7% of production traffic is repeat-after-success, i.e. exactly this path); the fill path serialized the same response three times (value.to_string().len() for the size metric, CachedResponse::new, jsonrpsee).

Measured baseline (v2.0.15 without this PR, 2026-08-01)

Bench on the tko staging deploy (response cache enabled), 51 load-test-era blocks 6907000–6907050 — 36.7k–44.1k tx each (median 41,779), callTracer responses avg 20.0 MB, 1020 MB per round:

round status total p50 / p90 / max server CPU p50 (x-execution-time-ns)
1 (cold) 51/51 OK 1244 / 1527 / 1760 ms 721 ms (incl. ~337 ms EVM replay)
2–3 (cache hits) 51/51 OK 168–188 / 305–402 / 523–803 ms 140 / 135 ms

The warm-round ~140 ms CPU per hit is pure Value re-parse + re-serialization of the stored 20 MB string — the work this PR deletes; with RawJson the hit path should drop to transfer cost only. Raw data: validator-data/debug_trace_server/bench/new/monster51_files_tko2.tar.gz, analysis in bench/old-vs-new-report-2026-08-01.md §7.

Fix

  • RawJson in response_cache.rs: Serialize splices the stored bytes verbatim (:338); the cache stores it directly as its value type (get/insert at :483/:512), deleting CachedResponse.
  • compute_block_trace produces RawJson via serde_json::value::to_raw_value — one serialization, no validation re-scan of our own output; the size metric reads byte_len() (rpc_service.rs:395).
  • Both tx handlers (debug_traceTransaction, trace_transaction) drop their to_value + to_string double serialization; parity not-found returns RawJson::null().
  • insert_cache loses its slow-insert timing — the insert is now an Arc clone with nothing left to time.
  • debug_getCacheStatus keeps serde_json::Value (tiny payload, not cached).

Testing

  • raw_json_serializes_verbatim (response_cache.rs:985) pins the splice property the design rests on.
  • raw_json_passes_through_jsonrpsee_verbatim (rpc_service.rs:914) proves it end-to-end through a real RpcModule reply.
  • shares_bytes_with assertions pin that hits serve the inserted bytes without copying (rpc_service.rs:905, cache insert/get test).
  • Full workspace: 20/20 test targets green; fmt/clippy/cargo-sort clean.

Notes

  • Fill-path win (3 serializations → 1) applies immediately; the hit-path win shows up once the response cache is enabled in prod (currently disabled via ESTIMATED_ITEMS=0). Verify post-deploy via debug_trace_cpu_time / hit p50 against the baseline table above.
  • serde_json gains the raw_value feature in this bin only.
  • Wire format is byte-identical for fresh computations; cached replies now return the exact original bytes instead of a re-serialization of them.

🤖 Generated with Claude Code

Responses were serialized three times on the fill path (once for the response-size metric, once into the cache, once by jsonrpsee) and a cache hit paid a full tree re-parse plus re-serialization of multi-MB JSON. Handlers now return RawJson (Arc<RawValue>): the trace output is serialized exactly once via to_raw_value, the response-size metric reads the byte length, the cache shares the same bytes by Arc, and jsonrpsee splices them verbatim into the reply — a hit is an Arc clone with zero parsing, zero copies.

CachedResponse is deleted (the cache stores RawJson directly); insert_cache loses its serialization timing (nothing left to time). trace_transaction / trace_parity_transaction also drop their double serialization. Pinned by raw_json_serializes_verbatim, shares_bytes_with assertions on the hit paths, and an end-to-end raw_json_passes_through_jsonrpsee_verbatim through a real RpcModule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mega-maxwell

mega-maxwell Bot commented Aug 1, 2026

Copy link
Copy Markdown

Claude review status

Living comment — rewritten in place. The review workflow keeps this single comment up to date instead of posting a new one each round, so it always describes the latest reviewed commit and the earlier text is intentionally gone. No reply is needed here; reply to a finding in its own review thread, and answer an open question in a reply on this PR. The next review round reconciles your answer.

🛠️ Review did not finish

Attempted head a7faf080 · updated 2026-08-01T14:08:01+00:00

This round did not publish: MODEL_ACTION_FAILED in phase review_retry. Anything listed below is from the last round that did. Re-run the workflow or push a new commit to try again.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 084702ee16

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread bin/debug-trace-server/src/response_cache.rs Outdated
flyq and others added 2 commits August 1, 2026 21:48
…e explicitly

Address the Codex review note on the Serialize impl: spell out the
deref to the RawValue instead of leaning on method-resolution
auto-deref, which also keeps the impl independent of whether serde's
optional `rc` feature gets unified into the graph.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…module

/simplify pass over the PR:
- move RawJson to its own leaf module — the uncached tx handlers no longer
  import their reply type from the response-cache module
- store Arc<Box<RawValue>>: adopting the serializer's allocation removes the
  full-body memcpy that constructing an Arc<RawValue> (unsized pointee) paid
  on every fill, making the "nothing is copied" doc claims actually true
- RawJson::try_new replaces the to_raw_value+into incantation at all three
  fill sites and the test-only from_value twin; From<Box<RawValue>> deleted
- null() clones a static built from RawValue::NULL instead of allocating and
  validation-parsing "null" per call
- serialize_reply unifies the two tx handlers' byte-identical
  serialize + response-size-metric epilogues

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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