How it works
- Sync-time materialization (v2 only) — for standard Iceberg v2 equality and Parquet position deletes, the controller’s snapshot sync reads the applicable delete files using Iceberg’s scan-planning rules (partition spec, partition tuple, data sequence number; equality columns resolve by field ID even across renamed/dropped columns) and builds a per-file delete bitmap, written as a Puffin-format blob alongside the snapshot’s
dv-index.parquetand cached separately from native source Puffin files. Native v3 Puffin deletion vectors are already in this form and skip materialization. - Server-side loading — each server decodes the resulting deletion-vector index (materialized v2 bitmap or native v3 Puffin) for its segments and applies it when answering a query, so deleted/updated rows are excluded from results.
- Atomic snapshot readiness — before a new Iceberg snapshot becomes visible to queries, the controller polls every server until all of them confirm they’ve loaded the deletion vectors for that snapshot. This avoids a window where some servers see the new snapshot’s deletes and others don’t.
- Broker-side pinning and consistency — the broker pins every query to a single snapshot per table so all servers involved in a query agree on which snapshot (and therefore which deletes) to use. By default it pins the newest active snapshot automatically and injects the
snapshotVersionByTableoption for the servers; a query can also pin an explicit snapshot (see Query-time snapshot pinning). An invalid explicitly-pinned snapshot fails the query rather than silently mixing snapshot state.
Enabling deletion vectors
Set on the table’sExternalTableSyncTask config:
Changes to
enableDeletionVectors and deletionVectorSource (and to the table’s catalog or storage credentials) take effect on the servers without a restart — the deletion-vector readers are rebuilt when the table config changes.
Upgrade every controller and server first.
enableDeletionVectors=true is only safe once every controller and server in the cluster runs a build that supports derived row-delete blobs. Creating or enabling it earlier can publish artifacts older servers can’t read. An existing v3 Puffin table can keep running through a rolling upgrade; don’t introduce v2 equality or position delete files to it, and don’t flip the flag on an existing table, until the upgrade completes. After enabling the flag on an existing table, sync a fresh Iceberg snapshot before relying on row-delete results — previously published snapshot artifacts are immutable and aren’t reprocessed under the same snapshot ID.Incompatible with the Star-tree index. Table-config validation rejects a Star-tree index (explicit
starTreeIndexConfigs or enableDefaultStarTree) on a table with enableDeletionVectors=true, and the reverse — enabling deletion vectors on a table that already has a Star-tree index configured. Deletion vectors filter rows per query, and Star-tree’s precomputed aggregates can’t be reconciled against that. Remove the Star-tree index configuration or disable deletion vectors. See Supported Indexes.How Puffin delete files are read is no longer a table setting. The per-table
iceberg.dv.fetch.mode, iceberg.dv.puffinCache.maxBytes, and iceberg.dv.puffinCache.downloadTimeoutMs keys are retired (0.16.x). Puffin files are now cached by the server’s persistent disk cache, which StarTree manages; if an old table config still carries these keys, they are ignored.iceberg.snapshotProcessing.retention.maxActiveVersions (default 2).
Controller: snapshot readiness
Each server exposes a readiness endpoint, which the controller polls:
{"ready": true|false} plus additive load-progress fields the controller’s poller uses for stall detection — loaded, total, inFlight, consecutiveLoadFailures, and lastLoadError (present only when set). Older pollers ignore the extra fields; a server that omits them (older build) is treated as deadline-only, never assumed to be stalled. The check is non-blocking: it answers from the server’s in-memory state and, if the deletion vectors aren’t loaded yet, kicks off a background load and returns ready: false — so the first poll for a new snapshot normally reports not-ready. Returns 412 when the table is not configured for deletion vectors (enableDeletionVectors=false) or the deletion-vector index for the requested snapshot is missing or unreadable.
Controller: IS/EV convergence
Before polling for snapshot readiness, the controller waits for every affected segment’s Helix ideal-state and external-view to converge:
Broker-side snapshot pruning activates for OFFLINE External Tables when any of these is true on the table:
enableDeletionVectors=true, enableSnapshotConsistency=true (a flag the onboarding/sync flow sets automatically for snapshotting catalogs — pins queries to the last completed snapshot so they never see a half-ingested one), or segment groups enabled. It is a zero-cost no-op otherwise.
Query-time snapshot pinning
You do not need to pin a snapshot yourself: for a pruning-enabled table the broker automatically resolves each query to the newest active snapshot and injects the server-requiredsnapshotVersionByTable option. Set the option explicitly only to pin a query to a specific snapshot (time travel / reproducing an earlier state):
QUERY_EXECUTION error rather than returning results computed from a mix of snapshot states. A query also fails when the table has no active snapshots at all yet (nothing fully synced), or when the broker hasn’t finished loading the pinned snapshot’s segment state locally (the broker retries in the background).
Only snapshots in the active set can be pinned, and the set’s size is bounded by snapshot retention (iceberg.snapshotProcessing.retention.maxActiveVersions, default 2) — so time travel reaches back at most that many snapshots.
Storage format
The deletion-vector index itself is stored as a dictionary-encoded Parquet file (dv-index.parquet), with rows sorted by segment name within each row group and per-row-group bloom filters, which keeps heap usage low even for large delete sets and supports lazy point lookups. On the servers, cached Puffin files live in the persistent disk cache; the cache survives restarts and is cleaned up when the table is deleted.
Observability
The run-status endpoint’s
failurePhase field (see Observability) can also report WAITING_FOR_SEGMENTS_ONLINE or WAITING_FOR_SERVERS_READY when a sync run fails during deletion-vector convergence.
FAQs
Do I need to set snapshotVersionByTable on every query once deletion vectors are enabled?
No — the broker pins each query to the newest active snapshot automatically and injects the option for the servers. Set it explicitly only for time travel to a specific snapshot; an explicit pin that isn’t in the active set fails the query rather than returning potentially inconsistent results.
Does this work for raw S3 Parquet (non-Iceberg) External Tables?
No — this is an Iceberg-specific capability (deletionVectorSource=iceberg), covering both native v3 Puffin deletion vectors and standard v2 equality/position delete files; raw Parquet listings have no Iceberg delete-file concept to apply.
