An N-row × 6-column table (id, category, region, status, score, flag) is laid out two ways. In row-major storage a whole record sits contiguously, so answering a query still means reading every column of every unpruned row. In columnar storage each column is its own contiguous run, so a query touching one column reads only that column. The top canvas draws the table as a literal grid (rows = table rows, columns = table columns); the bottom canvas draws a bar per column comparing raw vs. dictionary-compressed bytes.
Dictionary encoding (per column):
bits = ceil(log2(distinct_values))
bytes = min(distinct_values × 4 + rows × bits / 8, rows × 4)
(naturally reduces to "raw size" when distinct_values ≈ rows: id, score)
Zone-map predicate pushdown (per row group):
skip group if [qMin,qMax] ∩ [groupMin(id), groupMax(id)] = ∅
→ only its footer min/max stats are read to decide this,
never the row-group's actual data
Row-group boundaries (generalized to any row count / group count):
group g spans rows [ floor(g·N/G), floor((g+1)·N/G) − 1 ]
— verified numerically to match exact division when N%G==0,
and to still tile every row exactly once when it doesn't
(e.g. N=48, G=5 → group sizes 9,10,9,9,9, not a crash on N/G)
- Storage layout — row-major must read every column of a surviving row group; columnar reads only the
score column.
- Dictionary encoding — replaces repeated values with small integer codes into a small dictionary; helps low-cardinality columns (category: 5, region: 4, status: 3, flag: 2) far more than near-unique ones (id, score).
- Row groups — the id column is sorted, so each group covers a contiguous id range; more/smaller groups make the query-range prune more precisely.
- Query id BETWEEN [min,max] — row groups whose id range never overlaps the filter turn gray (pruned) and cost nothing to scan.
- Scan playback — animates the actual read order: row-major sweeps row by row across all columns; columnar sweeps the score column alone, one row group at a time, skipping pruned groups instantly.
Real-world relevance: this is exactly how Parquet/ORC files are read by Spark SQL and Hive on top of HDFS — column pruning plus row-group (stripe) statistics are why a columnar warehouse query can scan megabytes instead of the whole terabyte table.