ClickHouse, explained with animations.
Visual guides to how ClickHouse stores, skips, merges and serves data—so you can use it efficiently from day one.
Why is ClickHouse fast? It reads less. #
One query, three cuts. Partitions, indexes and columns take about 8 TiB of raw data—about 1.7 TiB compressed on disk—down to about 0.9 MiB read.
Check it on your own table. Put EXPLAIN indexes = 1 in front of a query: ClickHouse prints how many parts and granules each index keeps, without running the query.
EXPLAIN indexes = 1SELECT device_type, sum(revenue)FROM eventsWHERE event_type = 'purchase' AND timestamp >= now() - INTERVAL 7 DAY AND revenue > 500GROUP BY device_typeWhat it prints
ReadFromMergeTree (default.events)Indexes: Min-Max Keys: timestamp Parts: 8/90 Granules: 147456/1658880 Partition Keys: toDate(timestamp) Parts: 8/8 Granules: 147456/147456 PrimaryKey Keys: event_type timestamp Parts: 8/8 Granules: 252/147456 Skip Name: idx_rev Description: minmax GRANULARITY 1 Parts: 8/8 Granules: 168/252These are the example's counts. Each reads kept/before, so 8/90 is 8 of 90 parts, one per day here, and each step starts from what the step above kept.
Min-Max and Partition are step 2 of the figure, partition pruning: the first checks each part's smallest and largest timestamp, the second its partition value.
PrimaryKey and Skip are step 3: the primary index keeps 252 granules and the skip index leaves 168 to read. The column cut, step 4, is not an index and is not listed.
Releases before 26.6 print MinMax instead of Min-Max. See the reference, EXPLAIN .
Columnar storage
Read only the columns you need. #
Columns are stored and compressed separately. The same query reads 4 of 7 columns and skips tenant_id, event_id and the wide payload: about 8.5 GiB instead of 4.1 TiB, before any rows are skipped.
payload, a JSON string, is 1.5 of the 1.7 TiB on disk.
When queries need fields from inside it, the JSON type lets them read single paths instead of the whole string. See the official guide, Use JSON where appropriate .
Primary index · skip index
Sort once. Skip granules before reading. #
Two small indexes decide what to read. The same query: the primary index finds the key range, a skip index drops granules whose min/max cannot match. Across the rolling 7‑day window that leaves about 168 of ≈ 1.66 M granules; the 8 below show how.
The 8 granules are neighbours in one part of partition 2025-06-08, the day the 7‑day window starts.
That part holds the rows inserted 08:00–12:00, sorted by event_type and timestamp.
Across the whole 7‑day window, the primary index keeps about 252 granules and the skip index leaves about 168 of them: ≈ 0.9 MiB to read, the total in the first figure.
Materialized views · table engines
Prepare results at insert time. #
Views feed small, pre-aggregated tables. Each insert is summarized once, as it arrives, and the table engine merges the summaries in the background. Dashboards read a few hundred rows instead of billions.