Skip to content

fix(tika-worker): stream S3 blobs to Tika instead of buffering (#189)#191

Merged
jensens merged 2 commits into
mainfrom
fix/tika-worker-stream-s3-189
Jun 29, 2026
Merged

fix(tika-worker): stream S3 blobs to Tika instead of buffering (#189)#191
jensens merged 2 commits into
mainfrom
fix/tika-worker-stream-s3-189

Conversation

@jensens

@jensens jensens commented Jun 29, 2026

Copy link
Copy Markdown
Member

Problem

TikaWorker materialized each blob entirely in memory before sending it to Tika — peak ≈ 2–3× the blob size (S3 download_fileobj buffer + getvalue() copy + httpx request body). On a deployment with S3-tiered blobs and a small worker pod (256Mi limit), a single large PDF/image OOM-killed the worker (exit 137) into CrashLoopBackOff, stalling extraction. Raising the memory limit only masks it.

Fix

_extract() now streams the S3 path straight from S3 into the Tika request body:

obj = s3.get_object(Bucket=bucket, Key=s3_key)
headers["Content-Length"] = str(obj["ContentLength"])
content = obj["Body"].iter_chunks(chunk_size=1 MiB)   # boto3 StreamingBody
client.put(f"{tika_url}/tika", content=content, headers=headers)

Bytes flow S3 → worker → Tika in 1 MiB chunks, so the worker never holds the whole blob and its memory stays roughly constant regardless of blob size. Content-Length from get_object avoids chunked transfer-encoding.

Blob-location logic is split into _blob_source() (returns a bytes or s3 descriptor without materializing S3); _extract() streams S3 while _fetch_blob() keeps materializing for its remaining callers. The PG-bytea path (small inline blobs below the S3 tiering threshold) is unchanged.

Pairs with the configurable TIKA_WORKER_HTTP_TIMEOUT (#183).

Tests

tests/test_tika_worker_extras.py — 2 new: the S3 path hands the iter_chunks iterator (not a bytes buffer) to httpx with Content-Length and never calls download_fileobj; the PG-bytea path still sends bytes. _extract's existing tests adjusted for the _blob_source split. 18 passed, 2 skipped.

Closes #189

🤖 Generated with Claude Code

jensens and others added 2 commits June 29, 2026 23:26
TikaWorker materialized each blob fully in memory before sending it to
Tika (peak ~2-3x blob size). A single large S3-tiered PDF/image OOM-killed
a small worker pod (256Mi) into CrashLoopBackOff.

_extract now streams the S3 path: get_object()['Body'].iter_chunks() is
handed straight to httpx as the request content, with Content-Length from
the object metadata. The worker's memory stays ~flat regardless of blob
size. The PG-bytea path (small sub-threshold inline blobs) still sends
bytes. Blob-location logic is split into _blob_source() so _extract streams
while _fetch_blob keeps materializing for its callers.

Closes #189

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jensens
jensens merged commit 850c590 into main Jun 29, 2026
5 checks passed
@jensens
jensens deleted the fix/tika-worker-stream-s3-189 branch June 29, 2026 21:39
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.

tika_worker buffers each blob fully in memory (~2-3x size) → OOM on large blobs; stream S3 → Tika instead

1 participant