Dictionary IDs and runs exploit different repetition
Estimate dictionary costs, distinguish cardinality from adjacency, and handle mixed dictionary/plain pages within a column chunk.
On this page
The question for today
What you’ll understand
- Estimate dictionary-plus-index payload for a stated cardinality and width.
- Explain when repeated dictionary IDs benefit from run-length encoding.
- Interpret dictionary fallback without assuming one encoding or globally stable IDs.
A column can repeat values without placing equal values next to each other. Dictionary encoding benefits from a small set of distinct values; run-length encoding benefits from long adjacent runs. Combining them can be effective, but they remove different redundancy. Understanding that difference helps explain why sorting, a changed data distribution, or a writer's fallback policy can alter both size and CPU cost even when row count stays constant.
Build one local vocabulary and store references
A dictionary stores distinct values once and replaces occurrences with integer IDs. With K entries, an ideal fixed-width representation needs enough bits to distinguish IDs zero through K minus one. The dictionary itself still costs bytes, and variable-length entries need lengths or other representation metadata. For short columns or nearly unique values, the dictionary plus IDs can cost more than directly encoded values. Evaluate cardinality over the actual dictionary scope rather than the entire table, since separate chunks can have different vocabularies and distributions.
Parquet dictionary pages store the values, while dictionary-encoded data pages store IDs using the specified hybrid RLE/bit-packing representation. At most one dictionary page occurs in a column chunk and it precedes its data pages. A writer may switch later pages to a different permitted encoding when dictionary growth becomes unattractive, so one chunk can contain both dictionary and plain pages. A reader must obey each page's encoding and its chunk's dictionary. The same numeric ID in another chunk need not mean the same category, which matters for joins and aggregations performed directly over IDs.
A low cardinality does not imply long runs
Consider IDs alternating zero, one, zero, one. The vocabulary is tiny, but adjacent equal runs are short. A block containing ten thousand zeros followed by ten thousand ones has the same two IDs and much better run structure. Hybrid encoding can choose runs for repetition and bit-packed literals for irregular stretches. Parquet's hybrid RLE is used for its supported streams, including dictionary indices and structural levels; it is not a blanket promise that arbitrary physical integer payload uses that encoding. ORC has its own type-specific RLE variants and framing rules.
Sorting can improve runs and statistics, but rearranging data has a cost and may alter useful locality on another key. Preserve row alignment across columns when reordering, and account for downstream consumers that require a particular order. Dictionary decoding can also involve indirection and cache access; reducing file bytes does not imply every downstream operator becomes faster. Some engines compare encoded IDs efficiently when dictionaries are compatible, while others materialize values. Compare the full required operation, including dictionary reconciliation or decoding, rather than benchmarking a code stream in isolation.
Check size and locality at the dictionary boundary
Inspect per-page encoding metadata, dictionary size, fallback points, and decoded equality results on representative data. Include high-cardinality and changing-cardinality groups, not only a small categorical sample. A metadata list of encodings used somewhere in a chunk does not establish that every page uses dictionary IDs. Verify null handling separately because structural validity is not an ordinary dictionary entry for a nonnull value. Compatibility tests should include mixed-encoding chunks so a reader cannot accidentally pass by assuming the writer's most common path is the only path.
The cache lab illustrates lookup working sets only. Reset, set both C=4 and s=4, predict a four-address cycle with twelve hits in sixteen requests, then Play or Step. Reset and set both C=2 and s=4, predict misses throughout that cycle, then Play or Step. Each Reset starts an empty cache followed by sixteen modeled requests; controls do not specify dictionary cardinality, encoded byte width, or actual Parquet addresses. The lesson is that a lookup table's hot working set can matter independently of how few bits its IDs occupy.
Work it through
Estimate a small categorical dictionary
A nonnullable column has 1,000 eight-byte values drawn from four distinct fixed-width values. Estimate an ideal dictionary representation with fixed-width packed IDs, excluding every header and generic compression.
Four dictionary entries require 4 × 8 = 32 bytes. Four possible IDs require two bits each.
One thousand IDs require 2,000 bits, or 250 bytes. Total ideal payload is 32 + 250 = 282 bytes, compared with 8,000 bytes of direct fixed-width values.
The payload ratio is 8,000/282, approximately 28.37. Actual pages add dictionary framing, hybrid run/literal headers, padding, and other metadata; run structure or generic compression can change the final result.
The idealized dictionary saves 7,718 bytes in this example, but the estimate does not imply a globally shared ID mapping or one encoding across every page.
The living notebook
Explore the model
Interactive lab · cache
A dictionary lookup still has a working set
Reset, set both C=4 and s=4, predict twelve hits among sixteen initial requests, then Play or Step. Reset and re-enter C=2 and s=4 to compare a too-small cache. The stream is (i·s) mod16 with at most 4,096 requests; it is not a dictionary encoder, RLE codec, or Parquet address trace.
- Hits / requests
- 0 / 16
- Hit rate
- 0%
- Address-stream period
- 16 addresses
A fully associative LRU cache starts empty and requests address (i·s) mod 16. Hits move an address to the newest position; misses evict the oldest when full. Playback starts with 16 requests and adds two per second, capped at 4096 explicitly simulated requests. This toy stream is not a processor-cache benchmark. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forVocabulary size, adjacent repetition, and decoded lookup locality are three distinct sources of cost.
Pause & reason
Check your understanding
Your turn
Try it yourself
Two ID sequences each use only zero and one. The first alternates for 1,000 positions; the second has 500 zeros followed by 500 ones. Explain which redundancy dictionary encoding sees and which additional redundancy run-length encoding sees.
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Count distinct IDs separately from adjacent equal stretches.
Compare with the solution
Both have cardinality two, so both permit one-bit ideal dictionary IDs. The alternating sequence has no long equal-ID runs, whereas the grouped sequence has two runs of length 500 that can amortize run headers. Exact file size depends on the codec's framing and padding, and reordering must preserve aligned rows and any required semantics.
Keep the idea
Take it with you
- Dictionary encoding exploits a small vocabulary; run-length encoding exploits adjacent repetition.
- Parquet dictionaries are scoped to column chunks and data pages can fall back to another encoding.
- Inspect byte size, page encodings, and downstream decoding costs together.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.