Skip to content

[python] Extend commit protocol for compaction (DataIncrement/CompactIncrement) - #7873

Closed
TheR1sing3un wants to merge 2 commits into
apache:masterfrom
TheR1sing3un:feat/python-compact-pr1-commit-protocol
Closed

[python] Extend commit protocol for compaction (DataIncrement/CompactIncrement)#7873
TheR1sing3un wants to merge 2 commits into
apache:masterfrom
TheR1sing3un:feat/python-compact-pr1-commit-protocol

Conversation

@TheR1sing3un

@TheR1sing3un TheR1sing3un commented May 16, 2026

Copy link
Copy Markdown
Member

Purpose

Align CommitMessage with Java's CommitMessageImpl shape and add a JSON-safe wire format, so later compaction work has somewhere to plug compact_before / compact_after files and a serializer to ship them through Ray workers.

Foundation only — read / write / commit produce the same snapshots.

Split from #7771.

Changes

  • DataIncrement / CompactIncrement value objects; CommitMessage now holds (partition, bucket, total_buckets, data_increment, compact_increment, check_from_snapshot). Convenience properties preserve msg.new_files / msg.compact_before ergonomics.
  • FileStoreCommit emits ADD for compact_after, DELETE for compact_before, auto-picks commit_kind=COMPACT when only compact increments are present. New commit_compact() skips row-id assignment.
  • DataFileMeta.to_dict / from_dict with tagged encoding for bytes / Decimal / datetime / date / time / Timestamp; encode_value / decode_value public for CommitMessage.partition round-trip.
  • CommitMessageSerializer (VERSION=1) covers the full DataIncrement + CompactIncrement shape including IndexFileMeta.

Tests

  • commit_message_serializer_test — round-trip with non-JSON-native partition values + index files + version rejection.
  • file_store_commit_compact_testcompact_before → DELETE, compact_after → ADD, auto-COMPACT kind.
  • Existing file_store_commit_test / partition_predicate_test / table_commit_test adapted to the new CommitMessage signature.

@TheR1sing3un
TheR1sing3un force-pushed the feat/python-compact-pr1-commit-protocol branch 3 times, most recently from c1fc089 to 4ee0654 Compare May 16, 2026 13:51
@TheR1sing3un
TheR1sing3un marked this pull request as ready for review May 16, 2026 13:55
@TheR1sing3un

Copy link
Copy Markdown
Member Author

Ready for review, 1st pr of the entire compaction feature.

@JingsongLi JingsongLi 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.

Review: [python] Extend commit protocol for compaction (DataIncrement/CompactIncrement)

Overall this is a well-structured PR that cleanly separates write-side and compaction-side semantics by introducing DataIncrement / CompactIncrement value objects, aligning pypaimon's CommitMessage with Java's CommitMessageImpl. The code is clean, well-documented, and has good test coverage. A few observations:

Correctness

  1. encode_value does not handle list / tuple values. If a GenericRow or partition column ever carries an ARRAY-typed value, serialization will raise TypeError. This may be fine for the current scope (partition keys are typically scalar), but worth a brief comment or a graceful error message mentioning "ARRAY" if it's intentionally out of scope.

  2. _index_file_to_dict drops dv_ranges and global_index_meta — the comment explains this is deferred to Phase 6/7. Just want to flag: if any code path populates those fields before Phase 6/7 lands, a round-trip through the serializer will silently lose them. The IndexFileMeta.__eq__ only checks the four scalar fields so tests won't catch this. Consider adding an assertion/warning in _index_file_to_dict if those fields are non-None, similar to how _build_commit_entries rejects un-wired increment slots.

Design

  1. _build_commit_entries entry ordering. For a message with both new_files and compact_before/compact_after (currently rejected at the API level but structurally possible), the method emits ADD(new_files), then DELETE(compact_before), then ADD(compact_after). The explicit separation of commit() vs commit_compact() makes this a non-issue today, but the ordering assumption should be documented in a comment on _build_commit_entries for future maintainers who may relax the validation.

  2. total_buckets fallback logic is good. Using the message's total_buckets (captured at plan time) over the table's current value correctly handles bucket rescale races. The test test_build_entries_uses_message_total_buckets_when_set explicitly covers this, which is great.

  3. Convenience properties on CommitMessage (new_files, compact_before, etc.) preserve ergonomics nicely. These delegate cleanly and avoid breaking existing call sites.

Minor / Nits

  1. In _generic_row_to_dict, the fallback path [row.get_field(i) for i in range(len(row))] assumes the row implements __len__. A brief type annotation or protocol check (e.g., InternalRow) would improve clarity for readers unfamiliar with the row hierarchy.

  2. The CommitMessageSerializer uses separators=(",", ":") for compact JSON output — good choice for a wire format since it minimizes payload size without sacrificing readability when pretty-printed for debugging.

  3. DataIncrement.empty_increment() and CompactIncrement.empty_increment() class methods duplicate what DataIncrement() / CompactIncrement() already do with default factories. Unless these are intended as semantic markers for readability in call sites, they could be removed to reduce API surface.

Summary

Solid foundation work. The separation of concerns between write and compaction paths is well-motivated, the serializer is defensively versioned, and the fail-loud approach for un-wired slots (NotImplementedError) is the right call for incremental development. The test suite covers the key round-trip and rejection scenarios thoroughly.

@TheR1sing3un
TheR1sing3un requested a review from JingsongLi May 24, 2026 03:00
@JingsongLi

Copy link
Copy Markdown
Contributor

I think the new CommitMessageSerializer drops one existing part of CommitMessage.

CommitMessage has index_deletes, and is_empty() treats it as meaningful content. The global-index update path can build messages that only contain index_deletes (build_index_delete_msgs(...)). But CommitMessageSerializer.to_dict/from_dict only round-trips partition, bucket, total_buckets, data/compact increments, and check_from_snapshot; it does not serialize index_deletes.

If such a message is shipped through this serializer, the deserialized message will lose the index manifest deletions and the commit can leave stale global-index entries behind. Could we include index_deletes in the wire format and add a round-trip test for a message whose only content is index_deletes?

@TheR1sing3un

Copy link
Copy Markdown
Member Author

I think the new CommitMessageSerializer drops one existing part of CommitMessage.

CommitMessage has index_deletes, and is_empty() treats it as meaningful content. The global-index update path can build messages that only contain index_deletes (build_index_delete_msgs(...)). But CommitMessageSerializer.to_dict/from_dict only round-trips partition, bucket, total_buckets, data/compact increments, and check_from_snapshot; it does not serialize index_deletes.

If such a message is shipped through this serializer, the deserialized message will lose the index manifest deletions and the commit can leave stale global-index entries behind. Could we include index_deletes in the wire format and add a round-trip test for a message whose only content is index_deletes?

Nice catch, fixed~

@TheR1sing3un

Copy link
Copy Markdown
Member Author

@JingsongLi @XiaoHongbo-Hope Hi, could me help to review it? Hope to land it!

Comment thread paimon-python/pypaimon/write/file_store_commit.py
@TheR1sing3un
TheR1sing3un requested a review from JingsongLi June 23, 2026 11:17
Comment thread paimon-python/pypaimon/write/file_store_commit.py Outdated
Comment thread paimon-python/pypaimon/write/file_store_commit.py Outdated
@TheR1sing3un
TheR1sing3un requested a review from JingsongLi June 23, 2026 15:54
Comment thread paimon-python/pypaimon/write/table_update_by_row_id.py
@TheR1sing3un
TheR1sing3un requested a review from JingsongLi June 24, 2026 07:18
Comment thread paimon-python/pypaimon/write/file_store_commit.py Outdated
Comment thread paimon-python/pypaimon/write/file_store_commit.py Outdated
@TheR1sing3un
TheR1sing3un requested a review from JingsongLi June 27, 2026 05:10
Comment thread paimon-python/pypaimon/write/file_store_commit.py Outdated
@TheR1sing3un
TheR1sing3un force-pushed the feat/python-compact-pr1-commit-protocol branch 2 times, most recently from 12cbd57 to f545125 Compare July 2, 2026 10:41
…Increment)

Align CommitMessage with Java's CommitMessageImpl shape and add a JSON-safe
wire format, so compaction work has somewhere to plug compact_before /
compact_after files and a serializer to ship them through Ray workers.

- DataIncrement / CompactIncrement value objects; CommitMessage now holds
  (partition, bucket, total_buckets, data_increment, compact_increment,
  check_from_snapshot, index_adds, index_deletes). Convenience properties
  preserve msg.new_files / msg.compact_before ergonomics.
- FileStoreCommit.commit() is a single entry that splits each batch by kind:
  data increments feed an APPEND/OVERWRITE snapshot, compact increments feed a
  separate COMPACT snapshot (up to two snapshots per commit, mirroring Java).
  Compaction skips row-id assignment and conflict detection; a COMPACT snapshot
  inherits the previous index manifest. index_adds / index_deletes ride the
  snapshot their payload belongs to.
- DataFileMeta.to_dict / from_dict with tagged encoding for bytes / Decimal /
  datetime / date / time / Timestamp; encode_value / decode_value public for
  CommitMessage.partition round-trip.
- CommitMessageSerializer (VERSION=1) covers the full DataIncrement +
  CompactIncrement shape including IndexManifestEntry and index_adds/deletes.
- Convert all CommitMessage construction sites to the increment API.

Tests: commit_message_serializer_test, file_store_commit_compact_test,
and adapted file_store_commit / partition_predicate / table_update tests.
@TheR1sing3un
TheR1sing3un force-pushed the feat/python-compact-pr1-commit-protocol branch from f545125 to 96b81f1 Compare July 2, 2026 12:28
Comment thread paimon-python/pypaimon/write/file_store_commit.py
@TheR1sing3un
TheR1sing3un requested a review from JingsongLi July 4, 2026 15:46
check_from_snapshot was collected from every message (including
compaction-only ones) and only ever set — never reset — on the reusable
ConflictDetection instance. A compact message can round-trip that field
through the serializer, so a compact-only commit left the row-id check
anchor set, and because StreamTableCommit reuses one FileStoreCommit the
next APPEND/OVERWRITE commit ran conflict detection + rollback even when
its own data messages had check_from_snapshot=-1.

Anchor row-id conflict detection only from messages that contribute
data-side entries (non-empty data_increment), and assign the anchor
unconditionally (None when absent) so it is recomputed from scratch each
data phase and cannot leak across commits.

@JingsongLi JingsongLi 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.

Thank you for your continuous updates, but I still feel concerned about this ability because Compaction is a very inefficient task in Python. I would prefer Compaction to be completed by the Rust layer.

@TheR1sing3un

Copy link
Copy Markdown
Member Author

Thank you for your continuous updates, but I still feel concerned about this ability because Compaction is a very inefficient task in Python. I would prefer Compaction to be completed by the Rust layer.

Agreed! Lets close it,thanks for your kindly reviews

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants