Skip to content

perf(ingest): size worker batches by bytes so each insert writes one compact part #779

Open

Description

@EricAndrechek

Area: ingest · performance — gap

Expected: each worker flush is as large as it can be while ClickHouse still writes it as a compact part: a batch target expressed in bytes (for example around 8 MiB uncompressed), below the table's min_bytes_for_wide_part, instead of a fixed row count. Fewer, larger parts per table, each a small, fixed set of files.

Actual: a table's batch flushes at 500 rows or 5 s (internal/ingest/worker.go:132-133) whatever the row width, and each flush is one INSERT, so one new part per partition it touches (unless the server buffers inserts with async_insert, see #557).

Measured with clickhouse local on 26.6.3.62 (the version pinned on main), a 20-column MergeTree table, merges stopped:

insert uncompressed compressed part type files in the part
500 rows 103 KiB 27 KiB Compact 10
45,000 rows 9.1 MiB 2.4 MiB Compact 10
70,000 rows 14.2 MiB 3.8 MiB Wide 74
  • min_bytes_for_wide_part defaults to 10,485,760 (10 MiB) and min_rows_for_wide_part to 0, on both 26.6.3.62 and 26.8.15.10. The comparison is on uncompressed bytes: the 14.2 MiB part went Wide although it compressed to 3.8 MiB.
  • A compact part is a fixed ~10 files whatever its row count; a wide part is about two files per column stream. On an object-storage disk each file is written as its own object (inferred), so a compact part costs a roughly fixed number of writes.
  • 500 rows of this schema is about 1% of the threshold. Small parts also mean more merge work and count toward parts_to_delay_insert (1,000) and parts_to_throw_insert (3,000) per partition (defaults, measured).

Constraints the change has to account for (measured in code unless marked):

Impact: a steady ingest stream writes many ~100 KiB parts per table instead of a few multi-MiB ones: more objects written on object storage, more merges, and less headroom before the too-many-parts limits under bursty or many-table load.

Related: #778 (making the target and the time trigger configurable, per tenant where it fits), #557, #657, #263, #544, #94 (batch-size and flush-duration metrics).


Found during a backlog review; measured with clickhouse local 26.6.3.62 and 26.8.15.10 and by code-read against origin/main at afd76695 on 2026-10-08.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/ingestIngest pipeline (Bento, batching, DLQ)enhancementNew feature or request

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions