Row order dominates the codec
Why does the order of the rows change the compression ratio more than the choice of codec?
The encoder looks at the values
Serialization settled where the description of the data lives (in a schema, in a registry, in a footer). From here on we assume that question is answered. We now have a homogeneous run of values: one column, one type, many values.
What is new in this stage is that the encoder gets to look at the data before it decides how to represent it.
This is the first time here that the representation depends on the values and not only on the type. Until now the schema fixed the layout: an int64 took eight bytes because it was declared int64. Here the encoder inspects a block of values and may find that all 128 values lie between 1,000,000 and 1,000,255. Then it spends eight bits on each value. Nothing in the schema said that. The schema said 64 bits. The schema was not wrong, but it was loose: it gave an upper bound, and people mistook the upper bound for a description.
The new constraint is that the choice must be made from a bounded window of data (one block), must be recorded somewhere so the reader can undo it, and can turn out to be wrong if the distribution changes later. The constraint that stops mattering is field extent (where a field starts and ends) and field identity. The governing question becomes:
Given the real distribution of a column, what is the fewest bits per value that still lets you decode at memory-bandwidth speed, and, better, that lets you answer the query without decoding at all?
The last part of that sentence is the reason this stage exists separately from general-purpose compression (General-purpose compression). Many introductions miss it.
Operability means: you can still do useful work (compare, search, add up) on the encoded form.
The floor that everything is measured against
Before looking at any technique, let us set a numeric target. We use entropy: the minimum average number of bits per symbol. For values drawn from a distribution p, the order-0 entropy is
- how often each value occurs
- the minimum average number of bits per symbol
Data table
| Frequency of the commoner value p | Entropy H (bits per value) |
|---|---|
| 0.5 | 1 |
| 0.506 | 1 |
| 0.512 | 1 |
| 0.519 | 0.999 |
| 0.525 | 0.998 |
| 0.531 | 0.997 |
| 0.537 | 0.996 |
| 0.544 | 0.994 |
| 0.55 | 0.993 |
| 0.556 | 0.991 |
| 0.562 | 0.989 |
| 0.569 | 0.986 |
| 0.575 | 0.984 |
| 0.581 | 0.981 |
| 0.587 | 0.978 |
| 0.594 | 0.974 |
| 0.6 | 0.971 |
| 0.606 | 0.967 |
| 0.612 | 0.963 |
| 0.619 | 0.959 |
| 0.625 | 0.954 |
| 0.631 | 0.95 |
| 0.637 | 0.945 |
| 0.644 | 0.94 |
| 0.65 | 0.934 |
| 0.656 | 0.928 |
| 0.662 | 0.922 |
| 0.669 | 0.916 |
| 0.675 | 0.91 |
| 0.681 | 0.903 |
| 0.687 | 0.896 |
| 0.694 | 0.889 |
| 0.7 | 0.881 |
| 0.706 | 0.874 |
| 0.712 | 0.866 |
| 0.719 | 0.857 |
| 0.725 | 0.849 |
| 0.731 | 0.84 |
| 0.737 | 0.831 |
| 0.744 | 0.821 |
| 0.75 | 0.811 |
| 0.756 | 0.801 |
| 0.762 | 0.791 |
| 0.769 | 0.78 |
| 0.775 | 0.769 |
| 0.781 | 0.758 |
| 0.787 | 0.746 |
| 0.794 | 0.734 |
| 0.8 | 0.722 |
| 0.806 | 0.709 |
| 0.812 | 0.696 |
| 0.819 | 0.683 |
| 0.825 | 0.669 |
| 0.831 | 0.655 |
| 0.837 | 0.64 |
| 0.844 | 0.625 |
| 0.85 | 0.61 |
| 0.856 | 0.594 |
| 0.862 | 0.578 |
| 0.869 | 0.561 |
| 0.875 | 0.544 |
| 0.881 | 0.526 |
| 0.887 | 0.508 |
| 0.894 | 0.489 |
| 0.9 | 0.469 |
| 0.906 | 0.449 |
| 0.912 | 0.428 |
| 0.919 | 0.407 |
| 0.925 | 0.385 |
| 0.931 | 0.362 |
| 0.937 | 0.338 |
| 0.944 | 0.313 |
| 0.95 | 0.287 |
| 0.956 | 0.26 |
| 0.962 | 0.231 |
| 0.969 | 0.201 |
| 0.975 | 0.169 |
| 0.981 | 0.135 |
| 0.987 | 0.098 |
| 0.994 | 0.056 |
| 1 | 0.001 |
"Order-0" means we only use how often each value occurs, and we ignore which values come before or after it. The minimum size for a column of N values is then N × H bits.
This is the economic case for the whole stage: lightweight encodings capture nearly all of the order-0 gain at almost no CPU cost. They leave the general-purpose codec only the last few percent, plus whatever structure of higher order remains. It is also why the pipeline order in Bytes become an output is forced and not just a habit.
But these encodings go beyond order-0, and this is where their real power is. Take one million distinct 64-bit timestamps. Every value is unique, so the order-0 entropy is log₂(10⁶) ≈ 20 bits per value. That is a hard floor for any order-0 method, including a dictionary. Now sort the timestamps. The differences between neighbours are small and regular. The conditional entropy (the entropy of a value given the value before it) falls to two or three bits. The data and the information are the same, but the achievable size is about 7× smaller (20 ÷ 3). This gain came from a decision that has nothing to do with the codec.
This leads to the first topic. It is the answer to Nikhil's problem.
Row order: the decision that dominates the codec
Most lists of encodings do not mention sort order. In practice, the order of the rows changes the compression ratio more than the choice among all the encodings below, often by a factor of ten, and it costs nothing at read time.
Let us derive this for run-length encoding (RLE, explained fully in a moment: store a value and how many times it repeats). It is the clearest case.
- A column has k distinct values and N rows. If the rows are in random order, the number of runs is about N × (1 − 1/k). For any k above a few, this is essentially N: every row starts a new run. The ratio is 1 (no gain), and there is even a small expansion.
- If the rows are sorted, there are exactly k runs. The ratio is N / k.
- rows
- distinct values
Data table
| Distinct values k | series | Compression ratio |
|---|---|---|
| 2 | sorted | 500,000× |
| 2.29 | sorted | 437,000× |
| 2.62 | sorted | 382,000× |
| 3 | sorted | 333,000× |
| 3.44 | sorted | 291,000× |
| 3.93 | sorted | 254,000× |
| 4.5 | sorted | 222,000× |
| 5.15 | sorted | 194,000× |
| 5.9 | sorted | 169,000× |
| 6.76 | sorted | 148,000× |
| 7.73 | sorted | 129,000× |
| 8.85 | sorted | 113,000× |
| 10.1 | sorted | 98,700× |
| 11.6 | sorted | 86,200× |
| 13.3 | sorted | 75,300× |
| 15.2 | sorted | 65,800× |
| 17.4 | sorted | 57,400× |
| 19.9 | sorted | 50,200× |
| 22.8 | sorted | 43,800× |
| 26.1 | sorted | 38,300× |
| 29.9 | sorted | 33,400× |
| 34.2 | sorted | 29,200× |
| 39.2 | sorted | 25,500× |
| 44.9 | sorted | 22,300× |
| 51.4 | sorted | 19,500× |
| 58.8 | sorted | 17,000× |
| 67.3 | sorted | 14,900× |
| 77.1 | sorted | 13,000× |
| 88.2 | sorted | 11,300× |
| 101 | sorted | 9,900× |
| 116 | sorted | 8,650× |
| 132 | sorted | 7,550× |
| 152 | sorted | 6,600× |
| 174 | sorted | 5,760× |
| 199 | sorted | 5,030× |
| 227 | sorted | 4,400× |
| 260 | sorted | 3,840× |
| 298 | sorted | 3,360× |
| 341 | sorted | 2,930× |
| 391 | sorted | 2,560× |
| 447 | sorted | 2,240× |
| 512 | sorted | 1,950× |
| 586 | sorted | 1,710× |
| 671 | sorted | 1,490× |
| 768 | sorted | 1,300× |
| 879 | sorted | 1,140× |
| 1.01K | sorted | 993× |
| 1.15K | sorted | 868× |
| 1.32K | sorted | 758× |
| 1.51K | sorted | 662× |
| 1.73K | sorted | 578× |
| 1.98K | sorted | 505× |
| 2.27K | sorted | 441× |
| 2.59K | sorted | 385× |
| 2.97K | sorted | 337× |
| 3.4K | sorted | 294× |
| 3.89K | sorted | 257× |
| 4.46K | sorted | 224× |
| 5.1K | sorted | 196× |
| 5.84K | sorted | 171× |
| 6.69K | sorted | 150× |
| 7.66K | sorted | 131× |
| 8.76K | sorted | 114× |
| 10K | sorted | 99.7× |
| 11.5K | sorted | 87.1× |
| 13.2K | sorted | 76× |
| 15.1K | sorted | 66.4× |
| 17.2K | sorted | 58× |
| 19.7K | sorted | 50.7× |
| 22.6K | sorted | 44.3× |
| 25.9K | sorted | 38.7× |
| 29.6K | sorted | 33.8× |
| 33.9K | sorted | 29.5× |
| 38.8K | sorted | 25.8× |
| 44.4K | sorted | 22.5× |
| 50.9K | sorted | 19.7× |
| 58.2K | sorted | 17.2× |
| 66.6K | sorted | 15× |
| 76.3K | sorted | 13.1× |
| 87.3K | sorted | 11.4× |
| 100K | sorted | 10× |
| 2 | random order | 2× |
| 2.29 | random order | 1.78× |
| 2.62 | random order | 1.62× |
| 3 | random order | 1.5× |
| 3.44 | random order | 1.41× |
| 3.93 | random order | 1.34× |
| 4.5 | random order | 1.29× |
| 5.15 | random order | 1.24× |
| 5.9 | random order | 1.2× |
| 6.76 | random order | 1.17× |
| 7.73 | random order | 1.15× |
| 8.85 | random order | 1.13× |
| 10.1 | random order | 1.11× |
| 11.6 | random order | 1.09× |
| 13.3 | random order | 1.08× |
| 15.2 | random order | 1.07× |
| 17.4 | random order | 1.06× |
| 19.9 | random order | 1.05× |
| 22.8 | random order | 1.05× |
| 26.1 | random order | 1.04× |
| 29.9 | random order | 1.03× |
| 34.2 | random order | 1.03× |
| 39.2 | random order | 1.03× |
| 44.9 | random order | 1.02× |
| 51.4 | random order | 1.02× |
| 58.8 | random order | 1.02× |
| 67.3 | random order | 1.02× |
| 77.1 | random order | 1.01× |
| 88.2 | random order | 1.01× |
| 101 | random order | 1.01× |
| 116 | random order | 1.01× |
| 132 | random order | 1.01× |
| 152 | random order | 1.01× |
| 174 | random order | 1.01× |
| 199 | random order | 1.01× |
| 227 | random order | 1× |
| 260 | random order | 1× |
| 298 | random order | 1× |
| 341 | random order | 1× |
| 391 | random order | 1× |
| 447 | random order | 1× |
| 512 | random order | 1× |
| 586 | random order | 1× |
| 671 | random order | 1× |
| 768 | random order | 1× |
| 879 | random order | 1× |
| 1.01K | random order | 1× |
| 1.15K | random order | 1× |
| 1.32K | random order | 1× |
| 1.51K | random order | 1× |
| 1.73K | random order | 1× |
| 1.98K | random order | 1× |
| 2.27K | random order | 1× |
| 2.59K | random order | 1× |
| 2.97K | random order | 1× |
| 3.4K | random order | 1× |
| 3.89K | random order | 1× |
| 4.46K | random order | 1× |
| 5.1K | random order | 1× |
| 5.84K | random order | 1× |
| 6.69K | random order | 1× |
| 7.66K | random order | 1× |
| 8.76K | random order | 1× |
| 10K | random order | 1× |
| 11.5K | random order | 1× |
| 13.2K | random order | 1× |
| 15.1K | random order | 1× |
| 17.2K | random order | 1× |
| 19.7K | random order | 1× |
| 22.6K | random order | 1× |
| 25.9K | random order | 1× |
| 29.6K | random order | 1× |
| 33.9K | random order | 1× |
| 38.8K | random order | 1× |
| 44.4K | random order | 1× |
| 50.9K | random order | 1× |
| 58.2K | random order | 1× |
| 66.6K | random order | 1× |
| 76.3K | random order | 1× |
| 87.3K | random order | 1× |
| 100K | random order | 1× |
With N = 1,000,000 and k = 100 this is a swing from 1× to 10,000×. Same column, same codec, four orders of magnitude, decided only by row order.
For delta encoding (store the difference from the previous value, explained below) the situation is worse than "no gain". On data with no sequential relation, delta is harmful. The difference of two independent random values has about twice the range of the original values, so you pay about one extra bit per value. Delta is a bet on correlation between neighbours, and losing the bet has a real cost.
Sorting also buys a second benefit through the same act. A file format stores min/max statistics for each chunk of rows (a "zone map"). A query like WHERE event_time > X can skip every chunk whose maximum is below X. But skipping works only when different chunks cover non-overlapping ranges. On a sorted column, every chunk has its own range and skipping is nearly perfect. On an unsorted column, every chunk spans almost the whole domain and skipping is worth exactly nothing. Compression and skipping are two returns on one decision.
The catch: sort order is a scarce resource
You get one physical order per file. Sort by (A, B), and column A gets long runs while column B gets runs only inside each run of equal A values, so the leading column takes almost all of the compression benefit. Which key to choose, how a composite key fails, and when space-filling curves such as Z-order are worth their partial locality are storage layout's questions; what matters here is that the answer decides the ratio.
Streaming ingestion cannot sort globally. Rows arrive in time order, so each file is ordered only locally. Sortedness is something writes consume and only rewriting restores, and the rewrite is compaction: a background job that merges small files into larger, re-sorted files. So compaction improves the compression ratio and the skipping, not only the file count. This benefit is almost never counted when people size the compaction budget.