Skip to content

Iceberg: reuse the Puffin object metadata across deletion-vector reads - #2419

Merged
zvonand merged 4 commits into
antalya-26.6from
puffin-dv-metadata-threading
Oct 24, 2026
Merged

zvonand merged 4 commits into
antalya-26.6from
puffin-dv-metadata-threading

Conversation

@UnamedRus

Copy link
Copy Markdown
Collaborator

Changelog category (leave one):

  • Performance Improvement

Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):

Reduce number of Puffin file HEAD requests

Documentation entry for user-facing changes

...

CI/CD Options

Exclude tests:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful tests
  • Unit tests
  • Performance tests
  • Aarch64 tests
  • All with ASAN
  • All with TSAN
  • All with MSAN
  • All with UBSAN
  • All with Coverage
  • All Regression
  • Disable CI Cache

Regression jobs to run:

  • Fast suites (mostly <1h)
  • Aggregate Functions (2h)
  • Alter (1.5h)
  • Benchmark (30m)
  • CAS (content-addressed storage; Antalya only)
  • ClickHouse Keeper (1h)
  • Iceberg (2h)
  • LDAP (1h)
  • OAuth (5m)
  • Parquet (1.5h)
  • RBAC (1.5h)
  • SSL Server (1h)
  • S3 (2h)
  • S3 Export (2h)
  • Swarms (30m)
  • Tiered Storage (2h)

Loading one deletion vector issued two or three S3 `HEAD` requests against the
same object. `loadDeletionVector` already calls `getObjectMetadata` for the
Puffin file to read its etag for the `PuffinFilesCache` key, but then passes
only the path down: `readFooterBlobs` and `loadDeletionVectorUncached` each
build a fresh `RelativePathWithMetadata` with empty metadata, and
`createReadBuffer` fills it in with another `getObjectMetadata` for every
buffer it opens.

Thread the already-fetched metadata into both helpers instead.
`createReadBuffer` issues its own request only when
`RelativePathWithMetadata::metadata` is empty, so seeding it removes the
redundant requests without changing which bytes are read. The two call sites
that run before the etag fetch keep passing `std::nullopt` and behave as
before.

On a lake scan with cold deletion vectors this is the dominant cost: a profile
of one such query recorded 5706 `S3HeadObject` requests against 2833
`PuffinFilesRead`, with about 94 seconds spent in `HEAD` alone.

Reusing a single metadata snapshot for every read of the same Puffin file is
also more coherent than re-fetching it. Iceberg data and delete files are
immutable, so the separate requests could only ever have observed the same
object.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Workflow [PR], commit [afc914b]

@blau-ai blau-ai mentioned this pull request Sep 23, 2026
9 of 30 tasks
@alsugiliazova alsugiliazova mentioned this pull request Sep 24, 2026
9 of 30 tasks
ianton-ru
ianton-ru previously approved these changes Sep 28, 2026
Move the `IObjectStorage.h` include next to the other alphabetically ordered headers, and label the positional `puffin_metadata` argument at the `loadDeletionVectorUncached` call sites.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@zvonand

zvonand commented Oct 24, 2026

Copy link
Copy Markdown
Member

@blau-ai

@blau-ai

blau-ai commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator

CI triage — afc914b

Verdict: 5 red checks, none of them a test failure caused by this PR. 4 are infra/flaky/formality; 1 is the aggregate roll-up gate. Builds, Fast test, all stateless Iceberg functional tests, integration tests (0/~6800), unit tests, stress, and the Iceberg (1) regression shard are all green.

Check Classification PR-caused?
DCO Missing Signed-off-by ⚠️ yes (formality)
Stateless (amd_asan_ubsan, cas s3, 2/2) — 02867_nullable_primary_key_final Transient / infra ❌ no
Stateless (amd_binary, cas storage) — 01164_detach_attach_partition_race Known flaky race ❌ no
RegressionTestsRelease / Iceberg (2) / iceberg_2 Wall-clock timeout ❌ no
PR Aggregate roll-up of the above —

1. DCO — ⚠️ the only thing actually tied to this PR

ACTION_REQUIRED. The two substantive commits (33aed19, 13f5880) have Co-Authored-By: trailers but no Signed-off-by: line, which DCO requires. This is a sign-off formality, unrelated to code correctness.

Fix (needs the author — I can't, it requires rewriting history + force-push):

git rebase --signoff antalya-26.6   # or: git commit --amend -s on each, then push
git push --force-with-lease origin puffin-dv-metadata-threading

2. 02867_nullable_primary_key_final — ❌ not PR-caused (transient)

CI's own rerun diagnosis settles it:

Step 1: Re-running with the same randomized settings… Runs: 14, Failed: 0, Passed: 14. Result: All reruns passed. The failure is not reproducible (likely a transient issue).

The stderr was AWSClient: Response status: 404, Not Found — a transient object-store hiccup in the CAS-S3 harness. Nothing to do with the deletion-vector path. Safe to re-run.

3. 01164_detach_attach_partition_race — ❌ not PR-caused (flaky)

Runs: 5, Failed: 1, Passed: 4. Result: Failure is flaky (1/5 reruns failed).

A DETACH/ATTACH PARTITION concurrency test; the warning is CasWriteRetryLater: CAS write could not be committed … retrying later — the CAS-storage test harness, not Iceberg. This test has no relation to the changed file. Safe to re-run.

4. RegressionTestsRelease / Iceberg (2) / iceberg_2 — ❌ not PR-caused (timeout)

This is a wall-clock cancellation, not a test failure. Job annotations:

The job has exceeded the maximum execution time of 3h30m0s · The operation was canceled.

It was cut off at 211 min with no results report uploaded (the S3 results.json returns NoSuchKey), so no test assertion failed. iceberg_2 is the heavy shard — it runs /iceberg/iceberg engine/glue catalog/*, /iceberg/iceberg table engine/*, and /iceberg/export partition/* against LocalStack — and sits right at the cap, whereas iceberg_1 finished green in 87 min in the same run.

This PR cannot plausibly have caused a slowdown: it is a pure read-path optimization that removes redundant S3 HEAD requests on the deletion-vector load path (seeds the already-fetched ObjectMetadata into readFooterBlobs / loadDeletionVectorUncached so createReadBuffer doesn't re-issue getObjectMetadata). Same bytes read, fewer requests — if anything it makes these suites faster. The two pre-etag call sites correctly keep passing std::nullopt, and the ctor RelativePathWithMetadata(String, std::optional<ObjectMetadata> = nullopt) makes the change behavior-preserving. Recommend re-running; if iceberg_2 keeps hitting the cap it's a suite-sharding/runner issue to raise with the regression maintainers, independent of this change.


Bottom line: no code fix is needed for the test failures — they're flaky/infra and should clear on a re-run. The only genuine to-do is adding the DCO sign-off to the two commits.

🤖 Generated with Claude Code

@zvonand
zvonand merged commit d05543d into antalya-26.6 Oct 24, 2026
308 of 315 checks passed
@zvonand zvonand added the port-antalya PRs to be ported to all new Antalya releases label Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

antalya antalya-26.6 port-antalya PRs to be ported to all new Antalya releases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants