perf: pre-serialize environment-document bytes on poll#12
Open
gagantrivedi wants to merge 1 commit intomainfrom
Open
perf: pre-serialize environment-document bytes on poll#12gagantrivedi wants to merge 1 commit intomainfrom
gagantrivedi wants to merge 1 commit intomainfrom
Conversation
The released `/api/v1/environment-document` path runs
`serde_json::to_vec(&Arc<Value>)` on every request and the handler
then `body.to_vec()` clones the resulting `Arc<[u8]>` into a fresh
`Vec<u8>` for the response body. Two allocations and a full JSON
encoding per request — for a document that only changes on the
polling refresh interval.
This change moves the serialization to the cache layer:
- New `EnvironmentsCache::get_environment_bytes` returns `Bytes`.
- `LocalMemEnvironmentsCache` produces the bytes once inside
`put_environment`, alongside the parsed `Arc<Value>` and the
pre-computed evaluation context. Failure to serialize leaves the
byte cache empty and the handler returns 503.
- `EnvironmentService::get_environment_bytes` becomes a thin
delegate over the cache; the previous endpoint-cache wrapper
(which served the same purpose under `endpoint_caches.environment_document.use_cache`)
is removed since the byte cache is always populated now.
- The route handler returns the `Bytes` directly as the response
body instead of `body.to_vec()`. axum accepts `Bytes` as a body
with no copy.
Per-request CPU on the env-doc endpoint goes from "serialize a 1-11 MB
JSON tree + memcpy" to "refcounted clone".
Measured on Fargate (1 vCPU / 2 GB), endpoint cache disabled:
small project (1 MB env-doc):
peak RPS: 343 -> 566 (1.65x)
p50 @ c=25: 82ms -> 36ms
medium project (11 MB env-doc):
peak RPS: 21 -> 52 (2.46x)
p50 @ c=25: 1.03s -> 451ms
p99 at high concurrency (c>=500 small / c>=50 medium) gets worse
because the bottleneck shifts from CPU to socket-write queueing on
the response body — those ranges are past the useful operating point
for the endpoint anyway. p50 and p90 improve across the curve.
Member
Author
Code reviewFound 1 issue:
edge-proxy-rs/src/cache/environment.rs Lines 74 to 128 in 84137d2 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
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.
Summary
Move JSON serialization of the environment document off the request path. The released
/api/v1/environment-documentpath runsserde_json::to_vec(&Arc<Value>)on every request and the handler thenbody.to_vec()clones the resultingArc<[u8]>into a freshVec<u8>. Two allocations and a full JSON encoding per request — for a document that only changes on the polling refresh interval.This PR moves the serialization to the cache layer:
EnvironmentsCache::get_environment_bytesreturnsBytes.LocalMemEnvironmentsCache::put_environmentproduces the bytes once, alongside the parsedArc<Value>and the pre-computed evaluation context.EnvironmentService::get_environment_bytesbecomes a thin delegate.Bytesdirectly as the response body — axum acceptsByteswith no copy.Per-request work on the env-doc endpoint goes from "serialize a 1-11 MB JSON tree + memcpy" to "refcounted clone".
Measured on Fargate (1 vCPU / 2 GB)
Endpoint cache disabled. Image-vs-image A/B with the released
flagsmith/edge-proxy-rs:0.1.1baseline.Small project (1 MB env-doc)
Medium project (11 MB env-doc)
Medium gets a bigger relative win because at 11 MB per response the released path's per-request
serde_json::to_vecwas dominating CPU. After the fix, throughput is bandwidth-bound rather than CPU-bound and reaches roughly the same physical limit as the Python edge-proxy on the same task spec.p99 caveat
At high concurrency (c≥500 small, c≥50 medium) the fix's p99 tail is worse than baseline. The bottleneck has shifted from CPU (serializing) to socket-write queueing on the response body — some requests get a fast slot, others queue behind 1 MB / 11 MB of in-flight bytes. p50 and p90 improve across the curve; p99 only matters past the useful operating range for the endpoint.
Test plan
cargo build --releaseclean fromorigin/maincargo test --release --lib— all 19 unit tests passcargo clippy --release --all-targets -- -D warningscleanGET /api/v1/environment-documentreturns identical byte output as the baseline image for both project sizes