Repository navigation
Conversation
kszucs
force-pushed
the
cdc-pages
branch
4 times, most recently
from
September 25, 2026 08:32
ce16f93 to
c0d7629
Compare
This was referenced Oct 2, 2026
Closed
kszucs
force-pushed
the
cdc-pages
branch
2 times, most recently
from
October 5, 2026 21:24
73a59fa to
006ea5b
Compare
Add opt-in content defined chunking (CDC) to the writer. Data page boundaries are chosen by a rolling gear hash over each column's (definition level, repetition level, value) stream instead of by position, so an insert or delete only changes the pages around it and files deduplicate in content addressable storage. The chunker is a port of Arrow C++ (parquet/chunker_internal) and arrow-rs (column/chunker/cdc.rs): same gear tables, mask derivation, defaults and cut rules. Each column keeps one chunker for the whole file, as arrow-rs does, so chunk boundaries continue across row groups instead of restarting in each. Arrow C++ builds a new chunker for every row group, contrary to its own documentation, so its chunks match ours in the first row group but generally not after it. - CdcOptions: min/max chunk size and normalization level, defaulting to 256 KiB / 1 MiB / 0 as in Arrow. - ParquetProperties and ParquetWriter.Builder: withContentDefinedChunkingEnabled(boolean) and withContentDefinedChunking(CdcOptions). - ParquetOutputFormat: parquet.page.content-defined-chunking.* keys. - ChunkingColumnWriter wraps the column writer only when chunking is enabled. It ends pages at chunk boundaries and, as Arrow C++ does, applies the page size and row count limits counted from the page start, so they cut a chunk in the same places after an edit. - The writer keeps each column's chunker for the whole file, in CdcChunkers, which makes every row group's store. - With chunking on, a column falls back from dictionary encoding only when its dictionary outgrows its size limit, as in Arrow C++, not on its first page, which chunking cuts by content. Pages of nulls alone get an empty dictionary page, as Arrow C++ writes one. - CdcWriteBenchmarks measures the write cost. Off by default, and with it off the writer behaves as before.
kszucs
force-pushed
the
cdc-pages
branch
3 times, most recently
from
October 14, 2026 17:26
46e3b11 to
03f588f
Compare
- Chunker state lives per file, in a ContentDefinedChunkers that InternalParquetRecordWriter keeps and passes to every row group's store through a new ParquetProperties.newColumnWriteStore overload. With chunking enabled, a store made without it fails fast rather than silently restarting the chunking. - Content defined chunking is configured through one setter, withContentDefinedChunking(CdcOptions), where null disables it, and named as Arrow names it: CdcOptions, ContentDefinedChunker. - ChunkingColumnWriter wraps each column writer once, when the store is built, and checks the page limits at every record start, counted from the page start; cached nulls are flushed on the unchunked schedule's maximum. - Under chunking a dictionary falls back to plain encoding only when it outgrows its page size limit, as in Arrow C++ and arrow-rs, and column chunks of nulls alone get an empty dictionary page. - Binary values are hashed through one path. - The tests fail when chunking is switched off or broken, cover the cases the Arrow C++ and arrow-rs suites do (V2 pages, dictionary encoding, edits across row groups, a chunker per column, the record writer path), and follow the project's test guidelines. The benchmark writes one mixed dataset.
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.
Rationale for this change
Parquet C++ and parquet-rs can end data pages at content-defined boundaries. Files that share a run of values then share byte-identical pages, which content-addressable storage (e.g. Hugging Face Xet) can deduplicate. This PR adds the same opt-in feature to parquet-java. Iceberg Java needs it to honor the
write.parquet.content-defined-chunking.*table properties.What changes are included in this PR?
ContentDefinedChunker, a port of the Arrow C++ / arrow-rs chunker with the same gear tables, mask derivation, defaults and cut rules, so that for the same physical values the page boundaries match Arrow C++. Arrow types that Arrow hashes at their own width (narrow integers, decimals, dictionary arrays) chunk differently.CdcOptions: min/max chunk size and normalization level, defaulting to 256 KiB / 1 MiB / 0 as in Arrow.ParquetProperties.BuilderandParquetWriter.Builder:withContentDefinedChunking(CdcOptions), wherenull, the default, disables it.ParquetOutputFormat:parquet.page.content-defined-chunking.{enabled,min.size,max.size,norm.level}.InternalParquetRecordWriterkeeps oneContentDefinedChunkersper file and passes it to each row group's store through the newParquetProperties.newColumnWriteStore(schema, pageStore, bloomFilterWriteStore, chunkers). With chunking enabled, a store made without the file's chunkers fails fast instead of silently restarting the chunking, so integrations that build a column write store per row group, such as Iceberg'sParquetWriter, must keep oneContentDefinedChunkersper data file and use that overload.ChunkingColumnWriterwraps each column writer only when chunking is enabled, so the default write path is unchanged. The page size, value count and row count limits still apply, counted from the page start, so that they cut in the same places after an edit.ContentDefinedChunkingWriteBenchmarksmeasures the write cost. In local runs, chunking added about 12–30% to write time, depending on the data.Are these changes tested?
Yes:
list<int64>and a maximum-size-dominated envelope, with V1 and V2 pages and through the record writer.Are there any user-facing changes?
Yes: new writer options and configuration keys, and new public API (
CdcOptions,ContentDefinedChunkers, thenewColumnWriteStoreoverload and theColumnWriteStoreV1/V2constructors that take the chunkers), all marked EXPERIMENTAL and off by default. Written files are ordinary Parquet files and need no reader support.Closes #3817
🤖 Generated with Claude Code