bench(parquet): cover nullable and repeated large-value writes - #10561
Draft
adriangb wants to merge 1 commit into
Draft
bench(parquet): cover nullable and repeated large-value writes#10561adriangb wants to merge 1 commit into
adriangb wants to merge 1 commit into
Conversation
adriangb
force-pushed
the
claude/parquet-bench-nullable-large-values
branch
from
August 5, 2026 14:28
1baddcb to
d57e17f
Compare
The large-value `arrow_writer` benchmarks all build their arrays with `StringArray::from_iter_values`, so `RecordBatch::try_from_iter` marks the field non-nullable and the column's definition levels are absent. The writer resolves such a chunk's value count in O(1) and never inspects levels, so nothing here covers a nullable column whose values exceed `data_page_size_limit` — the path that decides how many values share a data page, and on `DELTA_BYTE_ARRAY` whether prefix deduplication survives at all. Add one nullable case, then five that each vary a single property of it, so a movement can be attributed rather than guessed at: - `_dense` varies null density, to a ratio of exactly two levels per value. Deriving a window from that ratio is exact there, so it is the case where the sub-batching arithmetic cannot go wrong. - `_trailing` varies null placement at a fixed count: a run of nulls at the end leaves every earlier window holding values only. - `distinct_nullable` varies the prefix, removing what deduplication has to work with. - `medium_string_*` varies value size, to where several values share a page budget instead of one overrunning it. - `_list` varies the level shape, to a repeated column whose records cannot span pages. `PLAIN` is measured where page count alone drives the result and omitted where it would restate a neighbour: it never reads the previous value, so prefix, null placement and value size do not change its per-page work.
adriangb
force-pushed
the
claude/parquet-bench-nullable-large-values
branch
from
August 5, 2026 14:55
d57e17f to
e5c0dc0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Every large-value benchmark in
arrow_writerbuilds its array withStringArray::from_iter_values. With no nulls present,RecordBatch::try_from_itermarks the field non-nullable, the column hasmax_def_level == 0, and its definition levels are absent.The writer resolves an absent-level chunk's value count in O(1) and never inspects levels, so nothing here covers a nullable column whose values exceed
data_page_size_limit— the path that decides how many values share a data page, and onDELTA_BYTE_ARRAYwhether prefix deduplication survives at all. The repeated case is a third level shape, also uncovered: records cannot span data pages, so mini-batches must step whole records.What
One nullable case, then five that each vary a single property of it, so a movement can be attributed rather than guessed at:
large_string_shared_prefix_nullable..._nullable_dense..._nullable_trailinglarge_string_distinct_nullablemedium_string_shared_prefix_nullable..._shared_prefix_listPLAINis measured where page count alone drives the result and omitted where it would restate a neighbour: it never reads the previous value, so prefix, null placement and value size do not change its per-page work.Validation
Run against #10554, which changes exactly this path, as base → branch → base on an idle machine so the two base passes give a per-benchmark noise floor. Base is #10505's head, so the effect is #10554 alone.
..._nullable_trailing/delta_byte_array..._nullable/plain..._nullable/delta_byte_array..._nullable_dense/delta_byte_arraylarge_string_distinct_nullable/delta_byte_arraymedium_string_shared_prefix_nullable/delta_byte_array..._shared_prefix_list/delta_byte_arraylist_struct_with_list/*(untouched control)The design holds up: the two cases built to move do, the case built as a control lands at −0.2% against a 0.1% floor, and the cases whose axis should not matter stay inside their own noise.
Time alone understates what is happening, so the same batches written to a file:
uniform_1in16/DELTA_BYTE_ARRAYtrailing_8/DELTA_BYTE_ARRAYPLAINWhich is why the throughput numbers split the way they do.
_trailinggets faster because 118 MiB of writing disappears; the uniform case pays a small amount because its output was already close to deduplicated and value-exact windows roughly double the mini-batch count;PLAINpays the most because it doubles pages for no reduction in output at all.Notes
Benchmark-only; no library code touched.
CI clippy is currently red on
mainfor an unrelated reason —arrow-arith/src/numeric.rs:756tripscollapsible_if, from #10409 — so this PR's Clippy job will fail until that lands a fix.cargo clippy -p parquet --all-features --benches -- -D warningsis clean.🤖 Generated with Claude Code