This is an automated email from the ASF dual-hosted git repository.
laskoviymishka pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git
The following commit(s) were added to refs/heads/main by this push:
new 87c9223c0 perf(table): skip metadata clone for duplicate requirements
(#2105)
87c9223c0 is described below
commit 87c9223c07068fff4b336e4631b70d6bc77b753e
Author: Minh Vu <[email protected]>
AuthorDate: Tue Oct 6 10:22:04 2026 +0200
perf(table): skip metadata clone for duplicate requirements (#2105)
* perf(table): skip metadata clone for duplicate requirements
* test(table): assert duplicate apply keeps metadata builder
* test(table): cover duplicate requirements with updates
---
table/transaction.go | 20 ++++++++++++++++----
table/transaction_internal_test.go | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 52 insertions(+), 4 deletions(-)
diff --git a/table/transaction.go b/table/transaction.go
index 18d9d5d89..dae5e3ebf 100644
--- a/table/transaction.go
+++ b/table/transaction.go
@@ -165,10 +165,7 @@ func (t *Transaction) apply(updates []Update, reqs
[]Requirement) error {
return errors.New("transaction has already been committed")
}
- stagedMeta := meta.clone()
- if stagedMeta == nil {
- return errors.New("cannot apply updates to nil metadata")
- }
+ var stagedMeta *MetadataBuilder
// Only new requirement validation needs the immutable metadata view.
// Updates can be applied directly to the staged builder, and duplicate
@@ -207,6 +204,12 @@ func (t *Transaction) apply(updates []Update, reqs
[]Requirement) error {
continue
}
+ if stagedMeta == nil {
+ stagedMeta = meta.clone()
+ if stagedMeta == nil {
+ return errors.New("cannot apply updates
to nil metadata")
+ }
+ }
if current == nil {
built, err := stagedMeta.Build()
if err != nil {
@@ -226,6 +229,15 @@ func (t *Transaction) apply(updates []Update, reqs
[]Requirement) error {
stagedReqs = append(stagedReqs, r)
}
}
+ if stagedMeta == nil {
+ if len(updates) == 0 {
+ return nil
+ }
+ stagedMeta = meta.clone()
+ if stagedMeta == nil {
+ return errors.New("cannot apply updates to nil
metadata")
+ }
+ }
prevUpdates, prevLastUpdated := len(stagedMeta.updates),
stagedMeta.lastUpdatedMS
for _, u := range updates {
diff --git a/table/transaction_internal_test.go
b/table/transaction_internal_test.go
index 306916335..4049d7b59 100644
--- a/table/transaction_internal_test.go
+++ b/table/transaction_internal_test.go
@@ -929,8 +929,10 @@ func
TestTransactionApplyDedupesEquivalentRequirementsWithinAndAcrossCalls(t *te
require.Len(t, txn.reqs, 1)
requireContainsRefSnapshotRequirement(t, txn.reqs, MainBranch,
&mainSnapshotID)
+ meta := txn.meta
err = txn.apply(nil, []Requirement{AssertRefSnapshotID(MainBranch,
&mainSnapshotID)})
require.NoError(t, err)
+ require.Same(t, meta, txn.meta)
require.Len(t, txn.reqs, 1)
requireContainsRefSnapshotRequirement(t, txn.reqs, MainBranch,
&mainSnapshotID)
}
@@ -1297,7 +1299,9 @@ func
TestTransactionApplyDefersMetadataBuildForNoopRequirements(t *testing.T) {
// A duplicate requirement only needs its conflict check. Make
Build fail
// after the first apply so this path cannot accidentally
rebuild metadata.
txn.meta.defaultSpecID = 999
+ meta := txn.meta
require.NoError(t, txn.apply(nil, []Requirement{requirement}))
+ require.Same(t, meta, txn.meta)
_, err := txn.meta.Build()
require.ErrorIs(t, err, ErrInvalidMetadata)
})
@@ -2171,3 +2175,35 @@ func TestTableCommitWithoutUpdatesStaysEmpty(t
*testing.T) {
assert.Equal(t, []Requirement{}, tc.Requirements, "an empty payload
must serialize as [], not null")
assert.Equal(t, []Update{}, tc.Updates, "an empty payload must
serialize as [], not null")
}
+
+func TestTransactionApplyDuplicateRequirementWithUpdates(t *testing.T) {
+ for _, fail := range []bool{false, true} {
+ t.Run(fmt.Sprintf("failure=%t", fail), func(t *testing.T) {
+ txn, _ := createTestTransactionWithMemIO(t,
*iceberg.UnpartitionedSpec)
+ requirement := AssertCurrentSchemaID(0)
+ require.NoError(t, txn.apply(nil,
[]Requirement{requirement}))
+ before, err := txn.meta.Build()
+ require.NoError(t, err)
+ original := txn.meta
+ updates :=
[]Update{NewSetPropertiesUpdate(iceberg.Properties{"test-key": "test-value"})}
+ if fail {
+ updates = append(updates,
NewSetCurrentSchemaUpdate(9999))
+ }
+ err = txn.apply(updates, []Requirement{requirement})
+ require.Equal(t, []Requirement{requirement}, txn.reqs)
+ after, buildErr := txn.meta.Build()
+ require.NoError(t, buildErr)
+ if fail {
+ require.Error(t, err)
+ require.Same(t, original, txn.meta)
+ require.True(t, before.Equals(after))
+
+ return
+ }
+ require.NoError(t, err)
+ require.NotSame(t, original, txn.meta)
+ require.Equal(t, "test-value",
after.Properties()["test-key"])
+ require.Empty(t, before.Properties()["test-key"])
+ })
+ }
+}