Skip to content

feat(transaction): Add simple snapshot producer, and wire fast append - #3474

Open
CTTY wants to merge 10 commits into
apache:mainfrom
CTTY:ctty/simple-sp
Open

CTTY wants to merge 10 commits into
apache:mainfrom
CTTY:ctty/simple-sp

Conversation

@CTTY

@CTTY CTTY commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Which issue does this PR close?

What changes are included in this PR?

Replaces the attempt-scoped SnapshotProducer and its hook traits with:

  • snapshot_helpers.rs: stateless functions for snapshot production (ids, paths, manifest writer, summary, manifest list + snapshot assembly). Shared with MergingSnapshotProducer later.
  • SimpleSnapshotProducer: retry-persistent state for FastAppendAction. Commit UUID and snapshot id are fixed across attempts, the added manifest is reused when schema and format version are unchanged, and each attempt writes its own manifest list. Previously every retry generated a new UUID and rewrote the manifest, orphaning the old one.

Are these changes tested?

Unit tests for manifest reuse across attempts and invalidation on schema/format change, plus a transaction-level test where a concurrent commit forces a rebase and the manifest is reused.

AI Disclosure

Used claude for the code, vetted by human eyes with a pair of glasses

use crate::{Error, ErrorKind};

/// Retry-persistent state for an action that appends data files in a single
/// new manifest.

@CTTY CTTY Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't think this is accurate, there can be multiple new manifests if the manifest is too large(rolling), or there are files from different partition spec (grouping).

Looks like currently we don't support the above mechanism in rust? Will need to double check

@CTTY CTTY Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

the rolling mechanism can be added later, and I'm more concerned about the spec id grouping.

Looks like we just assume all new data files belong to the same partition spec (current default spec):

self.table.metadata().default_partition_spec().clone(),

I think we can fix it, rather than just blocking them in the validation: https://github.com/apache/iceberg-rust/blame/main/crates/iceberg/src/transaction/snapshot.rs#L155-L164

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Investigated this a bit and I think it's better to defer this. We have open multiple manifest writers using runtime if implemented correctly.

Rolling manifest writer: #3499
Group files by their spec ids: #3500

@CTTY
CTTY requested review from blackmwk and laskoviymishka and removed request for laskoviymishka October 21, 2026 00:43
@CTTY

CTTY commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

cc @JanKaul

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

Requesting changes for the stale inherited sequence numbers described inline. All 104 existing transaction tests pass, but a focused manifest-reuse regression test returns sequence number 35 from the cache while a fresh read correctly returns 36. Including the inherited sequence number in the manifest cache key makes that test pass.

Comment thread crates/iceberg/src/transaction/simple_snapshot_producer.rs

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

This needs some work, but it's close. Keeping the commit UUID and snapshot id fixed across attempts is the right direction for #3266. Two things need to land first. One is @blackmwk's cache-key fix (+1 to that thread). The other is a reuse check in added_manifests that covers the files and spec its manifest was written from. Today that check relies on the caller passing the same files and on the default-spec check that #3500 will remove.

Comment thread crates/iceberg/src/transaction/append.rs Outdated
Comment thread crates/iceberg/src/transaction/simple_snapshot_producer.rs Outdated
Comment thread crates/iceberg/src/transaction/simple_snapshot_producer.rs
Comment thread crates/iceberg/src/transaction/snapshot_helpers.rs Outdated
blackmwk
blackmwk previously approved these changes Oct 22, 2026

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

Re-reviewed at 4f597f1. The expanded manifest cache key fixes my previous finding: the original warmed-cache/rebase reproduction now passes. The new v3 rebase test also covers moving the snapshot's row range after a concurrent append.

Validation: all 105 transaction tests, all 6 object-cache tests, and the original regression reproduction pass. I found no remaining blocking correctness issue. One non-blocking wording correction is noted inline.

Comment thread crates/iceberg/src/transaction/simple_snapshot_producer.rs Outdated
@blackmwk
blackmwk dismissed their stale review October 22, 2026 09:54

Approved by mistake

/// with sequence number 0 even when the manifest list entry referencing them
/// carries a nonzero one.
#[tokio::test]
async fn test_read_v1_manifest_defaults_sequence_number_to_zero() {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I was thinking about some cases that format version changes from V1 to V2 during retry attempts. Didn't find any issue, just think this test can be helpful to clarify things

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.

nit: Could we have a snapshot dir mod, and rename this simple.rs in the snapshot mod?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

good idea


// validate added files
snapshot_producer.validate_added_data_files()?;
async fn commit(&self, state: &mut Self::State, table: &Table) -> Result<ActionCommit> {

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.

Suggested change
async fn commit(&self, state: &mut Self::State, table: &Table) -> Result<ActionCommit> {
async fn commit(&self, state: &mut SimpleSnapshotProducer, table: &Table) -> Result<ActionCommit> {

// validate added files
snapshot_producer.validate_added_data_files()?;
async fn commit(&self, state: &mut Self::State, table: &Table) -> Result<ActionCommit> {
let data_files = self.dedupe_added_files();

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 think this is incorrect, why we need to do this in each retry? It only need once.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes I think the deduplication can be moved to the action

type State = SimpleSnapshotProducer;

fn new_state(&self) -> Self::State {
SimpleSnapshotProducer::new(self.commit_uuid)

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 think you should pass all info to snapshot producer?

) -> Result<()> {
let metadata = table.metadata();
for data_file in data_files {
if data_file.content_type() != DataContentType::Data {

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.

This should only do once.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Currently I'm copying the existing logic. How about revisiting this in #3500? These logic can be refactored and optimized at one go

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We can move every validation that does NOT depend on the current table to actions themselves

// TODO: Allowing snapshot property setup with no added data files is a workaround.
// We should clean it up after all necessary actions are supported.
// For details, please refer to https://github.com/apache/iceberg-rust/issues/1548
if data_files.is_empty() && properties.is_empty() {

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.

Similarly, it should only do once.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

3 participants