Fix: first append to a REST catalog table fails with a -1 ref assertion - #33
Merged
Merged
Conversation
Java writes `"current-snapshot-id": -1` for "no current snapshot" on v1/v2
tables instead of omitting the field, and the spec asks other implementations
to read that sentinel as null. Icebird passed it straight through, into both
the `parent-snapshot-id` of the new snapshot and the `assert-ref-snapshot-id`
requirement sent with the commit.
Reproduced against apache/iceberg-rest-fixture, which is the Java
implementation: creating a v2 table returns `"current-snapshot-id": -1`, and
the first icebergAppend is rejected server-side with
CommitFailedException: Requirement failed: branch or tag main is missing,
expected -1
at UpdateRequirement$AssertRefSnapshotID.validate(UpdateRequirement.java:123)
which matches the REST spec: a null `snapshot-id` means "the ref must not
already exist", and -1 instead asserts that main points at snapshot -1. So no
first append to a REST catalog table can ever succeed. Icebird reads the 409
as a concurrent-commit conflict and retries, so the call spends ~70s over 50
attempts before failing with "due to concurrent commits" — naming the wrong
cause, with the real one visible only in the server log. After the fix the
same append commits in ~20ms.
The file catalog path does not fail, but writes `parent-snapshot-id: -1` into
metadata, pointing at a snapshot that cannot exist; the spec omits that field
for a snapshot with no parent. Every engine-written empty table in
test/files/hyperparam-iceberg carries the sentinel (java/bunnies v1,
spark/rename_column v1, athena/example 00000-*).
Normalize in one helper, `normalizeCurrentSnapshotId`, applied at all three
sites that read `current-snapshot-id`: the snapshot/requirement builder in
buildSnapshotUpdate, the local requirement check in checkRequirements, and the
ref CAS fallback in stageSetRef. Normalizing only the builder would make the
file catalog reject its own commit, since checkRequirements would still
compare the emitted null against a raw -1.
platypii
approved these changes
Aug 12, 2026
Contributor
|
Good catch, thanks @ryo-rm ! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Java writes
"current-snapshot-id": -1for "no current snapshot" on v1/v2tables instead of omitting the field, and the spec asks other implementations
to read that sentinel as null. Icebird passed it straight through, into both
the
parent-snapshot-idof the new snapshot and theassert-ref-snapshot-idrequirement sent with the commit.
The result is that no first append to a REST catalog table can succeed.
The server rejects the commit; icebird reads the 409 as a concurrent-commit
conflict and retries, so the call spends ~70s over 50 attempts before failing
with
due to concurrent commits— naming the wrong cause, with the real onevisible only in the server log.
Repro
Against
apache/iceberg-rest-fixture, i.e. the Java implementation itself:docker run -d --rm --name ice-rest -p 8181:8181 apache/iceberg-rest-fixture:latest # wait for http://localhost:8181/v1/config to answer node repro.mjsOn
master:docker logs ice-restshows the real cause:which matches the REST spec — a null
snapshot-idmeans "the ref must notalready exist", and -1 instead asserts that main points at snapshot -1.
On this branch:
Fix
The file catalog path does not fail, but writes
parent-snapshot-id: -1intometadata, pointing at a snapshot that cannot exist; the spec omits that field
for a snapshot with no parent. Every engine-written empty table in
test/files/hyperparam-icebergcarries the sentinel (java/bunnies v1,spark/rename_column v1, athena/example 00000-*).
Normalizing only
buildSnapshotUpdatewould make the file catalog reject itsown commit, since
checkRequirementswould still compare the emitted nullagainst a raw -1 — so
normalizeCurrentSnapshotIdis applied at all threesites that read
current-snapshot-id.