learning.lab
Query Speed· 14/33

Skip Indexes

Data-skipping indexes

The primary key only helps if your query filters on a column that is part of ORDER BY. Skip indexes give ClickHouse a way to avoid reading granules based on other columns too — without physically reordering anything.

What a skip index actually stores

Unlike a Postgres index, a skip index doesn't point at rows. It stores a small summary — per group of granules — and uses that summary to decide whether a granule could possibly contain a match. If it can't, the granule is skipped entirely: never decompressed, never scanned.

ALTER TABLE requests
    ADD INDEX status_idx status_code TYPE minmax GRANULARITY 4;
minmax index on status_code — query: WHERE status_code = 500
timestatus
10:01200
10:02200
10:03304
skipped
timestatus
10:10200
10:11200
10:12200
skipped
timestatus
10:20200
10:21500
10:22500

The first two granules have a min/max range of 200–304 — the index proves 500 cannot be in there, so they're skipped. The third granule's range includes 500, so it gets scanned. No sorting was required for this to work; the index just needed the data to already be somewhat clustered — which is usually true if status_code correlates at all with time, or with a column earlier in ORDER BY.

The index types

  • minmax — stores min and max per block of granules. Cheap, effective for numeric/date columns with any locality.
  • set(N) — stores up to N distinct values per block. Good for low-cardinality columns where exact-match filtering is common.
  • bloom_filter — probabilistic membership test; can have false positives (scans a granule unnecessarily) but never false negatives (never skips a granule that has a match).
  • tokenbf_v1 / ngrambf_v1 — bloom filters over tokens or n-grams, for accelerating LIKE / substring search inside text columns.
Common mistake
A skip index only helps when values are locally clustered — not uniformly scattered across every granule. Adding a minmax index on a column with random values spread evenly through the whole table gives every granule the same wide min/max range, so nothing ever gets skipped. Check correlation with your existing sort order before adding one.
Under the hood
GRANULARITY 4 means one index entry covers 4 granules (so 4 × 8192 = 32768 rows by default), not one entry per granule. Larger granularity means a smaller index but coarser skipping.