Playbooks

Parts, merges & ingestion health

ClickHouse stores every insert as a part and merges parts in the background. When inserts arrive too small and too often, parts pile up faster than merges can keep up — and queries, merges and replicas all suffer. This playbook shows how to spot and fix merge debt at the source.

Symptoms

  • Too many parts errors, or inserts being throttled / rejected.
  • Query latency creeps up as part counts grow on a hot table.
  • Background merges run constantly and never catch up.
  • Replicas lag, or their replication queue keeps growing.

Step 1 — Find tables with too many parts

A healthy MergeTree table holds tens of active parts per partition, not thousands. Rank tables by active part count and watch the average part size — many tiny parts is the tell-tale sign of bad ingestion.

sql
SELECT
    database,
    table,
    count()                                AS parts,
    formatReadableSize(avg(bytes_on_disk)) AS avg_part,
    formatReadableSize(sum(bytes_on_disk)) AS total
FROM system.parts
WHERE active
GROUP BY database, table
HAVING parts > 300
ORDER BY parts DESC;

Step 2 — Diagnose insert pressure

Tiny parts come from tiny, frequent inserts. `system.part_log` records each part as it is created — a high rate of small new parts means the writer is inserting row-by-row instead of in batches.

sql
SELECT
    table,
    count()                                AS new_parts,
    formatReadableSize(avg(size_in_bytes)) AS avg_new_part
FROM system.part_log
WHERE event_type = 'NewPart'
  AND event_time > now() - INTERVAL 1 HOUR
GROUP BY table
ORDER BY new_parts DESC;
The fix is batching
Insert in batches of tens of thousands of rows, or enable async_insert = 1 so ClickHouse buffers small inserts server-side. Both produce fewer, larger parts and collapse merge debt at the source.

Step 3 — Check the merge backlog

`system.merges` shows merges in flight right now; a persistently long list means the cluster cannot keep up. Pair it with the part counts above to confirm the debt is growing, not transient.

sql
SELECT
    database,
    table,
    elapsed,
    round(progress, 2)                       AS progress,
    formatReadableSize(total_size_bytes_compressed) AS size,
    num_parts
FROM system.merges
ORDER BY elapsed DESC;

Step 4 — Watch replica health

On a replicated cluster, merge debt and insert pressure show up as replication lag. `system.replicas` exposes the queue size and delay per replica — a growing queue_size or absolute_delay means a replica is falling behind.

sql
SELECT
    database,
    table,
    is_readonly,
    absolute_delay,
    queue_size,
    inserts_in_queue,
    merges_in_queue
FROM system.replicas
WHERE is_readonly
   OR absolute_delay > 60
   OR queue_size > 100;
Don't forget stuck mutations
An ALTER ... UPDATE/DELETE that never finishes also blocks merges. Check SELECT * FROM system.mutations WHERE is_done = 0 for mutations stuck behind a failing part.

How OptiHouse automates this

Storage fragmentation map (1.2)
Tables with too many tiny parts and a low average part size, ranked by severity.
Insert pressure analyzer (3.1)
Tables receiving many small inserts that create part-merge debt — with the batching fix in the runbook.
Merge debt index (3.2)
A growing merge backlog the cluster is not keeping up with, tracked over time.
Replica imbalance detector (3.3)
Replicas carrying uneven load, lag or part counts.
Disk capacity forecast (8.4)
Disks above a utilisation threshold with a days-until-full estimate.
Runbooks, not just alerts
Each finding ships with an auto-generated runbook: probable cause, the diagnostic SQL above, and the concrete remediation — so on-call gets the fix, not just a red number.