Skip to content

fix: stream /metrics response to avoid 32 MiB Workers RPC return limit - #45

Open
ahmedbadr-workday wants to merge 1 commit into
cloudflare:mainfrom
ahmedbadr-workday:fix/stream-metrics-rpc-limit
Open

fix: stream /metrics response to avoid 32 MiB Workers RPC return limit#45
ahmedbadr-workday wants to merge 1 commit into
cloudflare:mainfrom
ahmedbadr-workday:fix/stream-metrics-rpc-limit

Conversation

@ahmedbadr-workday

@ahmedbadr-workday ahmedbadr-workday commented Jun 22, 2026

Copy link
Copy Markdown

Problem

On accounts with a large metric surface (many zones/hosts, EXCLUDE_HOST=false), GET /metrics intermittently returns HTTP 500 (Failed to collect metrics). The failure originates in the RPC return from MetricCoordinator.export(): the serialized Prometheus payload exceeds the 32 MiB Workers RPC single-return-value limit (https://developers.cloudflare.com/workers/platform/limits/). Once the aggregate crosses ~32 MiB, every scrape 500s.

Fix

Return the payload from MetricCoordinator.export() as a ReadableStream<Uint8Array> instead of a string — a streamed body is not subject to the 32 MiB single-return cap. A small textToStream() helper encodes the text and emits ~1 MiB chunks split on newline boundaries, so no chunk splits a multi-byte UTF-8 code point and we never
hold a second full-size copy of the payload in memory. worker.tsx returns new Response(stream, …) directly.

Output is byte-identical to the previous string response; only the transport changes. No config or API-surface changes.

Changes

  • src/lib/prometheus.ts: add textToStream()
  • src/durable-objects/MetricCoordinator.ts: export() returns a UTF-8 byte stream
  • src/worker.tsx: stream the response
  • src/lib/prometheus.test.ts: unit tests (round-trip, multi-chunk, multi-byte safety)

Verification

  • bun run check (biome) clean
  • bun run typecheck clean
  • bun run test — new textToStream tests pass

The aggregated Prometheus payload returned from MetricCoordinator.export() can exceed the 32 MiB Workers RPC single-return-value limit on large deployments (many zones/hosts with EXCLUDE_HOST=false), causing GET /metrics to fail with HTTP 500 on every scrape.

Return the payload as a ReadableStream<Uint8Array> instead of a string; a streamed body is exempt from the 32 MiB single-return cap. A new textToStream() helper encodes the text and emits ~1 MiB chunks split on newline boundaries so no chunk splits a multi-byte UTF-8 code point and we never hold a second full-size copy in memory. The worker returns the stream directly via new Response(). Output is byte-identical; only the transport changes.

Adds unit tests for textToStream (round-trip, multi-chunk, multi-byte safety).
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