Compression — the column store's superpower

When adjacent bytes are the same column they are the same type and often the same or similar values, so run-length encoding and dictionary encoding deliver 5-20x compression that fails completely on row pages — and the encoded form is what the executor reads.

Previously

Once adjacent bytes are the same column, compression that fails on row pages suddenly works — and the win is per-column, not generic gzip.

Scene 03

Compression: the column store's superpower

  1. Watch
  2. Try it
  3. Predict
  4. Capture
COLUMN FILEcountry.binWHERE country = 'US'MODE: RAWBYTES0 B1 string tilesRAW VALUESOPERATES ON ENCODED FORM:memcmp(bytes, 'US') // 2-byte string compare per rowRaw: each row carries its own 2-byte country code. WHERE country='US' is a per-row string compare.
What to watch for

country.bin streams in — 20 rows of a low-cardinality string column (US, DE, JP). The byte counter top-right tracks the cost of the raw layout: 2 bytes per row, 40 bytes total. Notice how adjacent rows often share the same value — that's the property the next mode will exploit.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Column.rleEncode(values)
collapse adjacent repeats into (value, run_length) tuples
def rle_encode(values):
runs = []
for v in values:
last = runs[-1] if runs else None
if last and last.value == v:
last.run += 1 # extend current run
else:
runs.append(Run(value=v, run=1))
return runs # on-disk form for RLE columns
Executor.countWhere(column, value='US')
the same WHERE clause, three encodings, three scans
def count_where(col, target):
if col.mode == RAW:
return sum(1 for v in col.values if v == target)
if col.mode == RLE:
# no decompression — sum run lengths of matching runs
return sum(r.run for r in col.runs if r.value == target)
if col.mode == DICT:
code = col.dictionary.codeFor(target) # 'US' -> 2
return sum(1 for c in col.codes if c == code)
Column.dictEncode(values)
intern each distinct value; column becomes integer codes
def dict_encode(values):
dictionary = {} # value -> small int code
codes = []
for v in values:
if v not in dictionary:
dictionary[v] = len(dictionary) # next code
codes.append(dictionary[v])
# cardinality check — the LowCardinality(String) trap
# fires when len(dictionary) approaches len(values).
return codes, dictionary

Where this sits in Build a columnar OLAP store (ClickHouse / Druid style)

Scene 03 of 13, in the Speedups act — Compression and vectorized execution — where the orders of magnitude live.. Adjacent column values are same-type and often similar, so RLE and dictionary encoding deliver 5–20× shrinkage that fails completely on row pages.

Up next. The bytes on disk are now tiny. But pulling tiny bytes through the CPU one row at a time still leaves a 50x performance win on the table — it depends on how the executor walks those bytes.

All 13 scenes in Build a columnar OLAP store (ClickHouse / Druid style) · Every curriculum

Built with Arqly
Every scene in Build a columnar OLAP store (ClickHouse / Druid style) builds on the one before it.All 13 Build a columnar OLAP store (ClickHouse / Druid style) scenes