Datagrunt 5.3.10: Wave 2B Pooled HTTP Keep-Alive, Single-Pass Cache, Min-Heap Runner & Streaming Batch
Datagrunt 5.3.10 is now available! This release delivers Wave 2B performance optimizations across the rationalization structured extraction subsystem (src/datagrunt/rationalization/), accelerating provider communication, request key hashing, scheduler dispatching, offline batch processing, and envelope rendering (PR #492).
Key Highlights in Datagrunt 5.3.10
- Pooled HTTP Keep-Alive & Transport Fault Retirement (
_BoundConnection& Client Retirement) — Retains pre-TLS plainsocket.socketduplicates across pooled HTTP/1.1 connections so reused connections can still be cut at exact deadlines (keepalive_connections > 0), while automatically retiring failed transport clients on network/TLS errors to preventhttpcore 1.0.9pool stalls. - Single-Pass Fast Request Key & Unified Preflight (
_fast_request_key&_WorkAccumulator) — Pre-serializes canonical provider suffixes (_provider_key_suffix) and envelope prefix states (_envelope_key_prefix), using copyable SHA-256 state (hashlib.sha256().copy()) in_fast_request_keyto compute bit-identical request keys without canonical JSON dict/string overhead._WorkAccumulatorunifies document expansion, unit packing, envelope generation, key hashing, and cache probing into a single pass. - Min-Heap Rate-Key Scheduler (
heapqMin-Heap) — Replaces linear queue scanning in_RequestSchedulerwith aheapqmin-heap across active rate key queues, reducing request dispatch overhead from $O(K)$ to $O(\log K)$ under high concurrency. - Streaming Batch Preparation & Ingestion (
to_vertex_jsonl,to_anthropic_jsonl,_read_bounded_jsonl) — Streams Vertex AI (generateContent) and Anthropic Message Batches JSONL requests line-by-line (to_vertex_jsonl,to_anthropic_jsonl) and ingests batch responses via bounded binary streams (_read_bounded_jsonl), providing $O(1)$ memory usage with strict 16 MiB line and 1,000,000 line bounds. - Shared Frame & Band Cache Envelope Isolation (
build_envelopes_isolated) — Prepares shared document frames, prompt text, context JSON, and_BandCacheonce per document during non-strict envelope generation, isolating per-unit PDF rendering errors without redundant re-parsing. - Ephemeral Provider Lifecycle Cleanup (
_close_ephemeral) — Automatically tracks and executesclose()hooks for string-resolved ephemeral model providers on preflight failure or normal termination without masking active exceptions.
1. Pooled HTTP Keep-Alive & Transport Fault Retirement
The Challenge of Connection Reuse & Deadline Cuts
When sending high volumes of online extraction requests to LLM endpoints, establishing fresh TCP and TLS handshakes on every request introduces significant network overhead. However, retaining pooled HTTP connections introduces two challenges:
- Reused pooled connections must remain strictly bound to request deadline timers so hung servers cannot block worker threads past their unit deadlines.
- Transport failures (such as interrupted TLS handshakes or proxy
CONNECTtunnel errors) can leave corrupted connection handles active insidehttpcore 1.0.9connection pools, eventually exhaustingmax_connectionsand stalling the runner.
Socket Duplication and Client Retirement
Datagrunt 5.3.10 introduces pooled HTTP/1.1 keep-alive connection management (http.py):
- Pre-TLS Socket Duplication (
_BoundConnection): Whenkeepalive_connections > 0,HttpClientduplicates the underlying plainsocket.socketbefore TLS negotiation. During pooled connection reuse, the timer thread uses this duplicate handle to cut the socket at the exact request deadline. - Immediate Client Retirement: Upon any transport error (network drop, deadline cut, or TLS handshake failure), the failing client instance is immediately retired (
_retire_client). Subsequent requests receive a fresh client instance while the retired client closes cleanly once its remaining active requests complete.
from datagrunt.rationalization.providers.http import HttpClient
# Initialize an HTTP client with pooled keep-alive connections
client = HttpClient(max_connections=64, keepalive_connections=16)
# Requests reuse pooled HTTP/1.1 connections while preserving deadline enforcement2. Single-Pass Fast Request Key Generation & Unified Preflight
The Preflight Hashing Bottleneck
Before executing requests, Datagrunt computes a canonical SHA-256 request key (request_key) for every (unit, provider) pair to check the memory and file caches. Previously, this required serializing a complete canonical JSON dictionary holding envelope key material, model identifiers, provider schemas, generation parameters, and schema versions for every candidate request.
Copyable SHA-256 Digest State & Single-Pass Accumulator
Datagrunt 5.3.10 accelerates cache key generation and preflight checks:
- Canonical Byte Suffix Pre-Serialization (
_provider_key_suffix): Non-envelope fields (format,generation_params,model_id,provider_kind,provider_schema,schema_version) are pre-serialized into canonical UTF-8 bytes once per provider snapshot. - Copyable SHA-256 Hashing (
_fast_request_key): Envelope key material is converted to an opening SHA-256 prefix state (_envelope_key_prefix)._fast_request_key()duplicates this state viahashlib.sha256().copy()and updates it with the pre-built provider suffix, yielding a bit-identical hex digest without dictionary or string serialization passes. - Unified Target Work Accumulator (
_WorkAccumulator): Combines target document expansion, unit packing, envelope building, fast request key generation, and cache probing into a single pass (_build_target_work), populating_PreflightPlanefficiently.
from datagrunt.rationalization.cache import (
_envelope_key_prefix,
_provider_key_suffix,
_fast_request_key,
)
import hashlib
# Fast bit-identical request key generation using copyable SHA-256 state
prefix = _envelope_key_prefix(envelope)
env_hasher = hashlib.sha256(prefix)
# Compute key in O(1) time without dictionary canonicalization
key = _fast_request_key(envelope, provider_suffix, envelope_hasher=env_hasher)3. Min-Heap Rate-Key Scheduler
Logarithmic Scheduler Dispatch
In multi-model extraction runs spanning multiple providers and rate keys (e.g., separate rate limits for Gemini, OpenAI, and Anthropic), the request runner dispatches queued requests as rate-limit tokens and concurrency slots become available. Previously, the scheduler scanned rate key queues linearly on every dispatch iteration.
Datagrunt 5.3.10 updates _RequestScheduler (runner.py) to maintain a heapq min-heap of active rate keys ordered by earliest due token time. When concurrency slots open up, the scheduler pops the earliest due request in $O(\log K)$ time instead of scanning $O(K)$ rate key queues, eliminating scheduling latency when managing dozens of concurrent rate limits.
4. Streaming Batch Preparation & Ingestion
Memory-Efficient Offline Cloud Batch I/O
Datagrunt’s offline cloud batch mode (prepare_batch and ingest_batch) allows users to prepare extraction requests offline, submit them to provider batch endpoints (Vertex AI / Gemini and Anthropic Message Batches) for a 50% token price discount, and ingest the results back into the rationalization pipeline.
Datagrunt 5.3.10 transitions batch I/O to streaming operations:
- Direct Line-By-Line JSONL Export (
to_vertex_jsonl,to_anthropic_jsonl): Batch items stream directly to output file handles line-by-line, avoiding full payload accumulation in memory. - Bounded Binary Stream Ingestion (
_read_bounded_jsonl&_load_jsonl_file): Response JSONL files are read via binary stream buffers with strict per-line caps (BATCH_LINE_MAX_BYTES = 16 MiB) and line count caps (BATCH_MAX_LINES = 1,000,000), stripping CR/LF line endings safely before length validation.
from datagrunt import prepare_batch, ingest_batch
from my_schemas import InvoiceSchema
# 1. Prepare offline batch plan
plan = prepare_batch(
target="large_corpus/*.pdf",
schema=InvoiceSchema,
models=["gemini-2.5-flash", "claude-3-5-sonnet"],
)
# 2. Stream batch requests directly to JSONL files with O(1) memory footprint
plan.to_vertex_jsonl("batch_requests/vertex_gemini.jsonl")
plan.to_anthropic_jsonl("batch_requests/anthropic_messages.jsonl")
# 3. Ingest response JSONL files via bounded binary streams
result = ingest_batch(
plan,
responses=[
"batch_responses/vertex_gemini_results.jsonl",
"batch_responses/anthropic_messages_results.jsonl",
],
)5. Shared Frame & Band Cache Envelope Isolation
When rationalize() runs under strict=False, rendering errors on individual PDF pages (such as corrupt embedded raster images) are caught and isolated into SkippedUnit records rather than failing the entire extraction run.
Datagrunt 5.3.10 introduces build_envelopes_isolated() (envelope.py), which constructs the document _Frame, context JSON, prompt text, and _BandCache once per document. During unit processing, per-unit envelope generation reuses these shared inputs, eliminating redundant frame creation and input validation while preserving page-level render fault isolation.
Verification & Upgrade
To upgrade to Datagrunt 5.3.10 using uv:
uv pip install --upgrade datagruntVerify your installed version in Python:
import datagrunt
print(datagrunt.__version__) # 5.3.10