◐ ai-teacher

🧱 Foundations · Chapter 6

Compression and encoding: why Parquet is 10x smaller

Writing Shillong one crore times costs 80 MB as text. Parquet stores it in a few kilobytes — not by compressing harder, but by noticing it's the same word.

Study this lesson free in the app →

The basics

Encoding rewrites a column's values into a smaller representation using what it knows about them — "these are the same 40 cities over and over", "these timestamps increase by a few seconds each". Compression is a generic algorithm (Snappy, gzip, zstd) that squeezes whatever bytes it is given.

The problem it solves: storage is cheap, but reading is not. Every byte a query scans costs I/O, network and money (BigQuery and Athena literally bill per byte scanned). Fewer bytes → faster, cheaper queries.

The mental model: Parquet does it in two layers, per column, per page:

  1. Encode — dictionary, run-length, delta, bit-packing turn values into tiny integers
  2. Compress — a codec squeezes the encoded page

Columnar layout is what makes step 1 possible: a page full of city values is highly repetitive; a CSV line mixing city, fare and timestamp is not.

Words you must know 5

Dictionary encoding
store each distinct value once, then small ids
RLE (run-length encoding)
"id 0, repeated 3 times"
Bit-packing
use only as many bits as the largest id needs
Delta encoding
store differences between consecutive values
Codec
the general compressor (Snappy, zstd, gzip)

Explained

A month of Meghalaya Cabs rides: 9 GB as CSV, about 900 MB as Parquet.

About half of that win comes from encoding — exploiting what the values are. The codec then works on far friendlier bytes. Understand the first part and you can make files smaller and queries faster by changing nothing but the sort order.

The cricket scorer's shorthand

A scorer doesn't write "no run, no run, no run, no run, one run, no run". He writes dots: . . . . 1 . — a dictionary of symbols instead of words.

A maiden over isn't six dots, it's a single M — a run-length: "nothing, six times".

The total isn't rewritten each ball; he notes the change — +1, +4 — a delta.

At the end of the day he folds the scorebook into an envelope to post it — that's compression, applied to an already tiny record. The shorthand did most of the work.

Parquet's encodings, precisely

Chosen per column chunk by the writer:

Then each page is compressed with the file's codec.

Where the 10× comes from

column       CSV text            Parquet encoding        approx size
                                                        (1 crore rows)
─────────────────────────────────────────────────────────────────────
city         "Shillong" ~8 B     dictionary + RLE        80 MB → KBs*
pickup_ts    "2026-03-14 10:03:27" delta-binary-packed   190 MB → ~15 MB
             19 B
fare_paise   "24500" ~5 B        dictionary/bit-packed   50 MB → ~12 MB
is_cash      "false" 5 B         RLE / bit-packed        50 MB → ~1 MB
notes (90%   "" + delimiter     definition levels only  10 MB → ~0.2 MB
 null)

* when sorted by city; ~7.5 MB of 6-bit ids if not
 then: Snappy/zstd on every page squeezes further

The trick isn't one algorithm. It's choosing the right one per column, which only columnar storage allows.

Codecs: the second layer

codec    speed      ratio    use for
──────────────────────────────────────────────────────
Snappy   very fast  modest   Spark's Parquet default; hot data
LZ4      very fast  modest   same niche as Snappy
zstd     fast       high     modern default; tunable levels
gzip     slow       high     archives, legacy

Crucial fact: Parquet compresses page by page, so a Parquet file stays splittable whatever codec you choose — workers split on row groups. That's different from a .csv.gz, where one gzip stream covers the whole file.

ORC works the same way (dictionary, RLE v2, then a codec — ZLIB by default).

At scale: the levers you actually pull

1. Sort before writing. Sorting by low-cardinality filter columns (city, ride_date, status) lengthens runs, shrinks files, and tightens min/max stats for skipping. Teams routinely see 20–40% smaller files from sort order alone.

2. Watch for dictionary fallback. High-cardinality strings (UUIDs) blow the dictionary limit and fall back to plain. That's expected — don't fight it, but don't expect them to compress well.

3. Pick zstd for cold, Snappy/LZ4 for hot. On object storage, bytes read usually cost more than CPU, so zstd often wins even for hot data — measure.

4. Store the right types. A fare as a STRING "245.50" defeats delta and bit-packing; store paise as INT64 or use DECIMAL. Timestamps as strings are the same mistake.

Gotchas

Encode by meaning, then compress the bytes.

Dictionary for repeats, RLE for runs, delta for sequences, bit-pack the ids — and sort first so the runs are long.

Words to know

Dictionary encodingStore each distinct value once and replace occurrences with small integer ids.
Run-length encoding (RLE)Store a repeated value once with a count of how many times it repeats.
Bit-packingStore integers using only as many bits as the largest value requires.
Delta encodingStore each value as the difference from the previous one.
CodecA general-purpose compression algorithm such as Snappy, LZ4, zstd or gzip.
Definition levelsParquet metadata marking which values are null, so nulls take no value storage.
Dictionary fallbackWhen a column has too many distinct values, the writer switches from dictionary to plain encoding.

In two minutes

Test yourself

1. What is the difference between encoding and compression in Parquet?

  1. They are two names for the same step
  2. Encoding transforms values using knowledge of the column (dictionary, delta); compression then squeezes the encoded bytes generically
  3. Compression happens first, then encoding
  4. Encoding is only for strings, compression only for numbers
Show the answer

Encoding transforms values using knowledge of the column (dictionary, delta); compression then squeezes the encoded bytes generically — Encoding is type- and value-aware and happens first; the codec (Snappy, zstd…) is applied to each encoded page afterwards.

2. Which encoding suits a pickup_ts column of steadily increasing timestamps best?

  1. DELTA_BINARY_PACKED
  2. Dictionary encoding
  3. PLAIN
  4. BYTE_STREAM_SPLIT
Show the answer

DELTA_BINARY_PACKED — Consecutive timestamps differ by small amounts; storing bit-packed differences takes a few bits per value instead of 8 bytes.

3. How does Parquet store a column where 90% of values are null?

  1. It writes the string 'null' each time
  2. It writes zeros for nulls
  3. It refuses columns with nulls
  4. Definition levels mark the nulls; only the non-null values are stored
Show the answer

Definition levels mark the nulls; only the non-null values are stored — Definition levels (RLE-encoded) record presence. Mostly-null columns end up tiny.

4. A ride_id UUID column shows PLAIN encoding in the file metadata, not dictionary. Why?

  1. The dictionary exceeded the writer's size limit, so it fell back to plain
  2. The writer had a bug
  3. UUIDs cannot be stored in Parquet
  4. Dictionary encoding only works for integers
Show the answer

The dictionary exceeded the writer's size limit, so it fell back to plain — With millions of distinct values, the dictionary would be as large as the data. Writers detect this and fall back — expected behaviour.

5. You write the same rides data twice: once sorted by city, once after repartition(200). Which is larger and why?

  1. Sorted is larger — sorting adds metadata
  2. Identical — compression doesn't depend on order
  3. The repartitioned one — shuffling breaks runs, so RLE and dictionary pages compress worse and min/max ranges widen
  4. Repartitioned is smaller because files are smaller
Show the answer

The repartitioned one — shuffling breaks runs, so RLE and dictionary pages compress worse and min/max ranges widen — Order changes run lengths. Random order gives short runs and every row group spans every city.

6. Your lake on S3 is queried constantly via Athena (billed per byte scanned). Which codec is usually the better default?

  1. gzip level 9 for maximum ratio
  2. zstd — better ratio at modest CPU, so fewer bytes scanned and billed
  3. No compression, to save CPU
  4. Bzip2
Show the answer

zstd — better ratio at modest CPU, so fewer bytes scanned and billed — Per-byte billing makes ratio valuable; zstd gives near-gzip ratios at much lower CPU cost. Uncompressed multiplies the bill.

There are 9 questions, 3 puzzles and 5 recall cards for this lesson in the app — the recall cards come back on later days, just before you would forget.

Learn it so it stays. Read it, test yourself, then recall it on two different days. The app schedules that for you, free.

Start this lesson →