Skip to content

fix(table): fast-append must inherit all parent manifests unconditionally#4

Open
cassio-paesleme wants to merge 3 commits intodocker:mainfrom
cassio-paesleme:fix/fast-append-v3
Open

fix(table): fast-append must inherit all parent manifests unconditionally#4
cassio-paesleme wants to merge 3 commits intodocker:mainfrom
cassio-paesleme:fix/fast-append-v3

Conversation

@cassio-paesleme
Copy link
Copy Markdown

@cassio-paesleme cassio-paesleme commented Apr 10, 2026

Problem

Data written by Athena (or Spark/Trino) disappears after any iceberg-go fast-append. The new snapshot only contains rows written by iceberg-go; all previously existing data becomes invisible to queries.

Root cause

Two bugs in fastAppendFiles.existingManifests():

Bug 1 — Field name mismatch

The Iceberg spec names the manifest list count fields added_files_count (field 504), existing_files_count (505), deleted_files_count (506). Athena uses added_data_files_count, existing_data_files_count, deleted_data_files_count for the same logical fields. When iceberg-go read an Athena-written manifest list, it found nothing at the spec name, read 0 for all counts, and HasAddedFiles() returned false for every Athena manifest.

Fix: Both naming conventions are handled on read — the manifestFile struct carries both avro tags and AddedDataFiles() coalesces whichever is non-zero. Both names are written on every manifest list write (same field-id, different names) so Athena readers find their expected name and spec-compliant readers find theirs.

Bug 2 — Manifest inheritance filter

existingManifests() kept only manifests where HasAddedFiles() || HasExistingFiles(). Because of Bug 1, both returned false for all Athena manifests — they were silently dropped. The new snapshot only referenced iceberg-go's newly written files.

Fix: A fast-append never removes data. Remove the filter and return previous.Manifests() directly — all parent manifests are inherited unconditionally.

Tests

TestFastAppendInheritsZeroCountManifests — reproduces Bug 2: constructs a parent snapshot with Athena-style manifests (zero counts), fast-appends a new data file on top, and asserts all parent manifests are present in the resulting snapshot.

TestReadManifestListAthenaFieldNames — reproduces Bug 1: encodes an OCF manifest list using Athena field names, reads it back via ReadManifestList, and asserts the counts are decoded correctly.

Note on field-ids

added_files_count and added_data_files_count are the same logical Iceberg field (id 504) — Athena diverged from the spec name. Both names carry field-id 504 in the writer schema because they represent the same field; this is what field renaming in schema evolution looks like. Spark, Trino, and other spec-compliant readers ignore the unknown added_data_files_count field; Athena ignores added_files_count.

🤖 Generated with Claude Code

hcrosse added 2 commits March 30, 2026 14:12
Add write.parquet.root-repetition property (required/optional/repeated,
default: required) to control the Parquet root schema element's
repetition type. arrow-go defaults to Repeated, which Snowflake
interprets as one-level list encoding and rejects files with list
columns. Defaulting to Required aligns with the Parquet spec and
matches arrow-rs, pyarrow, and parquet-java behavior.
iter.Pull(args.counter) was called unconditionally, but in the
partitioned path newWriterFactory creates its own iter.Pull and the
original stopCount was never called, leaking one goroutine per write.

Move iter.Pull into the unpartitioned branch where it is actually used.
Add a regression test confirming goroutine count stays stable.
@cassio-paesleme cassio-paesleme force-pushed the fix/fast-append-v3 branch 5 times, most recently from 3b42d06 to b33dcb2 Compare April 10, 2026 15:12
}
previous, err := fa.base.txn.meta.SnapshotByID(fa.base.parentSnapshotID)
if err != nil {
return nil, fmt.Errorf("could not find parent snapshot %d: %w", fa.base.parentSnapshotID, err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice

…ally

fastAppendFiles.existingManifests() filtered parent manifests using
HasAddedFiles() || HasExistingFiles(). Both methods return false when the
manifest list entry has added_files_count=0 and existing_files_count=0,
which is the standard Iceberg v2 representation for inherited manifests
written by external writers such as Athena, Spark, and Trino.

As a result, any data written by an external writer was silently dropped
from the snapshot on the next iceberg-go fast-append. Queries against
the table after the append returned only the iceberg-go-written rows;
all previously existing data became invisible.

A fast-append never removes or overwrites data files, so the correct
behaviour is to inherit all manifests from the parent snapshot
unconditionally. Remove the filter and return previous.Manifests()
directly.

Fixes: data loss when appending to an Iceberg table that was previously
written by Athena or other external writers.

Tested: new TestFastAppendInheritsZeroCountManifests reproduces the bug
(FAIL before patch, PASS after) and the full ./table/... suite passes
with no regressions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants