Repository navigation
Support atomic multi-table commits through the REST catalog - #4123
kotwal-itpro wants to merge 3 commits into
Conversation
8c59067 to
920d5a9
Compare
|
This is ready for review. CI is green, and the integration tests run against There are three open questions under "Not in this PR" in the description, and I'd welcome a maintainer's view on them before polishing further: retry on conflict, creating tables in the same commit, and the @kevinjqliu @Fokko, would one of you have time to take a look? |
| orders_tx = orders.transaction() | ||
| orders_tx.append(new_orders) | ||
| items_tx = items.transaction() | ||
| items_tx.append(new_items) | ||
|
|
||
| catalog.commit_transactions(orders_tx, items_tx) |
There was a problem hiding this comment.
I'm a big fan of the context-manager, should we use that in this example as well?
There was a problem hiding this comment.
Good idea. with orders.transaction() as tx: can't be used here as it is, since leaving that block commits the table on its own. So I added Catalog.transaction(), which commits every table in one commit_transactions call when the block exits, and nothing if it raises:
with catalog.transaction() as tx:
tx.table(orders).append(new_orders)
tx.table(items).append(new_items)The docs now lead with this and keep commit_transactions(...) as the explicit form. It's in 6a29452, with unit tests for the commit on exit, no commit on an exception, and a table from another catalog, plus an integration test. Happy to rename it (multi_table_transaction()?) or drop it if you'd rather keep this PR to the explicit call.
920d5a9 to
68697df
Compare
Add Catalog.commit_transactions(*transactions), which commits the staged changes of several table transactions in one request to the REST catalog's transactions/commit endpoint: either every table is updated, or none of them. Each table is refreshed after a successful commit. Other catalogs raise NotImplementedError. A transaction can be given only once, each table at most once, and creating a table is not supported. Conflicts are not retried. On a definite failure (409, 404) the files written for the transactions are deleted; on an unknown outcome (500, 502, 504) they are kept, as in commit_transaction. The endpoint is assumed supported by servers that don't list their endpoints, as in the Java client. Closes apache#4095 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZBQVsXRmKT8WXDr2qSfbd
A delete whose filter matched no rows stages no updates, so commit_transactions skipped it and a concurrent change to that table went unnoticed. Send it as a table change that asserts the branch still points at its starting snapshot, so the catalog checks it in the same atomic commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZBQVsXRmKT8WXDr2qSfbd
Stage the changes of each table through tx.table(table) and commit them atomically with commit_transactions when the block exits; nothing is committed if it raises. The per-table Transaction context manager can't be used for this, because its exit commits that table alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZBQVsXRmKT8WXDr2qSfbd
68697df to
6a29452
Compare
Closes #4095
Rationale for this change
The REST spec's
POST /v1/{prefix}/transactions/commitapplies changes to several tables in one atomic operation, and the Java client exposes it asRESTCatalog.commitTransaction(...). PyIceberg had no way to call it, so write-audit-publish across tables or a backfill of related tables couldn't be published atomically from Python.This adds
Catalog.commit_transactions(*transactions):What it does
CommitTransactionRequest, adding the sameAssertTableUUIDthatcommit_transactionadds. The endpoint returns 204, so each table is refreshed afterwards and the transactions are cleared.CatalograisesNotImplementedError, so they can add support later.commit_table: 409 →CommitFailedException, 404 →NoSuchTableError, 500/502/504 →CommitStateUnknownException.commit_transactionfollows. A failed transaction can't be reused.CreateTableTransaction. Transactions without staged changes are skipped, and a call with only empty ones sends nothing. The exception is a snapshot operation that staged nothing, such as a delete whose filter matched no rows: it is sent withassert-ref-snapshot-idon its starting snapshot and no updates, so a concurrent change to that table fails the whole commit.commit_transactionvalidates the same case on the client; here the catalog checks it atomically with the other tables (the same requirement-only pattern as Multi-table commit does not isolate requirements on tables without updates: concurrent transactions both succeed polaris#5807).transactions/commitis assumed supported, as in the Java client'sDEFAULT_ENDPOINTS.Not in this PR (open questions)
assert-create) could come later.Catalog.commit_transactions(*transactions)as proposed in Support atomic multi-table commits through the REST catalog (transactions/commit) #4095. Glad to change it, for example to a context manager.Are these changes tested?
tests/catalog/test_rest.py: 13 tests with a mocked server. They cover the request body, the prefix, each error status, file cleanup only when nothing was applied, skipping empty transactions, the requirement sent for a delete that matched nothing, the input checks and an unsupported endpoint.tests/catalog/test_base.py: the base catalog raisesNotImplementedError.tests/integration/test_rest_catalog.py, against theapache/iceberg-rest-fixturewith S3-compatible storage:CommitFailedException, and neither table gets the staged rows.make test: 4273 passed.make lintpasses on the changed files.Are there any user-facing changes?
Yes: the new
Catalog.commit_transactionsmethod, and a "Multi-table commits" section inmkdocs/docs/api.md.I used an AI coding assistant while writing this change; I reviewed and tested all of it.
🤖 Generated with Claude Code
https://claude.ai/code/session_011ZBQVsXRmKT8WXDr2qSfbd