File-like values passed to KeyValueStoreClient.set_record (and Actor run inputs) are fully read into memory by value.read() in encode_key_value_store_record_value, and _prepare_request_call then compresses the whole buffer into a second full copy. Uploading a large record (e.g. multi-GB) from a file handle needs ~2x its size resident in RAM.
Root cause
There is no chunked/streaming path anywhere in the stack:
HttpClient.call / HttpClientAsync.call type the body as data: str | bytes | bytearray | None — no iterator or file object.
HttpClientBase._prepare_request_call reads the whole data and compresses it in one shot: self._http_compressor.compress(data) runs over the full buffer, regardless of the configured HttpCompressor (applies to both gzip and brotli).
- The default transport (
ImpitHttpClient) only accepts a bytes-like content= body.
Notes on a fix
Full streaming requires changes across the whole layer, not just the transport:
- Accept a streaming body (bytes iterator / async iterator / file object) through the
HttpClient abstraction.
- Make compression incremental or skippable, so the body is not re-buffered. This has to hold for every
HttpCompressor implementation (gzip and brotli), not a single one.
- Use a transport that can stream the body.
httpx supports this today — content= accepts a sync/async bytes generator and streams it chunk-by-chunk (multipart uploads via files= stream by default). It is unclear whether impit exposes an equivalent; that needs checking, and impit was chosen for its browser-like TLS fingerprints, so swapping transports has trade-offs.
Follow-up to commit bc092b8 (file-like read fix), where this was noted as out of scope.
✍️ Drafted by Claude Code
File-like values passed to
KeyValueStoreClient.set_record(and Actor run inputs) are fully read into memory byvalue.read()inencode_key_value_store_record_value, and_prepare_request_callthen compresses the whole buffer into a second full copy. Uploading a large record (e.g. multi-GB) from a file handle needs ~2x its size resident in RAM.Root cause
There is no chunked/streaming path anywhere in the stack:
HttpClient.call/HttpClientAsync.calltype the body asdata: str | bytes | bytearray | None— no iterator or file object.HttpClientBase._prepare_request_callreads the wholedataand compresses it in one shot:self._http_compressor.compress(data)runs over the full buffer, regardless of the configuredHttpCompressor(applies to both gzip and brotli).ImpitHttpClient) only accepts a bytes-likecontent=body.Notes on a fix
Full streaming requires changes across the whole layer, not just the transport:
HttpClientabstraction.HttpCompressorimplementation (gzip and brotli), not a single one.httpxsupports this today —content=accepts a sync/async bytes generator and streams it chunk-by-chunk (multipart uploads viafiles=stream by default). It is unclear whetherimpitexposes an equivalent; that needs checking, andimpitwas chosen for its browser-like TLS fingerprints, so swapping transports has trade-offs.Follow-up to commit bc092b8 (file-like read fix), where this was noted as out of scope.
✍️ Drafted by Claude Code