Skip to content

GH-51684: [C++][Parquet] Keep the CDC chunking state across row groups - #51685

Merged
kszucs merged 8 commits into
apache:mainfrom
kszucs:cdc-row-group-reset
Oct 12, 2026
Merged

kszucs merged 8 commits into
apache:mainfrom
kszucs:cdc-row-group-reset

Conversation

@kszucs

@kszucs kszucs commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Rationale for this change

The content-defined chunker was owned by the column writer, which is recreated for every row group, so the chunking state was reset at each row group boundary. See #51684.

What changes are included in this PR?

The file writer creates one chunker per leaf column and passes it to the column writers of every row group through a private ColumnWriter::Make() overload.

Are these changes tested?

Yes. IndependentOfRowGroupBoundaries writes the same table into one and into six row groups and expects the same page boundaries apart from the row group ends. The first commit adds it alone, failing without the fix.

Are there any user-facing changes?

  • Files with multiple row groups get different page boundaries, since the chunking continues across row groups.
  • ColumnWriter::Make() is unchanged, but column writers created through it no longer chunk, so content-defined chunking requires ParquetFileWriter.
  • Multi-threaded writes with content-defined chunking are about 1.7× slower until GH-51684: [C++][Parquet] Speed up content-defined chunking with a local gear hash #51692 (9.9 ms vs 5.8 ms writing 12 int32 columns of 1M rows on an M4 Max)

Deduplication results

A 1 GiB file of 128Mi random int64 values (PLAIN, snappy, default CDC options) compared with a copy that has 10 rows inserted at 20%. The ratio is the share of bytes left after deduplicating both files with the Xet chunker; 50% means the edited copy adds almost nothing.

Row groups pyarrow 25.0.1 This PR arrow-rs 58.4
8 × 16Mi rows 50.2% 50.1% 50.1%
32 × 4Mi rows 50.6% 50.4% 50.5%
128 × 1Mi rows 52.4% 51.6% 51.6%
512 × 256Ki rows 59.8% 56.4% 56.5%
2048 × 64Ki rows 84.8% 74.2% 74.2%
Without CDC 86.3% 86.3% 86.3%

The page boundaries now match arrow-rs, which already keeps the chunker state across row groups.

Was AI used for this PR?

In accordance to the AI generation guidelines, please disclose below whether and how AI was used in this PR.

Claude Code wrote the code, tests and description under human direction.

PR code and description written by:

  • Human
  • AI

Reviewed before submission by:

  • Human
  • AI
  • Not reviewed

🤖 Generated with Claude Code

@kszucs
kszucs force-pushed the cdc-row-group-reset branch 5 times, most recently from ed7b88a to 10d98d8 Compare October 2, 2026 10:14
@kszucs
kszucs marked this pull request as ready for review October 2, 2026 10:31
Copilot AI balanced review requested due to automatic review settings October 2, 2026 10:31

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.

Copilot review overview

🟡 Changes recommended

The changed public factory breaks ABI and disables CDC for existing direct callers.

Review effort: Balanced
Findings: 1 High severity

Open (1)
What changed in this PR

Persists Parquet content-defined chunking state across row groups.

Changes:

  • Moves chunker ownership to the file writer.
  • Passes chunkers into successive column writers.
  • Adds row-group boundary independence coverage.
File Description
cpp/​src/​parquet/​file_writer.cc Manages per-column chunkers.
cpp/​src/​parquet/​column_writer.h Extends the column-writer factory.
cpp/​src/​parquet/​column_writer.cc Uses externally managed chunkers.
cpp/​src/​parquet/​chunker_internal.h Updates ownership documentation.
cpp/​src/​parquet/​chunker_internal.cc Stores level metadata by value.
cpp/​src/​parquet/​chunker_internal_test.cc Tests stable page boundaries.

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

Comment thread cpp/src/parquet/column_writer.h Outdated
@kszucs
kszucs force-pushed the cdc-row-group-reset branch from 10d98d8 to 8d016a7 Compare October 2, 2026 13:54
Copilot AI balanced review requested due to automatic review settings October 2, 2026 13:54

This comment was marked as duplicate.

Copilot AI balanced review requested due to automatic review settings October 2, 2026 15:29
@kszucs
kszucs force-pushed the cdc-row-group-reset branch from 8d016a7 to f556bae Compare October 2, 2026 15:29

This comment was marked as duplicate.

Copilot AI balanced review requested due to automatic review settings October 2, 2026 16:35
@kszucs
kszucs force-pushed the cdc-row-group-reset branch from f556bae to 0a0f214 Compare October 2, 2026 16:35

This comment was marked as duplicate.

Copilot AI balanced review requested due to automatic review settings October 2, 2026 16:38
@kszucs
kszucs force-pushed the cdc-row-group-reset branch from 0a0f214 to 0e77f38 Compare October 2, 2026 16:38
@kszucs
kszucs force-pushed the cdc-row-group-reset branch from f20c5f0 to 77b9299 Compare October 10, 2026 18:09
@github-actions github-actions Bot added awaiting changes Awaiting changes awaiting change review Awaiting change review and removed awaiting change review Awaiting change review awaiting changes Awaiting changes labels Oct 10, 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.

🔵 Needs a closer look

The exported ColumnWriter::Make change still breaks ABI compatibility and existing CDC callers.

1 open finding

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 10, 2026 18:09
Drop ContentDefinedChunker::Make() and the move assignment operator, the file
writer only needs the move constructor to keep the chunkers in a vector.

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

The exported ColumnWriter::Make change still breaks API/ABI compatibility.

2 open findings

🧠 Review effort: Balanced

Comment thread cpp/src/parquet/column_writer.h Outdated
Comment on lines +134 to +137
static std::shared_ptr<ColumnWriter> Make(
ColumnChunkMetaDataBuilder*, std::unique_ptr<PageWriter>,
const WriterProperties* properties, BloomFilter* bloom_filter = NULLPTR,
internal::ContentDefinedChunker* content_defined_chunker = NULLPTR);

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.

Is it permissible to expose internal class via a public interface and should internal::ContentDefinedChunker be moved out of the internal namespace?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

chunker_internal.h isn't installed (headers matching internal are skipped), so code outside Arrow can only pass null; the forward declaration just lets the file writer pass its chunker. Parquet's public headers already take ::arrow::internal::Executor* the same way. Making the chunker public would commit us to its API, which is out of scope here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Well, originally I added another private Make for this exact reason, but then removed :)

Though it might be better idea to keep a private Make which can be removed once the chunker becomes public?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I moved the chunker to a private ColumnWriter::Make() overload:

  • The public Make() stays unchanged, so there's no API or ABI break.
  • ContentDefinedChunker lives in chunker_internal.h, which isn't installed, so it shouldn't appear in a public signature that users can't even include.
  • Once CDC becomes a public feature, the private overload can be removed, and the chunker can be added to the public Make() with a proper public type.

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.

I simply noticed this potential issue. Given the precedent ::arrow::internal::Executor, either approach works for me.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

To correct my earlier comparison: ::arrow::internal::Executor is declared in the installed arrow/util/thread_pool.h, so users can create one (e.g. via ThreadPool::Make()); internal there only means it has no stability guarantee. ContentDefinedChunker lives in chunker_internal.h, which isn't installed, so users couldn't construct one at all. That's another reason to keep it in the private overload.

@kszucs

kszucs commented Oct 10, 2026

Copy link
Copy Markdown
Member Author

Thanks @wgtmac for reviewing! I addressed the issues, ready for another look.

@wgtmac wgtmac left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Thanks @kszucs!

Comment thread cpp/src/parquet/column_writer.h Outdated
Comment on lines +134 to +137
static std::shared_ptr<ColumnWriter> Make(
ColumnChunkMetaDataBuilder*, std::unique_ptr<PageWriter>,
const WriterProperties* properties, BloomFilter* bloom_filter = NULLPTR,
internal::ContentDefinedChunker* content_defined_chunker = NULLPTR);

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.

Is it permissible to expose internal class via a public interface and should internal::ContentDefinedChunker be moved out of the internal namespace?

Comment thread cpp/src/parquet/column_writer.cc Outdated
The file writer decides from the writer properties whether to pass a chunker,
so the column writer doesn't need to check them.
Copilot AI balanced review requested due to automatic review settings October 12, 2026 08:20

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.

🔵 Needs a closer look

The exported factory change breaks ABI and disables CDC for direct callers that omit the new argument.

2 open findings

🧠 Review effort: Balanced

@github-actions github-actions Bot added awaiting changes Awaiting changes and removed awaiting change review Awaiting change review labels Oct 12, 2026
Pass the content defined chunker through a private overload instead, so the
internal chunker isn't part of the public API.
Copilot AI balanced review requested due to automatic review settings October 12, 2026 08:52
@github-actions github-actions Bot added awaiting change review Awaiting change review awaiting changes Awaiting changes and removed awaiting changes Awaiting changes awaiting change review Awaiting change review labels Oct 12, 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.

🔵 Needs a closer look

The public column-writer factory silently disables CDC despite CDC-enabled properties.

2 open findings

🧠 Review effort: Balanced

@kszucs

kszucs commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

Thanks everyone for the reviews! Merging.

@kszucs
kszucs merged commit 9b34c4a into apache:main Oct 12, 2026
60 of 62 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants