All posts

5 min of ClickHouse: Reading system.merges — Is Your Cluster in a Merge Storm?

How to read system.merges to tell a healthy background merge load from a merge storm eating your CPU and I/O.

Third in the series. system.merges shows every merge and part-mutation currently running on a MergeTree table. It’s the table to check whenever a cluster feels slow but no single query looks guilty — background merges compete for the same CPU and disk I/O as your SELECTs.

The query

SELECT
    database || '.' || table AS table,
    elapsed,
    round(progress * 100, 1) AS pct_done,
    num_parts,
    formatReadableQuantity(rows_read) AS rows_read,
    formatReadableQuantity(rows_written) AS rows_written,
    formatReadableSize(memory_usage) AS memory_usage,
    is_mutation,
    merge_type,
    merge_algorithm
FROM system.merges
ORDER BY progress DESC

Every row is a merge or mutation in flight right now — this table only holds active operations, not history (for history, system.part_log with event_type = 'MergeParts' gives you the completed record). An empty result set just means nothing is merging at this instant, which is normal between merge cycles on a quiet table.

Reading the columns

Is it a storm, or just healthy background work?

A “merge storm” is a self-reinforcing pattern: inserts create parts faster than merges can consolidate them, so the merge pool keeps launching larger and larger merges to catch up, each one consuming more memory and I/O, further starving new small merges — this is the mechanism behind Too Many Parts. Signs you’re in one:

-- Count of concurrent merges right now
SELECT count() AS active_merges, sum(memory_usage) AS total_merge_memory
FROM system.merges

-- Merge/mutation volume trend over the last two weeks
SELECT toStartOfDay(event_time) AS day,
       countIf(event_type = 'MergeParts') AS merges,
       countIf(event_type = 'MutatePart') AS mutations
FROM system.part_log
WHERE event_time > now() - INTERVAL 14 DAY
GROUP BY day
ORDER BY day

If active_merges is consistently pinned at (or near) background_pool_size and part counts are still climbing (from the first post’s query), the pool can’t keep up — not a transient blip.

Fix

How chmonitor surfaces this

Merges shows this exact table live, auto-refreshing every 30 seconds, with progress bars per merge and a linked Merge Performance page for the historical system.part_log trend above.

chmonitor does this for you

chmonitor watches system.merges continuously and flags when the active-merge count sits near the pool ceiling for a sustained period — before it turns into a rejected insert.

docker run -d --name chmonitor -p 3000:3000 \
  -e CLICKHOUSE_HOST=https://clickhouse.example.com:8443 \
  -e CLICKHOUSE_USER=default \
  -e CLICKHOUSE_PASSWORD=change-me \
  ghcr.io/chmonitor/chmonitor:latest

Or skip setup and try the live demo.