advanced25 min studyLesson 291 in Engineering

Row groups, column chunks, and pages solve different problems

Trace a projected scan through Parquet's physical hierarchy without confusing row partitions, column ranges, encoding units, or in-memory batches.

On this page

What you’ll understand

  • Locate row groups, leaf-column chunks, and pages in a Parquet scan.
  • Estimate projected payload while accounting separately for metadata and request overhead.
  • Explain why row-group size and in-memory batch size need not match.

A columnar file is not one undivided array per field, and a row group is not a row-oriented record block. The physical hierarchy creates different boundaries for parallel work, selective reading, and decoding. If those boundaries are confused, a configuration change can increase memory or request count while failing to improve the intended scan. We use Parquet terminology precisely, then compare it with the separate in-memory representation used by an execution engine.

Horizontal membership and vertical storage

A row group represents a horizontal subset of rows. For a flat schema, that subset has one column chunk for each column; for nested schemas, reason about the encoded leaf-column paths rather than assuming one chunk per top-level object. A column chunk holds that column's values and structural information for the row group in a contiguous file range. Consequently, reading two projected columns can avoid unrelated column chunks, while reading all rows of one column can require visiting its chunk in every selected row group.

Column chunks contain pages, which carry encoded data or a dictionary and have headers describing their contents. Encoding and compression operate at their defined page boundaries; a row group is a larger planning unit, not a single mandatory compression stream. File metadata records locations and schema information, allowing a reader to find the ranges it needs. A file's storage block size, object-store request size, Parquet page size, and engine vector batch size are distinct settings. Naming the layer prevents accidental tuning of a parameter that the actual bottleneck does not use.

Dflat≈∑g∈G∑c∈CngscD_{\rm flat}\approx\sum_{g\in G}\sum_{c\in C}n_gs_c
For selected row groups G and required flat fixed-width columns C, n_g counts rows and s_c is bytes per unencoded nonnullable value. This estimates logical payload bytes, not compressed physical reads; levels, metadata, headers, encoding, and skipped pages are excluded.↔ Scroll wide equations horizontally.

Choose granularity for work, memory, and skipping

Larger row groups can amortize metadata and scheduling overhead and give encoders a broader local population. Smaller row groups can expose more independent tasks and allow a selective predicate to discard smaller regions. Neither direction is universally better. A query that scans an entire dataset, a point lookup, and a memory-constrained training loader can prefer different shapes. Distribution matters too: if values are mixed uniformly through every group, reducing group size may still leave nearly every group relevant, while increasing footer entries and setup work.

An execution engine can stream decoded batches smaller than a row group rather than materializing every projected column at once. Yet dictionaries, decompression buffers, pending range requests, and concurrent tasks still consume memory. Conversely, an Arrow record batch is an in-memory collection of equal-length arrays; it is not a promise that its underlying Parquet row group has the same row count or byte layout. Build a peak-live-buffer budget for the chosen reader and concurrency. Do not derive memory consumption simply by multiplying the on-disk compressed file size by one guessed factor.

Mlive≈k(Mcompressed+Mdecode+Mbatch+Mdictionary)M_{\rm live}\approx k\bigl(M_{\rm compressed}+M_{\rm decode}+M_{\rm batch}+M_{\rm dictionary}\bigr)
This illustrative byte budget sums simultaneously live buffers per task and multiplies by k concurrent tasks. Shared buffers and different lifetimes can alter the total; measure the implementation rather than assuming every listed maximum overlaps.↔ Scroll wide equations horizontally.

Inspect the ranges a reader actually fetches

Start a scan investigation with a known schema, row-group count, selected columns, and metadata dump. Then observe requested byte ranges and decoded rows. A network client may coalesce nearby ranges or prefetch extra bytes, and metadata requests may dominate a small query. A logical ability to skip a column does not require a client to issue the smallest imaginable request. Keep logical payload, requested bytes, bytes delivered, decoded bytes, and output bytes as separate counters; they locate different opportunities for improvement.

Use the cache lab to isolate reuse, not to pretend it renders Parquet. Reset, set both capacity C to four and stride s to four, and predict twelve hits among the initial sixteen requests to four repeated addresses; then Play or Step. Reset and re-enter both C=2 and s=4 for a separate run, predicting no hits because the four-address cycle exceeds capacity. The cache starts empty before those sixteen simulated requests, adds two requests per second, and caps at 4,096. These synthetic addresses are not real row groups or file ranges.

Count projected payload before compression

A flat file has six row groups of 50,000 rows. A query needs one eight-byte column and one four-byte column from every group. Assume no nulls, encoding, compression, or metadata costs.

  1. The selected row count is 6 × 50,000 = 300,000. Required payload per row is 8 + 4 = 12 bytes.

  2. Logical projected payload is 300,000 × 12 = 3,600,000 bytes. There are twelve required column chunks: two per row group.

  3. Each selected row group contributes 50,000 × 12 = 600,000 payload bytes, and six groups sum to 3.6 MB. The selected columns have different widths, so column count alone cannot establish their fraction of total file payload. Real fetched bytes also include metadata, headers, compression effects, and reader range choices.

The result

The estimate is 3.6 decimal MB of selected logical payload and twelve column chunks. It is a planning baseline, not a physical network-transfer prediction.

Explore the model

Interactive lab

Interactive lab · cache

Reuse has its own boundary

Reset, set both C=4 and s=4, predict twelve hits in the initial sixteen requests, then Play or Step. Reset and re-enter C=2 and s=4 to predict no hits for a separate run. Requests are (i·s) mod16, begin with sixteen, and cap at 4,096. This fully associative LRU model is not a Parquet reader or real file-cache trace.

Reuse has its own boundaryA 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 forFile layout and cache reuse are separate mechanisms; neither a row-group count nor a toy hit rate predicts real scan throughput.

Check your understanding

Which description correctly separates Parquet's row group and column chunk for a flat schema?

Try it yourself

Four row groups each contain 80,000 rows. A query requires three nonnullable fixed-width columns of four bytes each. Estimate logical payload and required column-chunk count before page pruning or compression.

Take a moment to make a prediction or work it out on paper. Open a hint when you need one.

Give me a hint

Multiply total selected rows by required bytes per row, then count columns per group.

Compare with the solution

There are 320,000 rows and twelve required bytes per row, giving 3,840,000 payload bytes. Four groups times three columns require twelve chunks. This excludes levels, headers, metadata, compression, and any additional filter columns, so it cannot alone predict requested or transferred bytes.

Take it with you

  • Row groups, column chunks, and pages represent distinct planning and storage boundaries.
  • An engine can stream batches within a row group while retaining additional decoder state.
  • Separate logical payload estimates from observed range requests and live-memory measurements.

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.