feat(benchmark): add format-level Parquet read/write micro-benchmarks - #232
Open
SteNicholas wants to merge 1 commit into
Open
feat(benchmark): add format-level Parquet read/write micro-benchmarks#232SteNicholas wants to merge 1 commit into
SteNicholas wants to merge 1 commit into
Conversation
SteNicholas
force-pushed
the
PAIMON-228
branch
2 times, most recently
from
August 20, 2026 14:15
697d536 to
5b26215
Compare
Add paimon-parquet-format-benchmark, driving ParquetWriterBuilder and ParquetFileBatchReader directly so a change confined to the format layer can be attributed without the catalog lookup, split planning, merge/sort and commit that the table-level read_write_benchmark includes. Every axis is swept on its own rather than as a combined matrix, so each case answers one question and a change can be attributed to it. Writer cases cover flat primitives, VARCHAR by cardinality with and without file-level dictionary encoding, dictionary-encoded VARCHAR/INTEGER input arrays against flat controls holding value and width fixed, DECIMAL at precision 9/18/38, nested STRUCT/LIST/VECTOR/MAP, null density, rows per AddBatch call, column count, row group size, the byte-triggered writer memory threshold, and every codec Parquet accepts. Reader cases cover full scan, projection, predicate-filtered reads with page-index filtering on and off, skip-heavy reads driven by a strided selection bitmap whose strides straddle the coalesce hole limit, null density, DECIMAL, DOUBLE, dictionary against plain files, rows per NextBatch call, and nested column reads. Each case reports ns_per_row and bytes_per_row next to ns_per_input_row and bytes_per_input_row, so settings that prune by different amounts stay comparable, plus the reader's own row-group and batch counters. Read cases assert on the rows they materialized and the process exits non-zero on any SkipWithError, so a case that stops measuring what it claims fails instead of looking fast. Also add paimon-parquet-format-benchmark-test, which guards the format-layer assumptions the benchmark rests on. The benchmark only builds under PAIMON_BUILD_BENCHMARKS, which CI does not set, so the test is what keeps codec acceptance, dictionary round-trips, DECIMAL precision, nested types, predicate and bitmap row counts, encoding agreement and the memory-threshold flush covered.
SteNicholas
force-pushed
the
PAIMON-228
branch
from
August 20, 2026 14:19
5b26215 to
e99db80
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.
Purpose
Linked issue: close #228
benchmark/only had table-level cases, so a change confined to the format layer — writer properties, encoding passthrough, per-batch overhead in the read loop — could not be measured without the noise of catalog lookup, split planning, merge/sort and commit.This adds
paimon-parquet-format-benchmark, drivingParquetWriterBuilderandParquetFileBatchReaderdirectly. Every axis is swept on its own rather than as a combined matrix, so each case answers one question and a change can be attributed to it.Writer cases — flat primitives;
VARCHARby cardinality with and without file-level dictionary encoding; dictionary-encodedVARCHAR/INTEGERinput arrays against flat controls that hold value, cardinality and width fixed;DECIMALat precision 9 / 18 / 38; nestedSTRUCT/LIST/VECTOR/MAP; null density 0–100%; rows perAddBatch; column count; row group size; the byte-triggered writer memory threshold; and every codec Parquet accepts.Reader cases — full scan; projection; predicate-filtered reads with page-index filtering on and off; skip-heavy reads driven by a strided selection bitmap whose strides straddle the coalesce hole limit; null density;
DECIMAL;DOUBLE; dictionary against plain files; rows perNextBatch; nested column reads.Metrics —
ns_per_row/bytes_per_rowalongsidens_per_input_row/bytes_per_input_row, so settings that prune by different amounts stay comparable (pruning shrinks the per-materialized-row numerator and denominator together), plus the reader's ownrow_groups/row_groups_after_filter/batchescounters. Read cases assert on the rows they materialized, and the process exits non-zero on anySkipWithError, so a case that stops measuring what it claims fails instead of looking fast.Dictionary-encoded input gets cases of its own rather than being folded into the string ones. Paimon has no dictionary
FieldType, but nothing inParquetWriterBuilderrejects a dictionary Arrow type, so the writer does reach Arrow's decision about whether to pass indices through or materialize them — and each dictionary case is paired with a flat control that holds value, cardinality and width fixed, so the delta is the materialization alone.Tests
New
paimon-parquet-format-benchmark-test(7 cases). The benchmark only builds underPAIMON_BUILD_BENCHMARKS, which CI does not set, so this test is what keeps the format-layer assumptions it rests on covered:RegisteredCodecsWrite— every registered codec writes and reads back with matching values; asserts"lz4"is still rejected (it resolves to Arrow'sLZ4_FRAME, whichparquet::IsCodecSupportedrejects — this is why the sweep registerslz4_rawandlz4_hadoopinstead).DictionaryInputRoundTrip— dictionary-encodedVARCHAR/INT32input over multipleAddBatchcalls, compared value by value against the flat equivalent.DecimalPrecisionRoundTrip— precision 9 / 18 / 38, checking type and values (38 is the only one reachingFIXED_LEN_BYTE_ARRAY).NestedRoundTrip—LIST/MAPround trip, full row and per-column projection.FilteredAndBitmapRowCounts— predicate row counts bounded at the row-group grain, and selection-bitmap lower bound.PlainAndDictionaryFilesAgree— the two files the encoding case compares must decode identically.MemoryThresholdFlushesRowGroups— a smallparquet.writer.max.memory.usecuts extra row groups on both plain and dictionary input, and row-group boundaries do not change the data.API and Format
No. Nothing under
include/changes, and no storage format or protocol is affected. The new files are a benchmark binary and its test; the only existing files touched arebenchmark/CMakeLists.txtand the benchmark docs.Documentation
Yes —
docs/source/examples/benchmark.rstgains apaimon-parquet-format-benchmarksection covering the case inventory, how to read each counter (including why filtered cases must be compared onns_per_input_rowrather thanns_per_row, and thatrow_groups_after_filteris row-group level and does not show page-level pruning), and how the local-FileSystem write path bounds what the absolute numbers mean.Generative AI tooling
Generated-by: Claude Opus 5 (Claude Code)
🤖 Generated with Claude Code