Skip to content

feat(parquet): report decompression time and bytes in ArrowReaderMetrics - #11532

Open
zhuqi-lucas wants to merge 2 commits into
apache:mainfrom
zhuqi-lucas:feat/decompression-metrics-v2
Open

zhuqi-lucas wants to merge 2 commits into
apache:mainfrom
zhuqi-lucas:feat/decompression-metrics-v2

Conversation

@zhuqi-lucas

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Decompression happens inside decode_page, under both decoding and skipping, and nothing reports it. A scan that is slow because of its codec cannot be told apart from one that is slow because there is a lot to decode, and the cost of decompressing pages only to walk past them is invisible. On our data servers dictionary decompression on the skip path was 14-18% of CPU, and finding it took a profiler on a production pod.

Measured with a local prototype (1M rows, 40k distinct strings, zstd(3)):

read decompression share notes
full read 63.9% dictionary 1.15 ms, data pages 12.76 ms, of 21.78 ms wall
selective read (1 row in 10,000) 84.7% 100 rows out, still 99 data pages / 45 MiB decompressed, all charged to skip_records

What changes are included in this PR?

  • parquet::file::metrics::DecompressionMetrics: time, uncompressed bytes and page count, split by page kind (dictionary / data) and by phase (Read: pages decompressed to decode values; Skip: pages decompressed only to walk past values). Lives in file, not arrow, because decode_page is in the non-arrow file module.
  • SerializedPageReader::with_decompression_metrics; decode_page records into it. The column reader marks the phase.
  • ArrowReaderMetrics owns one handle and gives it to every page reader of a scan, on the sync reader and on the push decoder (and so the async reader). New accessors: decompression(), dictionary_decompress_time(), data_decompress_time(), dictionary_decompressed_bytes(), data_decompressed_bytes().

Decompression is nested inside decoding and skipping, so these numbers are a share of that time, not a stage to add next to it; the docs say so.

Are these changes tested?

Yes. Unit tests for the counters, and end-to-end tests on a real zstd file: a full read records everything under Read; a selective read records walked pages under Skip; disabled metrics report None; the push decoder records the same bytes and page counts as the sync reader (this test fails if the push decoder is not wired).

Overhead: collection is opt-in through ArrowReaderMetrics, off by default. When off, decode_page tests one enum discriminant and reads no clock. When on, with 1,000-row pages (1,000 decompress calls per read), 40 interleaved reads each: median 8.05 ms off vs 8.08 ms on (+0.4%, within noise).

Are there any user-facing changes?

New public API listed above. No behaviour change when metrics are disabled.

Decompression happens inside decode_page, under both decoding and skipping,
and no metric exposes it: a scan slow because of its codec cannot be told
apart from one slow because there is a lot to decode.

Add DecompressionMetrics (parquet::file::metrics), split by page kind
(dictionary or data) and by phase (pages decompressed to read values or to
skip past them). SerializedPageReader takes a handle via
with_decompression_metrics and records in decode_page. ArrowReaderMetrics
owns one and hands it to every page reader of a scan, on both the sync
reader and the push decoder (and so the async reader).

Disabled by default with ArrowReaderMetrics; when disabled no clock is read.
Copilot AI balanced review requested due to automatic review settings October 13, 2026 06:08
@github-actions github-actions Bot added the parquet Changes to the parquet crate label Oct 13, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Skip-phase attribution can substantially misclassify decompression later used to return selected values.

1 open finding
What changed in this PR

Adds opt-in Parquet decompression metrics to distinguish dictionary/data page costs across read and skip phases.

Changes:

  • Introduces shared decompression time, byte, and page counters.
  • Instruments page decoding and phase attribution.
  • Wires metrics through synchronous, push, and asynchronous Arrow readers with tests.
File Description
parquet/​src/​file/​serialized_reader.rs Records decompression metrics during page decoding.
parquet/​src/​file/​mod.rs Exposes the metrics module.
parquet/​src/​file/​metrics.rs Defines metrics API, counters, and tests.
parquet/​src/​file/​metadata/​dictionary.rs Disables scan metrics for metadata dictionary reads.
parquet/​src/​column/​reader.rs Sets read or skip decompression phase.
parquet/​src/​column/​page.rs Extends PageReader with phase attribution.
parquet/​src/​arrow/​push_decoder/​reader_builder/​mod.rs Wires metrics into push-decoder row groups.
parquet/​src/​arrow/​push_decoder/​reader_builder/​data.rs Initializes row-group metrics state.
parquet/​src/​arrow/​in_memory_row_group.rs Propagates metrics to in-memory page readers.
parquet/​src/​arrow/​arrow_reader/​mod.rs Wires synchronous reader metrics.
parquet/​src/​arrow/​arrow_reader/​metrics.rs Adds decompression accessors to Arrow metrics.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread parquet/src/column/reader.rs
A page first loaded by skip_records was counted as Skip even when its values
were then read: skip(1) followed by reading the rest of the page reported
the whole page as thrown away. Hold a page decompressed while skipping, and
the chunk's dictionary, in a DecompressionRecorder until it is known whether
values are read from it (PageReader::current_page_read, called by the column
reader when read_records starts on a page that still has values). A page the
reader moves past, or drops, without reading counts as Skip.

Tests: recorder state transitions; skip one row then read the rest counts
as Read; a selective read over a list column with no offset index counts
pages with a kept row as Read and pages walked past as Skip.
@zhuqi-lucas

Copy link
Copy Markdown
Contributor Author

cc @alamb @adriangb @etseidl What's your opinion about this metrics PR? Thanks!

@etseidl
etseidl self-requested a review October 14, 2026 05:28
@alamb

alamb commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

I will try and check it out

@adriangb

Copy link
Copy Markdown
Contributor

👍🏻 I think pretty much the more metrics the merrier

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parquet Changes to the parquet crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants