Skip to content

Perf/live ingest throughput - #10

Merged
lmangani merged 5 commits into
mainfrom
perf/live-ingest-throughput
Jul 31, 2026
Merged

Perf/live ingest throughput#10
lmangani merged 5 commits into
mainfrom
perf/live-ingest-throughput

Conversation

@lmangani

Copy link
Copy Markdown
Contributor

No description provided.

lmangani added 5 commits July 31, 2026 10:25
Bash + Python session driver with host-side timing, cold-then-warm in one
DuckDB process, warm schema-stability gate, and a main-level 100k baseline.
…f a fixed low cap.

AUTO_POOL_THREAD_CAP=4 and AUTO_PIPELINE_THREAD_CAP=8 clamped the
append-pool and parse-pipeline auto-scaling below the box's own
hardware_concurrency()/2 and *2/3 formulas, leaving cores idle on
many-core machines. Reusing the existing MAX_POOL_THREADS/
MAX_PIPELINE_THREADS=16 ceiling lets defaults scale with the machine;
verified with the full sqllogictest suite (663 assertions) and
back-to-back A/B ingest runs showing ~20-30% higher cold-ingest
throughput on a 20-core box with no regressions.
Re-measured ingest and query benchmarks on DuckDB v1.5.5 with the
scaled thread defaults: 1.35M/1.26M/970k records/s for traces/logs/
metrics (3M records in 2.6s), and 27-63x query speedup with 6x
smaller storage vs the JSON baseline.
httplib defaults TCP_NODELAY off, so Nagle's algorithm interacting with
delayed ACKs added tens of milliseconds of pure socket latency to every
request/response round trip -- devastating for live OTEL ingestion,
where each collector export is its own small POST. Isolated with a
controlled A/B: even the trivial /health endpoint (zero DB work) was
capped at ~428 req/s with the default off; enabling nodelay took it to
~10,000 req/s (24x) under the same 20-connection concurrent load.

On the real OTLP/HTTP ingestion path (traces envelopes, 1M records)
this took live throughput from ~33k records/s to ~300k records/s
synchronous, and ~625k records/s with async ingestion enabled -- a
pure transport fix, no ingestion or transaction logic touched.

Also widened the kernel accept-queue backlog from httplib's default of
5 to 512, sized for interactive use rather than a fleet of collectors
reconnecting concurrently (e.g. after a network blip).

Verified with the full sqllogictest suite (663 assertions, including
raw_api.test's server-lifecycle coverage) -- no regressions.
…r worker.

Researched ClickHouse's async_insert design for this. Its AsynchronousInsertQueue
uses a shared thread pool (async_insert_threads) so multiple buffers flush
concurrently; RawDuck's flusher was a single dedicated thread, serializing
every table's flush through it regardless of table count.

ClickHouse's MergeTree lets many concurrent writers coexist (each write is a
new immutable part, merged later); DuckDB's optimistic appends serialize
badly across concurrent transactions on the SAME table (AGENTS.md: "this was
v0.1's mistake"). So this only parallelizes across the table dimension --
different tables' due buffers flush concurrently on separate connections
(zero contention, independent DataTable objects), while each table's own
flush stays single-connection/single-transaction/sequential, exactly as
before. A single due table takes the same inline path as today, no thread
spin-up.

New rawduck_async_flush_threads setting (0 = auto from hardware
concurrency, capped at 16) controls the pool size; applies to the
periodic flush loop, raw_flush(), and the shutdown drain.

Verified: full sqllogictest suite (663 assertions) passes unchanged.
Correctness check -- 90,000 rows enqueued interleaved across 3 tables,
flushed concurrently, landed exactly once each with zero loss/duplication.
Concurrency check -- 3-table concurrent flush (0.246s) vs. 3x the measured
single-table serial cost (3 x 0.150s = 0.450s): ~1.8x from true fan-out,
while the single-table path is untouched (same inline route, no regression).
@lmangani
lmangani merged commit 61aa357 into main Jul 31, 2026
7 of 8 checks passed
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