learning.lab
Monitoring & Operations· 30/33

System Tables

system.parts, query_log, merges

Most databases make you reach for a separate CLI tool, an admin panel, or a vendor dashboard to see what the server itself is doing. ClickHouse exposes almost everything about its own internal state as ordinary tables in the system database — queryable with the same SQL you already know.

system.parts — the ground truth for storage

Every part described in Parts & Background Merges is a row here: its partition, size on disk, row count, and whether it's still active or waiting to be cleaned up after a merge.

SELECT
    table,
    partition,
    name,
    rows,
    formatReadableSize(bytes_on_disk) AS size,
    active
FROM system.parts
WHERE table = 'events' AND active
ORDER BY bytes_on_disk DESC
LIMIT 20;

A quick way to spot the "too many parts" problem before it becomes an error: count active parts per table and watch for numbers climbing into the thousands.

SELECT table, count() AS active_parts
FROM system.parts
WHERE active
GROUP BY table
ORDER BY active_parts DESC;

system.merges — merge pressure, live

Background merges don't run instantly or invisibly — while one is in progress, it shows up here with its progress percentage and which parts it's combining.

SELECT
    table,
    elapsed,
    round(progress * 100, 1) AS pct,
    num_parts,
    is_mutation
FROM system.merges;

An empty result isn't necessarily good news on a busy table — it can mean merges are keeping up comfortably, or that they've stalled. Cross-check against system.parts counts over time to tell the difference.

system.mutations — tracking UPDATE/DELETE progress

Every mutation you run is recorded here until it finishes, including whether it failed and why.

SELECT
    table,
    mutation_id,
    command,
    is_done,
    latest_fail_reason
FROM system.mutations
WHERE NOT is_done;
Production note
If a mutation looks stuck, latest_fail_reason is the first place to look — a common cause is the mutation silently retrying against a part that a concurrent merge keeps replacing.

system.query_log — every query that ran, with a cost breakdown

When query_log is enabled (on by default in most setups), every query is logged with how long it took, how many rows and bytes it read, and how much memory it used — the natural follow-up to the EXPLAIN advice in Query Optimization: EXPLAIN tells you what a query plans to do; query_log tells you what it actually did.

SELECT
    query,
    query_duration_ms,
    read_rows,
    formatReadableSize(memory_usage) AS memory
FROM system.query_log
WHERE type = 'QueryFinish'
  AND event_time > now() - INTERVAL 1 HOUR
ORDER BY query_duration_ms DESC
LIMIT 10;

system.processes — what's running right now

Currently executing queries, with their elapsed time and memory usage so far — and a query ID you can hand to KILL QUERY if one is misbehaving.

SELECT query_id, elapsed, memory_usage, query
FROM system.processes
ORDER BY elapsed DESC;

KILL QUERY WHERE query_id = '<id-from-above>';
Why it exists
None of these tables require special tooling, agents, or a separate monitoring stack to read — they're just tables. That said, querying them by hand doesn't replace continuous monitoring; see Monitoring for turning the same underlying metrics into dashboards and alerts.