Skip to content

perf(transcode): remove per-request route allocations on the hot path#42

Merged
polaz merged 1 commit into
mainfrom
perf/#41-hot-path-allocs
Jun 20, 2026
Merged

perf(transcode): remove per-request route allocations on the hot path#42
polaz merged 1 commit into
mainfrom
perf/#41-hot-path-allocs

Conversation

@polaz

@polaz polaz commented Jun 20, 2026

Copy link
Copy Markdown
Member

Summary

Removes two avoidable allocations paid on every proxied request (the hottest path in the proxy).

Changes

  1. Arc<RouteEntry> instead of deep clone. The handler closure captured RouteEntry by value, so axum's per-request clone of the handler deep-cloned its String fields (http_path, grpc_path, response_body). Wrapping the entry in Arc makes the per-request clone a single refcount bump.
  2. Parse the gRPC path once. grpc_path was re-parsed into a PathAndQuery on every request in both the unary and streaming handlers. It's now parsed once at route-build time and stored as PathAndQuery (whose clone is a cheap Bytes refcount bump). An invalid path now skips the route at startup with a log, instead of a per-request 500 branch.

Not done (deliberately)

  • SIMD / wildcopy: not applicable. There's no hot scalar byte loop in our code; byte-level work lives in serde_json / prost / hyper (already optimized), and the proxy is I/O-bound on the upstream gRPC RTT. Adding SIMD to the glue would be premature and wouldn't move P99.
  • The serde_json::Value intermediate in request building could be skipped by constructing the DynamicMessage directly via reflection, but that's a larger refactor with real correctness surface (coercion, oneofs, nested fields) and little payoff on an I/O-bound path. Left as a possible future optimization.

Testing

No behavior change on the success path. cargo nextest run --features redis: 119 passed. clippy (all-features) + fmt clean.

Closes #41

Two avoidable allocations were paid on every proxied request:

- The handler closure captured the RouteEntry by value, so axum's
  per-request clone of the handler deep-cloned its String fields
  (http_path, grpc_path, response_body). Wrap the entry in Arc so the
  per-request clone is a single refcount bump.
- grpc_path was re-parsed into a PathAndQuery on every request in both
  the unary and streaming handlers. Parse it once at route-build time and
  store the PathAndQuery (its clone is a cheap Bytes refcount bump); an
  invalid path now skips the route at startup instead of returning a
  per-request 500.

No behavior change on the success path. SIMD/wildcopy are not applicable
here: the byte-level work lives in serde_json / prost / hyper, and the
proxy is I/O-bound on the upstream gRPC call.

Closes #41
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0c0aa285-e7bc-45ad-8e61-d81a10d81667

📥 Commits

Reviewing files that changed from the base of the PR and between 8489371 and 1aa09ab.

📒 Files selected for processing (1)
  • src/transcode/mod.rs

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • Refactor
    • Improved request handling performance through optimized route resolution.
    • Route configuration errors are now detected at startup rather than during request processing.

Walkthrough

RouteEntry.grpc_path changes from String to a pre-parsed PathAndQuery. Route extraction (extract_routes, extract_streaming_routes) now parses the path once and skips routes with invalid paths. Each RouteEntry is wrapped in Arc before being captured by handler closures, and both transcode_handler and streaming_handler now accept Arc<RouteEntry> and clone the stored PathAndQuery directly.

Changes

Per-request allocation removal on the hot path

Layer / File(s) Summary
RouteEntry struct change and parse-time path validation
src/transcode/mod.rs
grpc_path field type changes from String to PathAndQuery. extract_routes and extract_streaming_routes parse the formatted path into PathAndQuery at route-build time; parse failures log an error and skip the route instead of deferring to a per-request 500.
Arc wiring in routes() and handler signature updates
src/transcode/mod.rs
routes() wraps each primary, alias, and streaming RouteEntry in Arc before closure capture. streaming_handler and transcode_handler parameter types change to Arc<RouteEntry>, and both clone the pre-stored PathAndQuery instead of re-parsing a string.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main optimization: removing per-request route allocations on the critical hot path.
Description check ✅ Passed The description provides a detailed summary of changes including the two main optimizations (Arc wrapping and path parsing), testing results, and deliberate non-changes with justifications.
Linked Issues check ✅ Passed The PR fully addresses both objectives from issue #41: wrapping RouteEntry in Arc to eliminate deep cloning, and parsing grpc_path once at route-build time instead of per-request.
Out of Scope Changes check ✅ Passed All changes are directly related to the two stated optimizations in issue #41; no out-of-scope modifications are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/#41-hot-path-allocs

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown

Greptile Summary

  • Optimizes the REST-to-gRPC transcoding hot path in src/transcode/mod.rs.
  • Stores route entries behind Arc so handler cloning avoids deep-copying route strings per request.
  • Parses generated gRPC paths once during route extraction and stores them as reusable PathAndQuery values.
  • Skips routes with invalid generated gRPC paths during startup instead of handling that parse failure per request.

Confidence Score: 5/5

The change appears safe to merge based on the limited, performance-focused scope and absence of identified correctness issues.

Only src/transcode/mod.rs changed, and the update preserves the success path while moving reusable route data into cheaper shared representations.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the unary proxied-route route-allocations tests before and after, and observed that both runs completed with exit code 0 and reported success_behavior_unchanged=true.
  • Encountered a runtime blocker when attempting to run cargo test transcode, because cargo was not found in the environment, blocking both before/after test attempts.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "perf(transcode): remove per-request rout..." | Re-trigger Greptile

@polaz
polaz merged commit 3339c49 into main Jun 20, 2026
3 checks passed
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.

perf(transcode): remove per-request route allocations on the hot path

1 participant