advanced26 min studyLesson 292 in Engineering

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

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.

w=⌈log⁡2K⌉,Sideal=Sdict+⌈Nw8⌉w=\lceil\log_2 K\rceil,\qquad S_{\rm ideal}=S_{\rm dict}+\left\lceil\frac{Nw}{8}\right\rceil
For K at least one dictionary values and N nonnull occurrences, w is ideal ID width in bits and S is bytes before run headers, padding, page headers, levels, or generic compression. A single-value vocabulary theoretically needs zero ID bits, but actual format framing still costs space.↔ Scroll wide equations horizontally.

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.

Srun≈H+V,Sliteral≈⌈Lw8⌉S_{\rm run}\approx H+V,\qquad S_{\rm literal}\approx\left\lceil\frac{Lw}{8}\right\rceil
For one illustrative run of L identical w-bit IDs, H is the run-header byte cost and V is the repeated-value byte cost. The literal comparison excludes its own framing. Exact Parquet hybrid framing and ORC encodings have specific rules; this estimate only explains amortization over a long run.↔ Scroll wide equations horizontally.

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.

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.

  1. Four dictionary entries require 4 × 8 = 32 bytes. Four possible IDs require two bits each.

  2. 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.

  3. 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 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.

Explore the model

Interactive lab

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.

A dictionary lookup still has a working setA 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. Current readings: Hits / requests: 0 / 16; Hit rate: 0%; Address-stream period: 16 addresses. All parameters are adjustable with the labeled controls below.Fully associative LRU cache after 16 deterministic requests: 0 hits and 16 misses.Most recent 16 requestsaddress = i × 3 mod 160M3M6M9M12M15M2M5M8M11M14M1M4M7M10M13MCurrent cache order · oldest → newest471013Latest address 13 · miss: inserted as newestToy address stream · period 16 · starts empty
Cached addressMiss / evictionMost recent request
Hits / requests
0 / 16
Hit rate
0%
Address-stream period
16 addresses
t = 0 s

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.

Check your understanding

A Parquet column chunk contains an initial dictionary page, several dictionary-encoded data pages, and later plain-encoded data pages. What should a correct reader do?

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.

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.