Skip to content

Iceberg deletion vector support - #2119

Open
ianton-ru wants to merge 5 commits into
antalya-26.6from
feature/antalya-26.6/iceberg-puffin-deletion-vectors-read
Open

Iceberg deletion vector support#2119
ianton-ru wants to merge 5 commits into
antalya-26.6from
feature/antalya-26.6/iceberg-puffin-deletion-vectors-read

Conversation

@ianton-ru

@ianton-ru ianton-ru commented Jul 27, 2026

Copy link
Copy Markdown

Changelog category (leave one):

  • New Feature

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

Iceberg deletion vector support

Documentation entry for user-facing changes

Iceberg deletion vector support.
Based on uncommited Puffin file reader ClickHouse#109990.

CI/CD Options

Exclude tests:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful 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)
  • 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)

@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

Workflow [PR], commit [4b30a8f]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5bd1ca373d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const bool effective_need_only_count = need_only_count;
#endif

const bool can_use_count_cache = effective_need_only_count && headers_requested

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Negate the _headers guard before using the count cache

When a count-style query requests _headers and a cached row count exists, this predicate enables the ConstChunkGenerator shortcut, so no data GET occurs and the result can contain metadata-probe/HEAD headers instead of the requested GET headers. Conversely, ordinary count queries without _headers can no longer use the cache. Require !headers_requested here so the shortcut is skipped rather than falling back to different headers.

AGENTS.md reference: AGENTS.md:L153-L153

Useful? React with 👍 / 👎.

@@ -0,0 +1,46 @@
---

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add mandatory sidebar fields to the new Puffin docs

Both this new page and docs/en/interfaces/formats/Puffin/PuffinMetadata.md omit the mandatory sidebar_label and sidebar_position frontmatter fields, leaving the new documentation pages noncompliant with the repository's required metadata. Add both fields to each page's frontmatter.

AGENTS.md reference: AGENTS.md:L9-L9

Useful? React with 👍 / 👎.

ianton-ru and others added 4 commits July 28, 2026 09:49
Restore `!headers_requested` after a merge-conflict resolution inverted the condition, which disabled the cache for ordinary counts and allowed wrong headers on cache hits.

Co-authored-by: Cursor <cursoragent@cursor.com>
Include `sidebar_label`, `sidebar_position`, and input/output flags so the new pages match AGENTS.md and peer format docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Without the guard, Fast test builds that disable Avro fail to compile because IcebergPathFromMetadata is only visible when Avro is enabled.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ianton-ru

ianton-ru commented Jul 28, 2026

Copy link
Copy Markdown
Author

Reviewer notes: Altinity PR #2119

Title: Iceberg deletion vector support
Base: antalya-26.6
Branch: feature/antalya-26.6/iceberg-puffin-deletion-vectors-read
Scope: read-only Iceberg v3 deletion vectors (deletion-vector-v1 in Puffin files). No DV write path.


Motivation

Iceberg v3 replaces classic position-delete files with deletion vectors stored as roaring bitmaps inside Puffin files. Without this, ClickHouse can return rows that engines like Spark already treat as deleted.
This PR ports and hardens that read path onto Antalya 26.6, based on upstream work around the Puffin reader (ClickHouse#109990) plus follow-up correctness fixes.


What landed (architecture)

1. SQL formats Puffin / PuffinMetadata

  • Puffin — materializes deleted row positions from deletion-vector-v1 blobs (referenced_data_file, deleted_rows).
  • PuffinMetadata — one row per footer blob entry (type, offsets, fields, etc.).
  • Shared low-level reader: PuffinDeletionVectorReader (envelope peek, CRC, cardinality checks, materialization ceiling before large allocates).
  • Docs under docs/en/interfaces/formats/Puffin/.

2. Iceberg table / table-function read path

  • Manifest parsing recognizes deletion-vector content and attaches DV metadata to data files.
  • IcebergDeletionVector loads the bitmap (optionally via PuffinFilesCache).
  • DeletionVectorTransform filters rows by file-local positions carried in excluded_rows.
  • Wired in StorageObjectStorageSource::createReader.

3. Cache and settings

  • Setting use_puffin_files_cache (default true) caches parsed deletion vectors keyed by path/etag/offset/size/referenced file.
  • Server setting + SYSTEM flush support for the cache.
  • Settings history entry for Antalya: 26.6.1.20001.altinityantalya (cherry-pick cleaned upstream 26.7/26.8 noise).

4. Correctness / fail-closed behavior (important for review)

Area Behavior
Cluster protocol Refuse to serialize tasks with non-empty excluded_rows / Iceberg eq+pos deletes / file_bucket_info when worker protocol is too old (would otherwise silently return deleted or duplicated rows).
DV vs equality deletes Apply DeletionVectorTransform before equality FilterTransform so file row numbers stay valid.
Count shortcuts Skip count-from-files cache when DVs / buckets / Iceberg equality+position deletes are present; require explicit total-equality-deletes == 0 for snapshot totalRows shortcut; disable need_only_count when equality deletes exist.
_headers + count cache Count-cache shortcut skipped when _headers is requested (otherwise cache hit returns wrong headers).
Data format Reject DVs on non-Parquet data files.
Avro-disabled builds IcebergDeletionVector.cpp wrapped in #if USE_AVRO (Fast test without Avro).

Tests

  • GTests: Puffin envelope/cardinality/bounds, files cache, DV-before-equality order, Iceberg count shortcuts, data-object clone, Parquet row-delete guards, ParquetV3 need_only_count + buckets.
  • Stateless: 04077 / 04257 / 04260 / 04548 / 04549 / 04612 / 04613 (Puffin formats); 0426104263 (Iceberg DV + cache) with MinIO fixtures under tests/queries/0_stateless/data_minio/dv_puffin_warehouse/ and data_puffin/.
  • Integration: tests/integration/test_storage_iceberg_with_spark/test_deletion_vectors.py.

Follow-up commits on top of the main cherry-pick

These are small fixes after the initial Iceberg deletion vector commit / review / CI:

  1. !headers_requested polarity — merge-conflict typo had inverted the count-cache guard.
  2. Puffin docs frontmattersidebar_label / sidebar_position (+ input/output flags) for AGENTS.md compliance.
  3. USE_AVRO guard on IcebergDeletionVector.cpp — fixes Fast test compile without Avro.
  4. Settings history — Antalya version key only for use_puffin_files_cache.

Out of scope / intentional limits

  • No writing Iceberg deletion vectors.
  • Non-deletion-vector-v1 Puffin blob types are skipped by the SQL Puffin format.
  • Selecting only referenced_data_file skips payload I/O (and thus envelope/CRC/materialization checks) by design.
  • Poco JSON Int64 wrap of values near 2^63 in footer parsing was reviewed and deferred (not treated as a blocker in this PR).

Suggested review focus

  1. Manifest → excluded_rows attachment and path matching (referenced_data_file vs data file).
  2. Transform order (DV before equality) and cluster fail-closed protocol checks.
  3. Count / totalRows / cache interactions — easy to silently over-count.
  4. Resource bounds in PuffinDeletionVectorReader (cardinality ceiling, envelope peek before allocate).
  5. Antalya-specific settings history entry vs upstream versioning.

Links

@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b30a8f3e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +63 to +64
for (UInt64 position : deleted_positions)
bitmap->add(static_cast<size_t>(position));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject positions beyond the data file

When a deletion vector contains a position greater than or equal to the referenced data file's manifest record_count, this loop inserts it without validation; the transform never encounters that position and silently returns rows that the manifest claims were deleted. This can also make a full scan disagree with Iceberg's trivial count() calculation, which subtracts the deletion vector's record_count. Pass the data file row count into the loader and reject out-of-range positions.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the note. Agreed that we do not currently bound-check positions against the data file’s manifest record_count (that field lives on IcebergDataObjectInfo / the data-file entry and is not passed into loadDeletionVector).

A few clarifications on impact:

  1. In-range deletes are unaffected. DeletionVectorTransform only filters rows whose file-local indices appear in the bitmap (rb_contains on ChunkInfoRowNumbers). Positions past the real file never match a produced row, so they are no-ops for the scan — they do not revive deleted in-range rows.
  2. What “manifest claims deleted” means here. The DV entry’s own record_count is already checked exactly against the roaring bitmap cardinality in PuffinDeletionVectorReader. The gap is only vs the data file row count. OOB positions mean some declared deletes are ineffective, not that valid deletes are skipped.
  3. Count disagreement with the snapshot totalRows shortcut (total_records - total_position_deletes) would require the table metadata/summary itself to be inconsistent with the on-disk DV (corrupt or non-spec writer). For normal Spark/Iceberg-written tables, writers keep positions within the file.

So this is a reasonable fail-closed hardening against bad metadata (and we could pass object_info->info.record_count into the loader and reject OOB positions), but we do not treat it as a correctness bug for well-formed Iceberg v3 deletion vectors. Prefer to leave it out of this PR unless reviewers want the extra check now.

if (etag.empty())
return std::nullopt;

return PuffinFilesCacheKey{file_path, etag, content_offset, content_size_in_bytes, referenced_data_file};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include declared cardinality in the cache key

When two manifest versions reference the same Puffin path, ETag, slice, and data file but declare different record_count values, this key aliases them even though deserialization validates the bitmap against that declaration. After one version populates the cache, the other receives the cached bitmap without running its cardinality check, so malformed or inconsistent metadata is accepted depending on query order. Include the expected cardinality in the key or revalidate it on every cache hit.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks. Confirmed: tryCreateKey / PuffinFilesCacheKey currently use (file_path, etag, content_offset, content_size_in_bytes, referenced_data_file) and omit the manifest record_count passed as expected_cardinality.

Why this is weaker than it sounds for query correctness:

  1. A cache entry is only created after a successful uncached load. That path always runs readDeletionVectorFromPuffin(..., expected_cardinality), which requires the roaring bitmap cardinality to exactly match the declared value. A mismatched declaration never populates the cache.
  2. Same path + etag + slice ⇒ same blob bytes. The true bitmap (and thus true cardinality) is fixed. The only realistic “two declarations, one key” case is inconsistent metadata for the same physical object — not two different bitmaps aliased together.
  3. On a later hit with a wrong declaration, we skip re-checking cardinality and return the already-validated bitmap. Scan filtering still matches the on-disk DV; we fail to detect that this manifest entry’s record_count disagrees with the blob.

So the finding is a real gap for fail-closed metadata validation (and adding cardinality to the key, or checking bitmap->size() == expected_cardinality on hit, would close it). It is not a silent wrong-bitmap / wrong-answer bug for consistent Iceberg tables.

Prefer to leave the key as-is for this PR unless reviewers want the stricter check now; happy to follow up with cardinality in the key (and a small gtest) if desired.

@ianton-ru

Copy link
Copy Markdown
Author

Failed tests looks unrelated to PR.
SQLLogic fails with not enough memory error.

Stateless test 04033_tpc_ds_q63 has timeout, marked as flaky
Stateless test 00071_merge_tree_optimize_aio also has a timeout.
Both are not related to puffin files.

Regression tests are useless unless all Antalya features are merged in the branch. Were turned on by default, I have turned them off only right now.

@ianton-ru ianton-ru changed the title [WIP] Iceberg deletion vector support Iceberg deletion vector support Jul 29, 2026
@ilejn

ilejn commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Doesn't it make sense to emphasize differences between this PR and 109990 (unless you are totally skeptical about its chances to be accepted to upstream)?
Is cache introduced by this PR?

@ianton-ru

Copy link
Copy Markdown
Author

109990 Is only a Puffin reader.
Current PR is a deletion vector implementation using that reader, cause deletion vectors for Iceberg are in puffin files.
Cache is only in current PR.

@ianton-ru

Copy link
Copy Markdown
Author

By the way I want to create PR with this changes in upstream too, but it is possible only after accept of puffin reader.
Other option is to close 109990 and create a new one with all together, but I think first way has more chances.

@mkmkme

mkmkme commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

@codex review

@mkmkme

mkmkme commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

@blau-ai

@blau-ai

blau-ai commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

CI triage for #2119

Verdict: 8 red checks — 0 caused by this PR. 5 are provably pre-existing on the target branch, 3 are infra/flaky (unrelated to the Iceberg/Puffin code this PR touches). Builds and Fast test are green, so the code compiles and unit/gtests pass.

The tested SHA is 4b30a8f (merge of the PR head into antalya-26.6).

Evidence baseline

I re-ran the same regression suites against the target branch itself (antalya-26.6, MasterCI run 29883716329, 2026-07-22). Iceberg (1), Iceberg (2), OAuth, S3Export (partition), Swarms, and the distributed-plan Stateless config all fail there too — without any of this PR's changes.

Per-check breakdown

Check Class Root cause / note
Regression · Iceberg (1) iceberg_1 Pre-existing Same 9 failing scenarios as base branch
Regression · Iceberg (2) iceberg_2 Pre-existing Same failing scenarios as base branch
Regression · Swarms swarms Pre-existing / infra 1326/1520 fail, swarm sanity breaks at setup
Regression · OAuth oauth Pre-existing Fails on base branch; unrelated to Iceberg
Regression · S3Export (partition) Pre-existing Fails on base branch; unrelated to Iceberg
SQLLogic test Infra / threshold Ran too few tests (5,650,364 < min 5,939,581), i.e. timed out at 2h15m; does not exercise Iceberg/Puffin
Stateless (amd_debug, parallel) Flaky (likely) Failed: 1 / Passed: 10858; see caveat below
Stateless (amd_debug, distributed plan, s3) Pre-existing / flaky Failed: 1, Broken: 3; this config also fails on base

Why the Iceberg failures are not yours

Both Iceberg suites fail with the identical scenario list on the PR and on the base branch — .../swarm/swarm examples, .../iceberg iterator race condition, .../sort key timezone/*, .../system tables partition sorting keys. Every one of them ends in the same server error:

Code: 115. DB::Exception: Unknown setting 'object_storage_cluster'. (UNKNOWN_SETTING)

object_storage_cluster is not defined in antalya-26.6 — it's explicitly commented out in src/Core/SettingsChangesHistory.cpp:490 (// {"object_storage_cluster", "", "", "Antalya: New setting"}). The testflows swarm tests reference it, so they fail on any PR against this branch until that setting is ported. Your diff does not touch this setting (it only adds use_puffin_files_cache), and notably the iceberg iterator race condition test — which lives near code you modified (IcebergIterator.cpp) — fails from the same object_storage_cluster error on the untouched base branch, so it is not a regression from your change.

Caveats (honest limits of this triage)

  • I cannot fetch the S3-hosted report.html/json.html artifacts from this container (no network beyond git/gh), and the GitHub Actions job logs contain only runner setup — the per-test detail lives in the S3 reports. So for the two Stateless Failed: 1 cases I could not extract the failing test name.
  • The plain Stateless (amd_debug, parallel) config passed on the 2026-07-22 base run, so I can't call that single failure strictly pre-existing. But one failure out of ~10,900 in a debug-parallel run is the classic flaky signature, and this PR changes only Iceberg/Puffin/format code — an unrelated stateless test regressing is implausible. Please glance at its report: if the failing test is an Iceberg/Puffin/iceberg/deltalake test, ping me and I'll dig in; otherwise a re-run should clear it.

Suggested next steps

  1. No code fix is warranted for the PR itself based on this CI — nothing red is attributable to the diff.
  2. Re-run SQLLogic and Stateless (amd_debug, parallel) — both look like timeout/flake and passed on base.
  3. The Iceberg/Swarms/OAuth/S3Export reds are a branch-level problem (missing object_storage_cluster setting + swarm infra) tracked independently of this PR; they'll stay red here until fixed on antalya-26.6.

Analysis only — I did not build or run anything (a full build/test isn't possible in my container). Correctness of the feature will be validated by CI once the branch-level regression blockers are resolved. Happy to open a fix PR if you want me to port the object_storage_cluster setting or investigate a specific stateless test — just say the word.

@mkmkme

mkmkme commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

I'm going to post the audit-report just for the sake of it. I'll review myself if it's applicable or not, but if you want, you can respond to some of them :)


High: Unsafe use of unvalidated Roaring bitmaps
    Impact: Malformed Puffin input can cause UB, memory corruption, or incorrect deleted positions.
    Anchor: PuffinBlockInputFormat.cpp / readRoaringPortableSafe; PuffinDeletionVectorReader.cpp / readRoaringPortableSafe
    Trigger: Internally inconsistent bitmap accepted by `Roaring::readSafe`.
    Why defect: Pinned CRoaring requires `roaring_bitmap_internal_validate` before operating on untrusted bitmaps.
    Fix direction: Validate every deserialized bitmap before size, cardinality, or iteration operations.
    Regression test direction: Valid-CRC malformed array/run containers under ASan.

High: Manifest can bind another file’s deletion vector
    Impact: Rows from file A can be silently deleted using file B’s bitmap.
    Anchor: IcebergDeletionVector.cpp / `loadDeletionVectorUncached`
    Trigger: Equal-cardinality blobs with a manifest pointing to the wrong offset.
    Why defect: The loader reads the raw slice without validating its Puffin footer descriptor.
    Fix direction: Bind type, offset, length, referenced file, and cardinality against the footer.
    Regression test direction: Swap offsets between two equal-cardinality DVs.

Medium: DV positions are not bounded by data-file row count
    Impact: Invalid deletes are silently ineffective and scans can disagree with optimized counts.
    Anchor: IcebergDeletionVector.cpp / `loadDeletionVectorUncached`
    Trigger: A position greater than or equal to the data file’s `record_count`.
    Fix direction: Pass and validate the data-file row count.
    Regression test direction: Test boundary and above-boundary positions.

Medium: Cache hits bypass cardinality validation
    Impact: Conflicting metadata is accepted depending on query order.
    Anchor: PuffinFilesCache.h / `PuffinFilesCacheKey`; IcebergDeletionVector.cpp / `loadDeletionVector`
    Trigger: Same ETag/slice/file with different declared cardinalities.
    Fix direction: Include cardinality in the key or revalidate every hit.
    Regression test direction: Cache once, then request mismatched and over-limit cardinalities.

Medium: Puffin cache undercounts allocated memory
    Impact: Resident cache memory can substantially exceed its configured byte limit.
    Anchor: AggregateFunctionGroupBitmapData.h / `getAllocatedBytes`
    Trigger: Sparse high keys or many Roaring containers.
    Why defect: `getSizeInBytes` measures serialized size, not allocated memory.
    Fix direction: Track actual or conservatively estimated allocation.
    Regression test direction: Verify eviction for sparse bitmaps under a small limit.

Medium: Parquet row-group offsets can overflow
    Impact: Corrupt metadata can produce wrong counts, row identities, or deletion filtering.
    Anchor: ParquetV3BlockInputFormat.cpp / `prepareNeedOnlyCountRowGroups`
    Trigger: Cumulative nonnegative row-group counts exceed `size_t`.
    Fix direction: Use checked addition and validate against file-level row count.
    Regression test direction: Crafted overflowing footer.

Low: Concurrent cache clear misreports a load as a hit
    Impact: `PuffinFilesCacheHits` and misses become inaccurate.
    Anchor: PuffinFilesCache.h / `getOrSetDeletionVector`
    Trigger: Clear while a same-key load is in flight.
    Fix direction: Distinguish discarded in-flight loads from hits.
    Regression test direction: Block load, clear, release, and check events.

Low: Deprecated spaced `DROP` alias is missing
    Impact: `SYSTEM DROP PUFFIN FILES CACHE` fails although peer caches accept this spelling.
    Anchor: ParserSystemQuery.cpp / `system_aliases`
    Fix direction: Add the spaced alias.
    Regression test direction: Parse spaced and underscore variants.

Low: Unit test uses a developer-specific absolute path
    Impact: The test can fail outside `/home/iantonspb` and collide across runs.
    Anchor: gtest_parquet_v3_need_only_count_buckets.cpp / `CountsOnlyAssignedRowGroups`
    Fix direction: Use the test temporary-directory utility and unique cleanup.
    Regression test direction: Run under an unprivileged CI user.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b30a8f3e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"Puffin deletion vector entry in manifest file '{}' is missing content_offset or content_size_in_bytes",
manifest_file_path);
}
if (!lower_reference_data_file_path.has_value())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require the direct reference field for Puffin vectors

When a Puffin manifest entry omits or nulls data_file.referenced_data_file but supplies a lower bound for the position-delete path field, the fallback at lines 300-311 populates lower_reference_data_file_path, so this check accepts the malformed deletion vector and may apply its positions to the lower-bound data file. Check whether the direct field was present and non-null for Puffin entries rather than accepting the bounds fallback.

AGENTS.md reference: AGENTS.md:L153-L153

Useful? React with 👍 / 👎.

if (etag.empty())
return std::nullopt;

return PuffinFilesCacheKey{file_path, etag, content_offset, content_size_in_bytes, referenced_data_file};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Namespace Puffin cache keys by object storage

When two object-storage configurations use the same resolved Puffin path, referenced data-file path, offsets, and opaque ETag, this process-global cache key aliases their entries because it contains no endpoint, bucket, or storage namespace. ETags are not globally unique across independent backends, so after one storage populates the cache, a query against the other can receive the wrong deletion bitmap and return an incorrect row set; include a stable object-storage identity in the key.

Useful? React with 👍 / 👎.

@svb-alt
svb-alt requested a review from mkmkme July 31, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants