Query Metrics

Query Metrics shows a second-by-second timeline of how a query consumed resources during its execution: memory, CPU, disk IO, cache hits and misses, network, and hundreds of other counters. Think of it as a heart rate monitor for a single query.

Navigate to Tools > Query Metrics to get started.


What Are Query Metrics?

When a query runs for more than about 1 second, ClickHouse® takes a snapshot of its resource usage every second. Each snapshot records hundreds of counters: how much memory the query is using right now, how many bytes it has read from disk so far, how many cache hits and misses occurred, and much more.

These snapshots are stored in the system.query_metric_log table. Query Metrics turns this raw data into visual timelines, grouped by category and unit, so you can see exactly how resources were consumed over the query's lifetime.

When is this useful?

  • A query uses too much memory and you want to see exactly when the memory spike happens
  • A query is slow and you want to know if it's spending time on disk reads, cache misses, or network waits
  • You want to compare two queries to understand why one is faster than the other
  • You need to tune cache sizes and want to see the hit/miss ratio for a specific workload

Getting Started

  1. Set the From and To datetime fields (default: last 1 hour)
  2. Click Load Queries. This shows queries that have per-second metric data
  3. Click a query in the list to see its details (query ID, SQL text, duration), then click Use This Query
  4. Click Show Query Metrics
  5. Charts appear, grouped by category and unit

Only categories with non-zero data appear. A simple SELECT 1 might show only Memory and CPU. A complex query with JOINs, disk spilling, and remote reads will show many more groups.


Understanding the Charts

Layout

Charts are displayed two per row in a grid layout. Each chart shows one category of metrics at a specific unit of measurement. The X axis is time (one data point per second of query execution). The Y axis is the metric value, labeled with the unit.

If a category has metrics with different units (for example, Memory has both byte values and count values), they appear as separate charts so the Y axis scale is meaningful. You'll see labels like "Memory, Bytes" and "Memory, Count" side by side.

Reading the Charts

Each colored line represents one metric. The legend at the bottom shows the metric names (with the ProfileEvent_ prefix stripped for readability). Click a legend item to show or hide that metric.

Tip: If a chart has too many lines to read clearly, click legend items to hide the less important ones and focus on what matters.

Chart Splitting

When a category has more than 4 metrics of the same unit, CHOps splits them into multiple sub-charts of 4 metrics each. The charts are labeled with part numbers: "In-Memory Caches, Count (1/3)", "(2/3)", "(3/3)". The most active metrics (by total value across the query's lifetime) appear in the first sub-chart.


Reading Patterns

Memory Chart

A typical Memory, Bytes chart shows:

  • memory_usage, current memory allocated by the query. Goes up and down as the query allocates and releases memory.
  • peak_memory_usage, the highest memory_usage seen so far. Only goes up.
PatternWhat It Tells You
Both lines climb steadilyThe query is accumulating data in memory (building a hash table, sorting, aggregating)
memory_usage spikes then drops, peak stays highA brief memory spike, common with large hash JOINs that allocate a big hash table, use it, then release it
Both lines are flat and lowThe query uses minimal memory, it's streaming data without buffering
peak_memory_usage hits the memory limitThe query was killed or throttled for exceeding max_memory_usage

Disk IO Chart

  • OSReadBytes, bytes read from disk. A steadily climbing line means the query is scanning data.
  • OSWriteBytes, bytes written to disk. A spike usually means the query is spilling intermediate results to disk.
  • DiskReadElapsedMicroseconds, time spent waiting for disk reads. High values mean IO is the bottleneck.

Cache Chart

  • MarkCacheHits vs MarkCacheMisses, the ratio tells you if the mark cache is effective. High misses mean the cache is too small.
  • PageCacheHits vs PageCacheMisses, same for the uncompressed page cache.

How Units Work

CHOps automatically detects the unit of each metric from its column name and ensures metrics with different units are never mixed in the same chart.

Column Name PatternUnitY Axis Label
*MicrosecondsμsTime (μs)
*MillisecondsmsTime (ms)
*NanosecondsnsTime (ns)
*Bytes, *BytesSent, *BytesReceived, *CharsbytesBytes
*RowsrowsRows
Everything else (hits, misses, faults, cycles, counts)countCount
memory_usage, peak_memory_usagebytesBytes

This means a category like "CPU & Time" may produce two charts: one for time metrics (μs) and one for count metrics (page faults, context switches). Both are labeled clearly.


Metric Categories

CHOps classifies the hundreds of possible metrics into categories. Only categories with non-zero data appear. Categories are ordered by how commonly they appear.

Common Categories (appear for most queries)

CategoryWhat It ShowsKey Metrics to Watch
MemoryMemory usage, peak, arena allocations, jemalloc statsmemory_usage vs peak_memory_usage, compare to see if there are spikes
CPU & TimeWall-clock time, CPU time, IO wait time, page faultsRealTimeMicroseconds vs UserTimeMicroseconds, if Real >> User, the query is waiting, not computing
Disk IOBytes and time for disk reads/writes, IO buffer allocationsOSReadBytes, if high, the query is reading a lot from disk
Data ReadRows and bytes selected, parts and marks scannedSelectedRows, SelectedMarks, high values mean a large scan
In-Memory CachesMark cache, page cache, primary index cache, query cache hits and missesHits vs Misses ratio, high misses mean the cache is too small
Marks & Index LoadingTime and count for loading mark files and primary index blocksWaitMarksLoadMicroseconds, if high, marks are being loaded from disk
Query ExecutionFunction calls, JIT compilation, overflow checksCompilation time and function call counts
Threading & LocksThread pool activity, context switches, lock wait timesHigh lock wait times indicate contention between threads

Write-Path Categories (appear for INSERT queries)

CategoryWhat It Shows
Data WriteRows and bytes inserted, delayed/rejected inserts
Merges & MutationsBackground merge activity, rows merged, merge duration

Infrastructure Categories (appear based on your setup)

CategoryWhen It Appears
Filesystem CacheWhen filesystem cache is enabled in your ClickHouse® config
Network & ConnectionsDistributed queries that read from remote shards
S3 / Azure / RemoteData stored on S3, Azure Blob Storage, or other remote storage
External OperationsQuery exceeds in-memory limits and spills to disk (sort, aggregation, join)
JOIN OperationsQueries with JOINs (hash table sizes, probe/build counts)
ClickHouse® KeeperOperations on replicated tables

Rare Categories

CategoryWhen It Appears
KafkaKafka engine table reads/writes
BackupBACKUP/RESTORE commands
LoggingAlways present but usually low (internal log message counts)
ThrottlingWhen bandwidth throttling settings are active
OtherMetrics that don't fit any category

What Appears Per Query Type

Different query types activate different categories. Use this as a quick reference to know what to look for.

Simple SELECT (e.g., SELECT * FROM table WHERE id = 123)

CategoryWhat to CheckRed Flags
Memory, Bytesmemory_usage should stay flat if streamingSteady climb means the query is buffering too much data
CPU & Time, Time (μs)RealTimeMicroseconds vs UserTimeMicrosecondsReal >> User means the query is waiting on IO, not computing
Disk IO, BytesOSReadBytes should be proportional to result sizeReads >> result size means the query is scanning too many parts
Disk IO, Time (μs)DiskReadElapsedMicrosecondsHigh values mean disk is the bottleneck, check if filesystem cache is enabled
Data Read, RowsSelectedRows, SelectedMarksHigh mark count means many granules scanned, check if WHERE clause aligns with ORDER BY
In-Memory Caches, CountMarkCacheHits vs MarkCacheMissesMiss ratio > 10% means mark cache is cold or too small

SELECT with JOIN

Everything from Simple SELECT, plus:

CategoryWhat to CheckRed Flags
Memory, Bytespeak_memory_usage may spike during hash table buildIf peak >> steady state, the JOIN hash table is large, consider partial_merge_join
JOIN, RowsJoinBuildTableRows, JoinProbeTableRowsBuild table too large? Move the smaller table to the right side of JOIN
JOIN, CountJoinResultRowsResult much larger than input? Likely a many-to-many join (check JOIN keys)
External Operations, BytesExternalJoinWritePartIf present, the JOIN spilled to disk, increase max_bytes_in_join or restructure

SELECT with GROUP BY / ORDER BY

Everything from Simple SELECT, plus:

CategoryWhat to CheckRed Flags
Memory, BytesArenaAllocBytes climbing steadilyLarge aggregation state, high-cardinality GROUP BY
External Operations, BytesExternalSortWritePart, ExternalAggregationWritePartData spilled to disk, increase max_bytes_before_external_sort / max_bytes_before_external_group_by
Threading, CountContextSwitchesHigh values with many threads, reduce max_threads or check lock contention

INSERT

CategoryWhat to CheckRed Flags
Memory, Bytesmemory_usage should be low for streaming insertsHigh memory means large batches or wide rows
Data Write, RowsInsertedRows, InsertedBytesCheck throughput matches expectations
Data Write, CountDelayedInserts, RejectedInsertsNon-zero DelayedInserts means too many parts, merges can't keep up
Merges & Mutations, RowsMergedRowsLarge values during INSERT mean background merges are competing for resources
Merges & Mutations, Time (ms)MergeTotalMillisecondsLong merge times during INSERT may slow down query performance

Distributed SELECT (across shards)

Everything from Simple SELECT, plus:

CategoryWhat to CheckRed Flags
Network, BytesNetworkSendBytes, NetworkReceiveBytesLarge transfer means too much data moving between shards, add PREWHERE or push down filters
Network, Time (μs)NetworkReceiveElapsedMicrosecondsHigh values mean network is the bottleneck, check inter-node bandwidth
Network, CountDistributedConnectionMissCountNon-zero means connection pool misses, connections being established on every query

Cloud Storage Query (S3 / Azure / GCS)

Everything from Simple SELECT, plus:

CategoryWhat to CheckRed Flags
S3/Remote, BytesReadBufferFromS3Bytes, S3ReadBytesLarge reads mean scanning too much remote data, use projections or local caching
S3/Remote, Time (μs)S3ReadMicrosecondsHigh latency per request, check region proximity
S3/Remote, CountS3ReadRequestsCountMany small requests, consider increasing remote_fs_read_backoff_max_ms
Filesystem Cache, CountCachedReadBufferCacheWriteBytesIf filesystem cache is active, check hit rate

Replicated Table Write

Everything from INSERT, plus:

CategoryWhat to CheckRed Flags
Keeper, Time (μs)ZooKeeperWaitMicrosecondsHigh values mean ClickHouse® Keeper is slow, check Keeper node health
Keeper, BytesZooKeeperBytesSent, ZooKeeperBytesReceivedLarge payloads to Keeper, unusual, may indicate large part metadata
Keeper, CountZooKeeperTransactionsMany transactions per insert, check replication queue depth

A simple SELECT 1 might show only Memory (bytes) and CPU (μs).


How Discovery Works

system.query_metric_log has over 700 columns, and the list changes between ClickHouse® versions. CHOps does not hardcode column names. Instead:

  1. Fetch all rows for the selected query with SELECT * (typically 1-60 rows for a 1-60 second query)
  2. Scan every row to find columns where any row has a non-zero value. This catches metrics that activate mid-query (for example, ExternalSortWritePart might be 0 for the first 5 seconds, then spike when the sort spills to disk)
  3. Sort by activity, the most active metrics (highest total absolute value) come first
  4. Cap at 100 columns, if more than 100 are active, the 100 most active are kept
  5. Classify each column by category and unit
  6. Split categories with more than 4 metrics of the same unit into sub-charts
  7. Build charts directly from the fetched data (no second query needed)

This approach is version-agnostic, it works across all ClickHouse® versions because SELECT * discovers the schema at query time.


Prerequisites

RequirementWhyHow to Check
ClickHouse® 26.3 LTS or newerCHOps's minimum supported versionSELECT version()
query_metric_log enabledMetric snapshots are stored here (enabled by default on 26.3)SELECT count() FROM system.query_metric_log, if it errors, the table is disabled
Query duration > ~1 secondClickHouse® samples once per second by defaultRun a longer query if you see "no metric data found"
SELECT access on system.query_metric_log and system.query_logThe ClickHouse® user needs permissionGRANT SELECT ON system.query_metric_log TO your_user

Enabling query_metric_log

On ClickHouse® 26.3 LTS (CHOps's minimum supported version), query_metric_log is enabled by default. If the table doesn't exist or you want to adjust the collection interval, use the query_metric_log_interval session setting:

-- Check if it's enabled (default 1000ms)
SELECT getSetting('query_metric_log_interval');
 
-- Set for a specific user profile (in users.xml or users.d/)
<!-- /etc/clickhouse-server/users.d/query_metric_log.xml -->
<clickhouse>
  <profiles>
    <default>
      <!-- Collect interval in milliseconds (default 1000). Set to 0 to disable. -->
      <query_metric_log_interval>1000</query_metric_log_interval>
    </default>
  </profiles>
</clickhouse>

Lower values give more granularity but increase overhead. Setting to 0 disables collection entirely.


Troubleshooting

ProblemCauseFix
"No metric data found"Query was too short (< 1s) or query_metric_log is disabledRun a longer query, or enable query_metric_log in server config
"All metrics are zero"The query was too simple (e.g., SELECT 1)Run a query that actually reads data or does computation
Only Memory and CPU appearThe query didn't use disk, cache, network, etc.Normal for simple queries, run a complex query to see more categories
Charts show flat linesThe query finished in 1-2 seconds (only 1-2 data points)Run a longer query for more detailed timelines
Too many lines in one chartMore than 4 metrics in a category×unit groupAlready handled: CHOps splits into sub-charts of 4. Click legend items to hide specific metrics.
Two charts for the same categoryThe category has metrics with different unitsBy design, "Memory, Bytes" and "Memory, Count" are separate to keep Y axis scales meaningful
Charts look empty after theme switchRare rendering glitchCharts auto-rebuild on theme change. If still empty, click Show Query Metrics again.

Tips

Compare Two Queries

Analyze the slow query and note which categories have high values. Then analyze the fast query. The difference tells you exactly why one is slower.

For example: if the slow query shows high MarkCacheMisses and the fast query shows high MarkCacheHits, the slow query is reading from disk while the fast one reads from cache. The fix is either to warm the cache or increase mark_cache_size.

Diagnose Memory Spikes

If a query is killed with "Memory limit exceeded":

  1. Find the timestamp where peak_memory_usage jumps in the Memory, Bytes chart
  2. Look at what other metrics are rising at the same moment in other charts
  3. If ArenaAllocBytes is rising, the query is building a large aggregation or sort buffer
  4. If the External Operations category appears right after the spike, the query started spilling to disk as a fallback

Check Cache Effectiveness

In the In-Memory Caches, Count charts, compare hits vs misses:

CacheHealthy RatioIf Misses Are High
MarkCache> 90% hitsIncrease mark_cache_size (default 5 GiB)
UncompressedCache> 80% hitsIncrease uncompressed_cache_size (default 0, meaning disabled)
PageCacheVariesOS-level page cache, consider adding more RAM

Detect Disk Spilling

If the External Operations category appears, the query exceeded in-memory limits:

  • ExternalSortWritePart / ExternalSortMerge means ORDER BY spilled. Increase max_bytes_before_external_sort.
  • ExternalAggregationWritePart / ExternalAggregationMerge means GROUP BY spilled. Increase max_bytes_before_external_group_by.
  • ExternalJoinWritePart / ExternalJoinMerge means JOIN spilled. Consider a different JOIN algorithm or increase memory limits.

Spilling is not an error, it's a safety mechanism. But it is significantly slower than in-memory processing.

Understand Time Metrics

When you see both "CPU & Time, Time (μs)" and "CPU & Time, Count" charts:

  • The Time chart shows RealTimeMicroseconds (wall clock), UserTimeMicroseconds (CPU in user mode), SystemTimeMicroseconds (CPU in kernel mode)
  • If RealTime >> UserTime + SystemTime, the query is waiting (IO, locks, network)
  • If UserTime is dominant, the query is CPU-bound (consider materialized views or projections to reduce computation)
  • The Count chart shows SoftPageFaults, HardPageFaults, ContextSwitches, high hard page faults mean the working set doesn't fit in RAM