This is an automated email from the ASF dual-hosted git repository.

github-actions[bot] pushed a commit to branch gh-pages
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/gh-pages by this push:
     new 45e8d570e deploy: 94102c5cb24f437a7c33e6bf53e2acb4efacdcc5
45e8d570e is described below

commit 45e8d570ee61cee0625d6b37657def42d5c3d74e
Author: laskoviymishka <[email protected]>
AuthorDate: Sun Nov 8 21:17:17 2026 +0000

    deploy: 94102c5cb24f437a7c33e6bf53e2acb4efacdcc5
---
 concurrent-writes.html | 42 ++++++++++++++++++++++++++++++++++--------
 print.html             | 45 +++++++++++++++++++++++++++++++++++++--------
 releases.html          |  3 +++
 searchindex.js         |  2 +-
 searchindex.json       |  2 +-
 5 files changed, 76 insertions(+), 18 deletions(-)

diff --git a/concurrent-writes.html b/concurrent-writes.html
index ba91b2d3f..bf7a8e53e 100644
--- a/concurrent-writes.html
+++ b/concurrent-writes.html
@@ -289,11 +289,11 @@ rejects when concurrent deletes touch the same files.</p>
 <p>iceberg-go can refresh the table and replay the operation against the latest
 snapshot between attempts. On each retry the table metadata is reloaded, the
 producer's validators re-run against the fresh snapshot, and the commit is
-re-submitted only if it is still valid. <code>RollbackToSnapshot</code>, 
<code>ExpireSnapshots</code>
-and <code>AssertRefSnapshotID</code> pin the refs they guard, so a retry does 
not rebase
-those assertions. If a guarded ref has moved, the commit fails with
-<code>ErrCommitFailed</code> instead of retrying. An 
<code>ExpireSnapshots</code> that removes nothing
-adds no guards.</p>
+re-submitted only if it is still valid. <code>RollbackToSnapshot</code> and
+<code>AssertRefSnapshotID</code> pin their refs, so retries do not rebase 
those assertions.
+If a pinned ref moved, the commit fails with <code>ErrCommitFailed</code> 
instead of
+retrying. <code>ExpireSnapshots</code> does not pin refs. Retries replan it as 
described in
+<a href="#snapshot-expiration">Snapshot expiration</a>.</p>
 <p>Retry is <strong>off by default</strong> 
(<code>commit.retry.num-retries</code> is <code>0</code>). Opt in by
 setting the retry properties — see <a 
href="./configuration.html#manifest-and-commit">Manifest and commit</a>
 in the configuration reference for the full list:</p>
@@ -328,9 +328,35 @@ retries disabled always has. An abandoned manifest rewrite 
also leaves the
 manifests that later attempts merged. <code>TableCommit</code> refuses a 
transaction whose
 commit failed, since the multi-table path does not refresh. Call 
<code>Commit</code> to
 resume it, or build a new transaction.</p>
-<p>Because an <code>ExpireSnapshots</code> that removes something pins the 
commit branch, a
-transaction that also appends fails instead of rebasing onto a peer's append.
-This is deliberate, since the expiry was computed from the old head.</p>
+<h3 id="snapshot-expiration"><a class="header" 
href="#snapshot-expiration">Snapshot expiration</a></h3>
+<p>A retry replays staged changes in order against the refreshed table. It 
replans
+each <code>ExpireSnapshots</code> call at its original position, with the 
original options
+and cutoff time. The plan retains snapshots that a concurrent commit made
+reachable, such as rollback targets and new branch heads. It can expire
+snapshots that a concurrent append moved outside the retention window. A
+transaction that appends and expires snapshots rebases onto a concurrent append
+like any other append.</p>
+<p>Each attempt requires the refs read by expiries that remove snapshots or 
refs
+to be unchanged since loading or refreshing the table. An expiry that removes
+nothing adds no such requirement. If a concurrent commit moves one of those
+refs before the attempt succeeds, it causes another conflict. The next attempt
+replans the expiry. If a concurrent commit creates a ref that the plan did not
+read, that ref does not cause a conflict. Expiring its snapshot also removes
+the ref.</p>
+<ul>
+<li>Retries replan an expiry even if staging or an earlier attempt produced no
+expiry updates.</li>
+<li>If no updates remain, for example because a concurrent commit already 
expired
+the same snapshots, <code>Commit</code> returns the refreshed table without 
committing.</li>
+<li>A transaction containing only expiries with no updates makes no commit 
attempt
+and never refreshes or replans.</li>
+<li>Post-commit cleanup deletes files for snapshots expired by the successful
+attempt, if cleanup is enabled.</li>
+</ul>
+<p>A retry fails with <code>ErrTransactionUnusable</code> if GC is disabled 
after refresh, a
+staged change no longer applies, or replanning changes whether a non-main
+commit branch is removed. <code>ErrTransactionUnusable</code> and 
<code>ErrCommitFailed</code> list
+these cases and which of them a new transaction can retry.</p>
 
                     </main>
 
diff --git a/print.html b/print.html
index 5a807e57e..07df51b55 100644
--- a/print.html
+++ b/print.html
@@ -2106,11 +2106,11 @@ rejects when concurrent deletes touch the same 
files.</p>
 <p>iceberg-go can refresh the table and replay the operation against the latest
 snapshot between attempts. On each retry the table metadata is reloaded, the
 producer's validators re-run against the fresh snapshot, and the commit is
-re-submitted only if it is still valid. <code>RollbackToSnapshot</code>, 
<code>ExpireSnapshots</code>
-and <code>AssertRefSnapshotID</code> pin the refs they guard, so a retry does 
not rebase
-those assertions. If a guarded ref has moved, the commit fails with
-<code>ErrCommitFailed</code> instead of retrying. An 
<code>ExpireSnapshots</code> that removes nothing
-adds no guards.</p>
+re-submitted only if it is still valid. <code>RollbackToSnapshot</code> and
+<code>AssertRefSnapshotID</code> pin their refs, so retries do not rebase 
those assertions.
+If a pinned ref moved, the commit fails with <code>ErrCommitFailed</code> 
instead of
+retrying. <code>ExpireSnapshots</code> does not pin refs. Retries replan it as 
described in
+<a href="concurrent-writes.html#snapshot-expiration">Snapshot 
expiration</a>.</p>
 <p>Retry is <strong>off by default</strong> 
(<code>commit.retry.num-retries</code> is <code>0</code>). Opt in by
 setting the retry properties — see <a 
href="./configuration.html#manifest-and-commit">Manifest and commit</a>
 in the configuration reference for the full list:</p>
@@ -2145,9 +2145,35 @@ retries disabled always has. An abandoned manifest 
rewrite also leaves the
 manifests that later attempts merged. <code>TableCommit</code> refuses a 
transaction whose
 commit failed, since the multi-table path does not refresh. Call 
<code>Commit</code> to
 resume it, or build a new transaction.</p>
-<p>Because an <code>ExpireSnapshots</code> that removes something pins the 
commit branch, a
-transaction that also appends fails instead of rebasing onto a peer's append.
-This is deliberate, since the expiry was computed from the old head.</p>
+<h3 id="snapshot-expiration"><a class="header" 
href="#snapshot-expiration">Snapshot expiration</a></h3>
+<p>A retry replays staged changes in order against the refreshed table. It 
replans
+each <code>ExpireSnapshots</code> call at its original position, with the 
original options
+and cutoff time. The plan retains snapshots that a concurrent commit made
+reachable, such as rollback targets and new branch heads. It can expire
+snapshots that a concurrent append moved outside the retention window. A
+transaction that appends and expires snapshots rebases onto a concurrent append
+like any other append.</p>
+<p>Each attempt requires the refs read by expiries that remove snapshots or 
refs
+to be unchanged since loading or refreshing the table. An expiry that removes
+nothing adds no such requirement. If a concurrent commit moves one of those
+refs before the attempt succeeds, it causes another conflict. The next attempt
+replans the expiry. If a concurrent commit creates a ref that the plan did not
+read, that ref does not cause a conflict. Expiring its snapshot also removes
+the ref.</p>
+<ul>
+<li>Retries replan an expiry even if staging or an earlier attempt produced no
+expiry updates.</li>
+<li>If no updates remain, for example because a concurrent commit already 
expired
+the same snapshots, <code>Commit</code> returns the refreshed table without 
committing.</li>
+<li>A transaction containing only expiries with no updates makes no commit 
attempt
+and never refreshes or replans.</li>
+<li>Post-commit cleanup deletes files for snapshots expired by the successful
+attempt, if cleanup is enabled.</li>
+</ul>
+<p>A retry fails with <code>ErrTransactionUnusable</code> if GC is disabled 
after refresh, a
+staged change no longer applies, or replanning changes whether a non-main
+commit branch is removed. <code>ErrTransactionUnusable</code> and 
<code>ErrCommitFailed</code> list
+these cases and which of them a new transaction can retry.</p>
 <div style="break-before: page; page-break-before: always;"></div><!--
   ~ Licensed to the Apache Software Foundation (ASF) under one
   ~ or more contributor license agreements.  See the NOTICE file
@@ -2777,6 +2803,9 @@ make integration-external-rest
 <p><strong>Branch transactions return initialization errors.</strong> 
<code>Table.NewTransactionOnBranch(branch)</code> now returns 
<code>(*Transaction, error)</code>, and 
<code>Table.NewTransactionOnBranchWithError</code> is removed. Use 
<code>NewTransactionOnBranch</code> and handle its error.</p>
 </li>
 <li>
+<p><strong>Snapshot expiration now tolerates concurrent ref changes.</strong> 
If a concurrent commit moves a ref that the expiry read, a retry or later 
<code>Commit</code> replans instead of failing with 
<code>table.ErrCommitFailed</code>. The committed expiry can differ from 
<code>Transaction.StagedTable</code>. See <a 
href="./concurrent-writes.html#snapshot-expiration">Snapshot expiration</a> for 
cases that now fail with <code>table.ErrTransactionUnusable</code>.</p>
+</li>
+<li>
 <p><strong>Schema JSON constructor uses the Go initialism.</strong> Replace 
<code>iceberg.NewSchemaFromJsonFields</code> with 
<code>iceberg.NewSchemaFromJSONFields</code>.</p>
 </li>
 <li>
diff --git a/releases.html b/releases.html
index ea990ba85..64eb83a45 100644
--- a/releases.html
+++ b/releases.html
@@ -309,6 +309,9 @@
 <p><strong>Branch transactions return initialization errors.</strong> 
<code>Table.NewTransactionOnBranch(branch)</code> now returns 
<code>(*Transaction, error)</code>, and 
<code>Table.NewTransactionOnBranchWithError</code> is removed. Use 
<code>NewTransactionOnBranch</code> and handle its error.</p>
 </li>
 <li>
+<p><strong>Snapshot expiration now tolerates concurrent ref changes.</strong> 
If a concurrent commit moves a ref that the expiry read, a retry or later 
<code>Commit</code> replans instead of failing with 
<code>table.ErrCommitFailed</code>. The committed expiry can differ from 
<code>Transaction.StagedTable</code>. See <a 
href="./concurrent-writes.html#snapshot-expiration">Snapshot expiration</a> for 
cases that now fail with <code>table.ErrTransactionUnusable</code>.</p>
+</li>
+<li>
 <p><strong>Schema JSON constructor uses the Go initialism.</strong> Replace 
<code>iceberg.NewSchemaFromJsonFields</code> with 
<code>iceberg.NewSchemaFromJSONFields</code>.</p>
 </li>
 <li>
diff --git a/searchindex.js b/searchindex.js
index 8db8f1c5e..8506c01c2 100644
--- a/searchindex.js
+++ b/searchindex.js
@@ -1 +1 @@
-Object.assign(window.search, 
{"doc_urls":["introduction.html#iceberg-go","introduction.html#where-to-start","introduction.html#what-works-today","introduction.html#beyond-go","introduction.html#help-and-contribution","install.html#install","install.html#requirements","install.html#installation","getting-started.html#getting-started","getting-started.html#1-install","getting-started.html#2-open-a-local-catalog","getting-started.html#3-create-a-namespace-and-a-table","getting-started.html#
 [...]
\ No newline at end of file
+Object.assign(window.search, 
{"doc_urls":["introduction.html#iceberg-go","introduction.html#where-to-start","introduction.html#what-works-today","introduction.html#beyond-go","introduction.html#help-and-contribution","install.html#install","install.html#requirements","install.html#installation","getting-started.html#getting-started","getting-started.html#1-install","getting-started.html#2-open-a-local-catalog","getting-started.html#3-create-a-namespace-and-a-table","getting-started.html#
 [...]
\ No newline at end of file
diff --git a/searchindex.json b/searchindex.json
index abc44bf7f..fe9cd28e3 100644
--- a/searchindex.json
+++ b/searchindex.json
@@ -1 +1 @@
-{"doc_urls":["introduction.html#iceberg-go","introduction.html#where-to-start","introduction.html#what-works-today","introduction.html#beyond-go","introduction.html#help-and-contribution","install.html#install","install.html#requirements","install.html#installation","getting-started.html#getting-started","getting-started.html#1-install","getting-started.html#2-open-a-local-catalog","getting-started.html#3-create-a-namespace-and-a-table","getting-started.html#4-write-some-arrow-data","get
 [...]
\ No newline at end of file
+{"doc_urls":["introduction.html#iceberg-go","introduction.html#where-to-start","introduction.html#what-works-today","introduction.html#beyond-go","introduction.html#help-and-contribution","install.html#install","install.html#requirements","install.html#installation","getting-started.html#getting-started","getting-started.html#1-install","getting-started.html#2-open-a-local-catalog","getting-started.html#3-create-a-namespace-and-a-table","getting-started.html#4-write-some-arrow-data","get
 [...]
\ No newline at end of file

Reply via email to