Why does loading a columnar table in sorted order shrink it far more than loading it randomly?
answer
- Encodings look at neighbours, not the whole column
- Random data breaks runs before they start
- One physical order per table, so it is a choice
- Low cardinality first maximises run length
basics
~20 sSorting puts equal values next to each other, so run-length encoding sees long runs and delta encoding sees small differences instead of noise. The same data can compress several times better sorted, and correlated columns downstream of the sort key gain too.
solid answer
~50 sLightweight encodings exploit **locality**, not global frequency. Run-length encoding stores (value, count) pairs, so on a randomly ordered column of three distinct values almost every run has length one and the encoding saves nothing — it can even expand the stream. Sort by that column and the same data becomes a handful of runs covering millions of rows. Dictionary encoding benefits indirectly: the code stream itself becomes long stretches of the same integer, which then run-length encodes. Numeric columns sorted ascending produce small, uniform deltas that bit-pack into a few bits per value instead of 32 or 64. Columns *correlated* with the sort key — city under country, model under manufacturer — inherit much of the locality for free. The costs: a table has only one physical order, sorting during load burns CPU and memory, and the order you pick for ratio may not be the one your predicates want.
code
sql · 7 lines-- unsorted load: status values interleave, runs are ~1 row
INSERT INTO events SELECT * FROM staging;
-- sorted load: low cardinality first, then rising cardinality
INSERT INTO events
SELECT * FROM staging
ORDER BY status, country, event_time;go deeper
Recall the core sentence: sorting groups equal values together, and encodings that store runs or differences only pay off when equal or nearby values are adjacent.
Explain the mechanics for each encoding — run lengths, dictionary code streams, delta widths — and state the low-cardinality-first ordering rule with its reasoning.
Show the trade-off in production: one physical order, the tension with block skipping, what streaming ingest does to sortedness, and how you measure per-column bytes before committing.
Frame it as a platform policy: which tables get a sort or reorganise budget, how ordering interacts with storage spend and scan latency, and who owns the choice when teams want different orders.
## Encodings are local, so order is everything The encodings that give columnar storage its ratio — run-length, delta, and the dictionary code streams that feed them — all look at **neighbouring values inside one block**. None of them has a global view of the column. That single fact explains the whole phenomenon: the compressed size of a column is a function not just of *which* values it contains but of the *order* they appear in. ## Run-length encoding is the extreme case Run-length encoding replaces a stretch of identical values with the value and a count. Take a `status` column with three distinct values over ten million rows. - **Random order:** the probability that the next row repeats the current value is roughly one in three, so runs average around one or two rows. The encoding must store a value and a count per run, so the output can be as large as the input — or larger. Engines detect this and fall back to another encoding, but the win is gone either way. - **Sorted on `status`:** the column becomes three runs. Ten million values collapse into a handful of pairs. That is a difference of orders of magnitude on the same data, produced purely by ordering. ## Dictionary streams inherit the effect Dictionary encoding itself is order-independent — the dictionary holds the same distinct values either way, and the code width depends only on cardinality. But the *code stream* is just an integer column, and it is encoded in turn. Sorted data gives `0 0 0 0 1 1 1 2 2 2`, which run-length or bit-packs to almost nothing. Shuffled data gives an incompressible integer stream, and the general codec on top finds much less repetition too. So sorting improves dictionary-encoded columns even though the dictionary is unchanged. ## Numeric columns: deltas and the frame of reference A sorted numeric column produces small, non-negative deltas, and small deltas bit-pack into a few bits each. The same values shuffled produce deltas that span the full range of the column, sometimes needing *more* bits than the raw values because they are signed. A per-block minimum plus offsets — the frame-of-reference idea — is likewise narrow only when a block's values are close together, which sortedness guarantees and randomness destroys. ## The correlation cascade Sorting on one column also delivers locality to every column functionally dependent on or correlated with it. Sort on `country` and the `city`, `currency` and `timezone` columns become nearly sorted too, because their values are determined by (or strongly clustered under) the sort column. This is why the benefit of one good sort key often shows up across half the table. ## Choosing the order Two rules of thumb, and one tension between them. - **For ratio:** put the **lowest-cardinality** column first. It forms the longest runs, and each subsequent column still gets locality within each run of the previous one. Order the remaining sort columns by ascending cardinality for the same reason. - **For skipping work:** put the column your queries filter on most in front, so block-level metadata can eliminate blocks. That is a different objective and the two frequently disagree. The resolution is workload-driven: a table whose storage bill dominates and whose queries scan broadly leans toward the ratio ordering; a table with a hot, selective predicate leans toward that predicate. Compound orders can serve both when the selective column is also low-cardinality. ## What it costs Sorting is not free. It costs CPU and memory at load time, and a streaming ingest cannot maintain a global order — data arrives in whatever order it arrives, so the sortedness is only as good as the batch. Systems that reorganise in the background pay that cost later, in a rewrite that reads and rewrites data already stored. And there is **only one physical order**; the second query pattern gets whatever locality it happens to inherit. ## Measuring it Don't argue about it — measure. Load a representative sample twice, once as-is and once sorted, and compare compressed bytes, ideally per column. Per-column figures tell you which columns actually responded and which are incompressible regardless; that is what tells you whether a different sort key would help or whether the column is simply high-entropy. ## Interview framing The crisp answer: *encodings compress locality, sorting manufactures locality*. Everything else — dictionary code streams, delta widths, correlated columns — follows from that sentence.
- If sorting helps so much, why not sort every table on its lowest-cardinality column?Because a table has one physical order and pruning wants a different one. Ordering by a two-value flag maximises the ratio but leaves your selective predicates — a timestamp, a tenant id — scattered across every block, so queries read everything. The ordering is a workload decision balancing bytes stored against blocks skipped.
- How does a streaming ingest change this picture?Rows arrive in arrival order, so sortedness holds only within each written batch. Small, frequent batches give short runs and poor ratio; larger batches sorted before write do much better. Engines that reorganise in the background recover the ratio later, but you pay for it in rewrite I/O rather than getting it free.
- Would sorting help a column of random UUIDs?Sorting on that column would make its own deltas smaller, but it would scramble every other column and the UUIDs still carry near-maximum entropy. As a non-leading column it gains essentially nothing, because there is no repetition or numeric structure for run-length or delta encoding to exploit.
saying these in an interview costs you the question
- Thinks compression depends only on distinct value count
- Claims run-length encoding always shrinks a low-cardinality column
- Believes sorting one column cannot help any other
- Assumes a table can maintain several physical orders
- Ignores that ordering also drives block skipping