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;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>';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.