Stream metadata responses above Cloud Run's non-streamed size cap - #3798
Draft
MaxGhenis wants to merge 2 commits into
Draft
Stream metadata responses above Cloud Run's non-streamed size cap#3798MaxGhenis wants to merge 2 commits into
MaxGhenis wants to merge 2 commits into
Conversation
GET /us/metadata serializes to ~70 MB. Cloud Run drops HTTP/1 responses above 32 MiB unless they use chunked transfer encoding or streaming (https://docs.cloud.google.com/run/quotas), so clients that do not negotiate gzip have received an empty 500 from the Google Frontend since the Cloud Run cutover; gzip-negotiating clients (browsers, python requests/httpx defaults) still got 200s at ~10 MB compressed. Serialize once as before, but deliver bodies at or above 20 MiB as a streamed response with no Content-Length, in both the native FastAPI route and the Flask fallback. Smaller responses keep their exact current framing. GZipMiddleware still compresses streamed bodies for clients that negotiate gzip. Part of #3797. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3798 +/- ##
==========================================
+ Coverage 85.11% 85.13% +0.01%
==========================================
Files 109 110 +1
Lines 6290 6309 +19
Branches 1093 1096 +3
==========================================
+ Hits 5354 5371 +17
- Misses 630 631 +1
- Partials 306 307 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
Part of #3797 (the
/us/metadatasymptom only; the simulation connection-reset symptom is separate).Root cause
GET /us/metadataserializes to ~70 MB (71,532,620 bytes on 2026-08-22). Cloud Run drops HTTP/1 responses above 32 MiB unless they useTransfer-Encoding: chunkedor streaming (Cloud Run quotas: "Maximum HTTP/1 response size: 32 MiB if not usingTransfer-Encoding: chunkedor streaming mechanisms"). Both the native FastAPI route and the Flask fallback send the body withContent-Length, so since the Cloud Run cutover any client that does not negotiate gzip gets an empty 500 generated by the Google Frontend (content-type: text/html,content-length: 0, nox-policyengine-*headers — the app never sees a failure).Clients that negotiate gzip were never affected:
GZipMiddlewarecompresses the body to ~10 MB, under the cap. Browsers and pythonrequests/httpxnegotiate gzip by default, which is why the app kept working while plaincurl(and any non-gzip monitor) saw 500s. Verified live on 2026-08-22:Fix
Serialize once exactly as before, but deliver bodies at or above 20 MiB as a streamed response with no
Content-Length— chunked on the wire, which Cloud Run accepts at any size. Applied to both the native FastAPI route (what production serves;ROUTE_IMPL_METADATA=fastapi_native) and the Flask fallback, sharing one helper. Responses under the threshold keep their exact current framing, so UK/CA/NG/IL metadata and every other route are byte-identical.GZipMiddlewarestill compresses streamed bodies for gzip-negotiating clients.Alternatives considered:
Accept-Encodingbreaks clients that genuinely cannot decode it.Tests
tests/unit/test_streaming_json.py: threshold boundary and chunk reassembly.tests/unit/test_stage6_native_metadata.py: large bodies stream withoutContent-Lengthfor identity clients, still gzip for negotiating clients, and small bodies keepContent-Length. Fulltests/unitrun locally: 1071 passed (the 3 pre-existing collection errors intest_gcp_logging.py/ai_promptsreproduce identically on clean master in the same environment).After deploy, the incident repro flips:
curl -s -o /dev/null -w "%{http_code}" https://api.policyengine.org/us/metadatashould print 200.🤖 Generated with Claude Code