Apache Iceberg v3 Explained: 7 Spec Features That Change Your Lakehouse Upgrade Plan (2026)
Upgrading a table format sounds like a one-line change. For Apache Iceberg v3 it is literally one line — ALTER TABLE t SET TBLPROPERTIES ('format-version'='3') — and that is exactly the problem. The statement is atomic, instant, rewrites nothing, and cannot be undone. Iceberg rejects an attempt to set format-version back to 2. The moment the new metadata file lands, every engine in your estate that only understands v2 stops being able to read that table, and you find out which ones those are by watching jobs fail.
That asymmetry — trivial to do, impossible to reverse, estate-wide blast radius — is why v3 deserves a planning document rather than a changelog skim. The seven capabilities it adds are genuinely good, and two of them change the physics of how a lakehouse handles updates. But the upgrade is a compatibility decision before it is a performance decision.
What this covers: what each of the seven v3 features changes at the metadata layer, the deep mechanics of deletion vectors and row lineage, engine support as of 18 September 2026, the one-way-door risks, and a sequenced rollout you can actually defend in a design review.
Context and Background
Iceberg’s format version is a contract, not a release number. Version 1 (2020) defined an immutable-file table: metadata JSON at the top, a snapshot pointing at a manifest list, manifest lists pointing at manifests, manifests listing data files with their partition values and column statistics. Deletes meant rewriting whole files. That was fine for append-mostly analytics and miserable for anything resembling a mutable dataset.
Version 2 (2021) introduced row-level deletes through delete files — positional deletes, which name a data file path and the row ordinals inside it to suppress, and equality deletes, which name predicate values. This made merge-on-read possible and unlocked CDC ingestion, GDPR erasure, and streaming upserts on object storage. It also introduced the operational tax that every Iceberg team since has paid: delete files accumulate, scan planning gets heavier, and compaction becomes a permanent background job you must budget for. Our production deep dive on Apache Iceberg covers that maintenance burden in detail.
Version 3 is the first format revision designed with four years of production scar tissue behind it. It took roughly that long to land, and the community deliberately bundled a wide feature set rather than shipping incrementally, because every format bump costs the ecosystem an engine-support cycle. The Apache Iceberg project declared the v3 feature set production-ready with the 1.11.0 release on 19 May 2026, which also dropped Java 11 and deprecated the Spark 3.4 connector.
The strategic backdrop matters too. The Delta Lake and Iceberg lineages have been converging since Databricks acquired Tabular, and several v3 features — deletion vectors, row tracking, the VARIANT type — are recognisably Delta ideas arriving in the Iceberg spec, with the VARIANT binary encoding itself defined in the Parquet project rather than in Iceberg. If you are still deciding between table formats rather than versions, our comparison of Iceberg and Paimon for lakehouse workloads is the better starting point; this post assumes Iceberg is already chosen.
What Apache Iceberg v3 Changes at the Metadata Layer
Apache Iceberg v3 is format version 3 of the Iceberg table specification. It adds seven capabilities: deletion vectors, row lineage, the VARIANT type, default column values, geometry and geography types, nanosecond timestamps, and multi-argument partition transforms. None of them change the data files you already have; all of them change what new metadata and new writes look like.

Figure 1: Where each Apache Iceberg v3 feature lands in the metadata tree.
The figure traces the path from the catalog pointer down to physical files. The table metadata JSON carries format-version: 3 and the schema, which is where VARIANT, GEOMETRY, GEOGRAPHY, nanosecond timestamp types and column defaults live. The partition spec, also in that JSON, is where multi-argument transforms appear. The snapshot gains a next-row-id counter. Manifest entries gain first-row-id, which is the anchor for row lineage inheritance. And a new class of sidecar appears next to the data files: Puffin files holding deletion vectors.
Read that list carefully and a pattern emerges. Five of the seven features are schema-level and additive — you can ignore them indefinitely and nothing about your table changes. Two of them, deletion vectors and row lineage, are structural: they alter how writes are recorded and how readers must reconstruct the live row set. Row lineage in particular is mandatory for v3 writers. That asymmetry should drive how you think about the upgrade.
The five additive features are cheap to adopt and easy to defer
The VARIANT type stores semi-structured data — JSON-shaped values whose structure varies row to row — in a binary encoding with typed access, rather than as an opaque string you re-parse on every query. Combined with shredding, which extracts frequently accessed sub-fields into their own physical columns, it gives you predicate pushdown and column pruning on payloads that previously defeated both. For anyone landing raw event JSON or device telemetry into a lakehouse, this is the single biggest ergonomics win in v3. It is also entirely optional: you adopt it by declaring a column of that type.
Geometry and geography types are native spatial types with real semantics. GEOMETRY covers planar data in a projected coordinate reference system; GEOGRAPHY covers spherical data on WGS84. Both carry bounding boxes in file-level statistics, which means spatial predicates can prune files during scan planning instead of scanning everything and filtering late. Previously you either stored WKB in a binary column and pushed nothing down, or you bolted on a separate spatial store.
Nanosecond timestamps add timestamp_ns and timestamptz_ns alongside the existing microsecond types. This is not a rounding-error concern. Financial tick data, high-rate industrial telemetry, and distributed tracing spans all generate events that collide at microsecond granularity, and a collision means you lose ordering. All the standard partition transforms — identity, bucket, year, month, day, hour — accept the nanosecond types exactly as they accept the microsecond ones.
Default column values let the schema carry both an initial default, applied to rows written before the column existed, and a write default, applied when a writer omits the column. Before v3, adding a non-null column to a large table meant either a full rewrite or client-side backfill logic in every writer. Now it is a metadata change. Note that support here is uneven across engines — several implementations shipped write defaults before initial defaults.
Multi-argument partition and sort transforms let a transform take more than one source column. The practical case is bucketing on a composite key without first materialising a concatenated column, which matters for join-locality strategies where you want two large tables bucketed identically on a two-part key. This is the least-supported of the seven features across engines as of September 2026, so treat it as forward-looking.
The two structural features are where the upgrade decision actually lives
Deletion vectors and row lineage are not opt-in in the same way. Once a table is v3, any engine writing to it must maintain row lineage fields — that is a spec requirement, not a preference. And deletion vectors become the delete representation for new delete operations, which means a v2-only reader looking at a v3 table with deletion vectors will not merely miss a feature; it will return wrong results if it somehow gets far enough to read the data files while ignoring the Puffin sidecars. In practice Iceberg readers check format-version and refuse the table outright, which is the safe failure. But it is worth understanding why the refusal is correct rather than conservative.
The remainder of this post treats those two in depth, then turns to engine support and rollout.
Deletion Vectors: The Real Performance Story
This is the feature people cite when they say v3 is worth it, and it is also the one most often explained badly. The headline — Databricks, Snowflake and AWS all quote “up to 10x faster DML” — is a vendor-reported figure, not an independently measured benchmark, and it compares deletion vectors against copy-on-write rewrites rather than against a well-tuned merge-on-read setup. The mechanical story underneath is more interesting and more useful for capacity planning.

Figure 2: The read path for v2 positional deletes versus v3 deletion vectors.
The diagram contrasts what a reader must do per data file. Under v2 it may have to open an unbounded number of positional delete files, merge their sorted position streams, and search that merged stream for every row position it reads. Under v3 it opens exactly one deletion vector, deserialises a roaring bitmap, and does a constant-time bit test per row. The difference is not a constant factor — it is a change in the shape of the cost curve.
What a positional delete file actually costs you
A v2 positional delete file is a data file in its own right, usually Parquet, containing two columns: file_path and pos. To delete ten rows scattered across three data files, a writer emits a delete file listing thirteen values across ten rows. That is cheap to write. The cost arrives later, and it compounds in three separate ways.
First, cardinality. Nothing in v2 limits how many delete files can apply to a single data file. A table receiving small, frequent MERGE operations — the classic CDC ingestion pattern with a five-minute micro-batch — accumulates a new delete file per data file per batch. After a day of five-minute batches that is up to 288 delete files layered over the same data file. Every reader must open, parse and merge all of them.
Second, planning overhead. Delete files appear in manifests. Scan planning must enumerate them, evaluate their applicability against the data files in scope, and hand the set to the executor. This happens on the driver, single-threaded in most engines, before any parallel work starts. Teams routinely see query latency dominated by planning on delete-heavy tables, which is invisible in executor metrics and therefore misdiagnosed as a cluster-size problem.
Third, small-file pressure on object storage. Each delete file is an S3 object with its own GET latency, request cost, and metadata footprint. A thousand tiny delete files is a thousand round trips the reader cannot avoid.
The standard mitigation is aggressive compaction — rewrite_data_files and rewrite_position_delete_files running on a schedule. That works, and it is also a permanent compute bill plus an operational surface that fails at three in the morning.
What a deletion vector does differently
A v3 deletion vector is a bitmap: bit position P set means the row at ordinal P in the associated data file is deleted. The bitmap is stored as a blob inside a Puffin file, the same sidecar container Iceberg already uses for statistics blobs, under a blob type added for this purpose. The encoding supports positive 64-bit positions but is optimised for the common case where positions fit in 32 bits, using a collection of 32-bit roaring bitmaps keyed by the high four bytes. Each serialised vector carries a length prefix, a four-byte magic sequence, the roaring bitmap in its portable serialisation, and a CRC-32 checksum.
Three properties follow from that design, and they are the properties that matter operationally.
One vector per data file per snapshot. This is the critical invariant. A data file has at most one deletion vector at any snapshot. A new delete operation does not append a layer — it reads the existing vector, sets additional bits, and writes a replacement. Delete metadata therefore stops accumulating. The 288-delete-files-per-day scenario becomes 288 successive replacements of a single vector, each one superseding the last. Storage for delete metadata converges instead of growing linearly.
Constant-time membership tests. A roaring bitmap answers “is position P set?” by indexing a container and testing a bit. There is no merge, no sorted scan, no binary search across streams. The per-row cost during read is effectively free relative to the decompression and decoding work the reader is already doing.
Dense packing. Many vectors live in one Puffin file. A single delete statement touching 500 data files writes 500 vectors back to back into one Puffin file, with the footer listing each vector’s offset and length. That collapses 500 object writes into one, and lets a reader fetch several vectors with a ranged read rather than 500 separate GETs. This is the part that most directly attacks small-file pressure.
Where the gain is real and where it is not
The gain is largest where delete files were accumulating fastest: high-frequency MERGE, CDC ingestion, streaming upserts, and per-record GDPR erasure. In those shapes you are removing a compounding cost, not shaving a constant one — and you also reduce the compaction work needed to keep the table readable, which is a second-order saving that rarely shows up in vendor numbers.
The gain is close to zero on append-only tables with no deletes, on tables where deletes are rare and large enough to trigger copy-on-write rewrites anyway, and on tables you already compact so aggressively that delete files never accumulate. If your rewrite_position_delete_files job runs every fifteen minutes and your tables are healthy, v3 buys you a cheaper maintenance bill rather than faster queries.
One genuine limitation deserves flagging. Deletion vectors replace positional deletes. Equality deletes — the v2 mechanism where a delete file names predicate values rather than row positions, heavily used by Flink CDC sinks because the writer often does not know row positions at write time — are a separate mechanism and are not subsumed by deletion vectors. If your ingestion path depends on equality deletes, verify your engine’s v3 behaviour explicitly rather than assuming the deletion-vector story covers you.
Upgrade behaviour for existing delete files
Upgrading a v2 table does not rewrite anything. Existing positional delete files remain valid and continue to be applied on read. New delete operations produce deletion vectors. Some engines — Amazon Redshift documents this behaviour explicitly — will merge pre-existing v2 positional deletes into the deletion vector for a data file the next time a DELETE, UPDATE or MERGE touches that file. Others simply leave the old delete files in place until compaction clears them.
The practical consequence is a mixed-representation window after upgrade, during which a reader must handle both v2 positional deletes and v3 deletion vectors on the same table, potentially on the same data file. That is legal and correct, but it means the performance improvement arrives gradually as writes touch files, not at the moment of the ALTER TABLE. Budget for a compaction pass if you want the benefit sooner, and do not read your first post-upgrade benchmark as the steady state.
Row Lineage: Identity That Survives a Rewrite
Row lineage is the second-deepest change and the one with the longest tail of consequences. It gives every row in a v3 table two system fields: _row_id, a long identifier unique within the table, and _last_updated_sequence_number, the sequence number of the commit that last modified that row.

Figure 3: How a reader materialises _row_id from manifest metadata rather than from stored column values.
Why this could not be done before v3
The problem row lineage solves is that Iceberg rows had no stable identity. A row’s position in a data file is not identity — compaction rewrites files and positions change. A primary key is not identity either, because Iceberg does not enforce one and many lakehouse tables genuinely lack a natural key. So answering “has this specific row changed since I last read the table?” required either a full anti-join against a prior snapshot, or an external CDC system maintaining its own keys, or a convention every writer had to honour and none reliably did.
That gap is why most Iceberg CDC pipelines in production today are expensive. Detecting a hundred changed rows in a billion-row table meant scanning a large fraction of both snapshots. Iceberg’s incremental read APIs could tell you which files changed, and for merge-on-read tables that is a blunt instrument: a single-row update rewrites or shadows an entire file.
The inheritance trick
The elegant part of the design is that _row_id and _last_updated_sequence_number are usually not physically stored. They cannot be, because the values depend on the commit sequence number and the starting row ID, and neither is assigned until the snapshot commits successfully — after the data files are already written.
So the spec defines inheritance. A writer emits data files with NULL in the lineage columns. At commit, the catalog assigns the snapshot’s data sequence number and allocates a block of row IDs from the table-level next-row-id counter, recording the block’s start as first-row-id on the manifest entry. A reader encountering NULL in _row_id computes it as the manifest entry’s first-row-id plus the row’s ordinal position in the file; a NULL _last_updated_sequence_number resolves to the file’s data sequence number.
When a row is updated and physically rewritten into a new file, the writer does materialise the original _row_id into the new file — preserving identity — while leaving _last_updated_sequence_number NULL so it inherits the new commit’s sequence number. Identity persists; the modification stamp advances. That is exactly the semantics CDC needs.
The failure mode this creates
Inheritance is efficient and it is also a trap for engines that implement v3 partially. If a reader treats the physical NULL as a real NULL rather than as an inheritance signal, then a filter such as WHERE _last_updated_sequence_number > 42 silently drops every row whose lineage value was inherited — which, for a freshly written table, is every row. This is not hypothetical: it has been reported as a live bug against at least one engine’s v3 implementation.
The reason this matters for your upgrade plan is that it is a silent wrong-answer bug, not a crash. A reader that refuses a v3 table is safe; a reader that half-implements lineage inheritance and returns an empty incremental batch will be trusted by your pipeline. When you validate an engine’s v3 support, do not stop at “it reads the table”. Write a row, update it, and check that a lineage-filtered query returns it.
What row lineage unlocks
With stable identity, incremental change feeds become a metadata-level operation. A downstream consumer records the sequence number it last processed and asks for rows whose _last_updated_sequence_number exceeds it. Because sequence numbers are recorded in manifest metadata, a large fraction of that filtering happens during scan planning by skipping whole files, before any data is read.
That collapses three patterns that previously needed external machinery. Native CDC without Debezium or a separate change-capture layer. Bidirectional interoperability, where two engines write to the same table and each can identify what the other changed — Snowflake has been explicit that this is the interoperability story v3 enables. And cheap incremental materialised views, where a downstream aggregate refreshes from the changed rows rather than recomputing.
The cost is a small per-row metadata overhead and a real constraint on writers: every engine writing to a v3 table must maintain lineage correctly. A writer that gets this wrong corrupts the table’s change history in a way that is hard to detect and harder to repair. This is the strongest argument for restricting write access to v3 tables to a small, verified set of engines during rollout, even when more engines can read them. If you are also choosing a catalog, note that lineage and next-row-id allocation are commit-path concerns — our comparison of Polaris, Nessie and Unity Catalog is the relevant companion read.
Engine Support as of 18 September 2026
Engine support is the gating constraint on this upgrade, and it moves monthly. Everything below is date-stamped to 18 September 2026 and should be re-verified against vendor documentation before you act on it — treat this as a snapshot, not a standing matrix.
| Engine / platform | v3 status (18 Sept 2026) | Notes |
|---|---|---|
| Apache Spark (Iceberg 1.11.0+) | Full read and write | Reference implementation; requires Java 17/21, Spark 3.5 or 4.0+ |
| Apache Flink (Iceberg 1.11.0+) | Read and write | Verify equality-delete behaviour for your CDC sink |
| Snowflake | GA since 7 May 2026 | Preview 4 Mar 2026; deletion vectors and row lineage both GA |
| Databricks | Public preview since 24 Apr 2026 | Runtime 18.0+; Unity Catalog managed Iceberg v3 supports row lineage, deletion vectors, VARIANT |
| AWS (EMR 7.12, Glue, S3 Tables, Glue Data Catalog, SageMaker notebooks) | Deletion vectors and row lineage supported | Announced Nov 2025; feature subset, not the full seven |
| Trino | Partial and advancing | Deletion-vector reads, row-level updates, OPTIMIZE and column defaults landed through 2026; docs corrected Sept 2026 |
| Dremio | v3 support added in Dremio Cloud | Roll-out ongoing |
| DuckDB | Partial, evolving | Iceberg extension has shipped v3-related fixes since 1.5.x |
| ClickHouse | Limited | v3 read support tracked in open issues; lineage-filter correctness bug reported |
| Amazon Athena | Verify before assuming | Lagged v3 through most of 2026 |
| Presto | Reader support in progress | Native v3 support tracked in open issues |
Two observations are more useful than the table itself.
First, support is per-feature, not per-version. “Supports v3” almost always means “supports deletion vectors and row lineage” — the two features with the clearest commercial payoff. VARIANT, geospatial types, nanosecond timestamps, initial defaults and multi-argument transforms each have their own support status on each engine, and official matrices from different vendors disagree with each other. A table using a v3 type an engine does not implement will fail at that engine even if the engine claims v3 support.
Second, read support and write support are different gates. Many engines shipped v3 reads first. That is the right order for adoption: it means a Spark-written v3 table can be consumed broadly before you let other engines write to it. Design your rollout around that asymmetry — one verified writer, many readers — rather than trying to reach uniform read-write support everywhere at once.
The One-Way Door and the Compatibility Trap
Three risks account for nearly every bad v3 upgrade story, and none of them are about performance.
The upgrade is irreversible. ALTER TABLE ... SET TBLPROPERTIES ('format-version'='3') writes a new metadata JSON declaring version 3. Iceberg rejects setting format-version back to 2. There is no downgrade path in the spec or the library. The only rollback is creating a new v2 table and copying data into it — for a large table that is a full rewrite plus a cutover, and you lose snapshot history. Treat the statement with the same ceremony you would give a destructive migration.
Older readers fail on the whole table, not gracefully on new features. Iceberg readers check the table’s format version and refuse anything above what they implement. This is correct behaviour and it is what you want, but the failure surface is wide: it is not the v3 features that break a v2 reader, it is the version number. A table that uses none of the seven features but carries format-version: 3 is equally unreadable to a v2-only engine. So the blast radius is every consumer of that table — including the BI tool, the ad-hoc notebook, the vendor integration nobody documented, and the data-science job that runs quarterly.
The estate inventory is almost always incomplete. The failure mode we see repeatedly is a team that verifies its three known consumers, upgrades, and discovers a fourth two weeks later. The catalog is your best instrument here: query access logs or catalog audit history for every principal that has touched the table in the last 90 days, and map each principal to an engine and version before you touch the format version. Do not rely on a wiki page or on asking around.
A fourth risk is subtler. Because row lineage is mandatory for v3 writers, an engine that can read v3 but writes it incorrectly is more dangerous than one that cannot write it at all. Restrict the writer set deliberately. A catalog-level or IAM-level write restriction on newly upgraded tables is cheap insurance for the first few weeks.
A Sequenced Rollout for Apache Iceberg v3
The rollout below assumes you have decided v3 is worth having. If your tables are append-only and your delete volume is negligible, the honest answer may be to wait — there is no penalty for staying on v2, and every month of waiting improves the engine-support picture.

Figure 4: A sequenced rollout for Apache Iceberg v3 that keeps the one-way door closed until the estate is verified.
Phase 0 — inventory and gate. Enumerate every reader and writer of every candidate table, with versions. Classify each as v3-capable, v3-capable-for-the-features-you-need, or not. If any consumer in the second or third bucket cannot be upgraded or removed, do not proceed on that table. Instead, fence: create v3 tables in a separate catalog namespace so that nothing depending on v2 can accidentally be pointed at them, and re-check engine support quarterly.
Phase 1 — pick a low-risk first table. The ideal candidate has a small, known consumer set, meaningful delete or update volume (so you can actually measure the benefit), and a tolerable recovery story if you need to rebuild it. A staging or intermediate table beats a curated mart. Do not start with the table everyone queries.
-- Verify current state before touching anything
SELECT * FROM catalog.db.events.metadata_log_entries
ORDER BY timestamp DESC LIMIT 5;
-- The one-way door
ALTER TABLE catalog.db.events
SET TBLPROPERTIES ('format-version' = '3');
Phase 2 — force the first write. Row lineage values are generated by writes, not by the metadata bump. Until a write touches a file, that file’s rows have no lineage anchor of their own. Run a write — an append, or a compaction pass — and then verify lineage is materialising as expected.
-- Confirm lineage is present and resolving
SELECT _row_id, _last_updated_sequence_number, count(*)
FROM catalog.db.events
GROUP BY 1, 2
ORDER BY 2 DESC
LIMIT 20;
Phase 3 — verify the delete representation switched. Run a DELETE or MERGE and then inspect metadata tables to confirm deletion vectors are being produced and that old positional delete files are being retired rather than accumulating alongside them.
-- Delete files by content type; deletion vectors appear as Puffin blobs
SELECT file_path, file_format, content, record_count
FROM catalog.db.events.all_delete_files
ORDER BY file_path;
Phase 4 — retune maintenance. Compaction settings tuned for v2 delete-file accumulation are probably now too aggressive. Because deletion vectors converge rather than accumulate, you can usually lengthen the rewrite_position_delete_files cadence substantially or drop it. Snapshot expiry becomes more important, not less, because superseded deletion vectors are retained as long as the snapshots referencing them are.
Phase 5 — promote the workloads that justified the upgrade. Move CDC consumers onto lineage-based incremental reads, and migrate JSON payload columns to VARIANT where the query pattern justifies it. Do this after the format is stable, not during the cutover — you want one variable at a time.
Trade-offs, Gotchas, and What Goes Wrong
The benchmark you run first is not the steady state. Immediately after upgrade the table is in mixed-representation mode: old positional delete files plus new deletion vectors. Measuring then tells you very little. Compact, let a few write cycles pass, then measure.
Deletion vectors do not shrink storage automatically. Each replacement writes a new vector; the old one survives as long as a snapshot references it. Teams who upgrade and then neglect expire_snapshots end up with more delete-metadata storage than before, and conclude v3 made things worse. It did not; snapshot retention did.
Equality deletes are a separate track. If your Flink CDC sink emits equality deletes, deletion vectors do not replace them. Verify the behaviour of your specific connector before assuming the v3 delete story applies end to end.
Partial implementations cause silent wrong answers. The lineage-inheritance NULL bug described earlier returns empty results rather than erroring. Any engine whose v3 support you have not personally exercised should be treated as read-only for v3 tables until you have tested an update-then-incremental-read cycle against it.
Vendor performance claims are directional, not predictive. “Up to 10x faster DML” is vendor-reported and compares against copy-on-write. Your baseline is probably merge-on-read with compaction, and your delta will be smaller. The durable win is usually the reduced compaction bill and the lower planning latency, not raw DML throughput.
Feature support is not monolithic. A table that uses VARIANT may be unreadable on an engine that reads deletion vectors fine. Constrain which v3 features each table uses to the intersection of what all its consumers support, and write that constraint down somewhere enforceable.
One-way door, again. Rehearse the upgrade on a clone before you do it for real. Most catalogs support table cloning or a CTAS copy cheaply enough that there is no excuse for skipping the dress rehearsal.
Practical Recommendations
Start from the workload, not the version number. If your tables are append-heavy with negligible deletes, Apache Iceberg v3 offers you convenience features you can adopt later at no cost — wait, and let engine support mature. If you run high-frequency MERGE or CDC ingestion and you are paying a visible compaction bill, deletion vectors alone justify the migration, and row lineage likely lets you retire a separate change-capture layer on top of that.
Whatever you decide, decide per table rather than per warehouse. There is no requirement to upgrade uniformly, and mixed v2/v3 estates are a normal steady state, not a transitional embarrassment.
Checklist before you run the ALTER TABLE:
- Enumerate every reader and writer from catalog audit logs covering at least 90 days, not from memory.
- Confirm v3 support per feature, not per version, for every consumer of the table.
- Restrict write access to a single verified engine for the first two weeks.
- Rehearse the upgrade on a clone and validate an update-then-incremental-read cycle on every reader.
- Plan a compaction pass immediately after upgrade to clear the mixed-representation window.
- Retune
expire_snapshotsbefore you retune anything else — superseded deletion vectors depend on it. - Fence v3 tables into a separate namespace if any consumer is still v2-only.
- Re-verify the engine-support picture on the day you act; it changes monthly.
Frequently Asked Questions
What is Apache Iceberg v3?
Apache Iceberg v3 is format version 3 of the Iceberg table specification, declared production-ready with Iceberg 1.11.0 on 19 May 2026. It adds seven capabilities: deletion vectors, row lineage, the VARIANT type for semi-structured data, default column values, geometry and geography types, nanosecond timestamps, and multi-argument partition transforms. Deletion vectors and row lineage are structural changes to how writes are recorded; the other five are additive schema-level features you can adopt whenever you choose.
Can you downgrade an Iceberg table from v3 back to v2?
No. Iceberg rejects setting format-version to 2 on a v3 table, and there is no downgrade path in the specification or the reference library. The only recovery is creating a fresh v2 table and copying the data into it, which costs a full rewrite and loses snapshot history. Because the upgrade itself is a single instant metadata change that rewrites no data, it is easy to run before the estate is ready — treat it as a destructive migration and rehearse it on a clone.
What is the difference between deletion vectors and positional delete files?
A v2 positional delete file is a Parquet file listing file paths and row positions, and an unbounded number of them can apply to one data file. A v3 deletion vector is a roaring bitmap stored in a Puffin sidecar, with exactly one vector per data file per snapshot; new deletes replace the vector rather than layering on top. The result is constant-time membership tests, delete metadata that converges instead of accumulating, and far fewer small objects to fetch.
Which engines support Iceberg v3 in 2026?
As of 18 September 2026, Spark and Flink have the fullest support through Iceberg 1.11.0, Snowflake has been GA since 7 May 2026, Databricks is in public preview on Runtime 18.0+, and AWS supports deletion vectors and row lineage on EMR 7.12, Glue, S3 Tables and the Glue Data Catalog. Trino, Dremio, DuckDB, ClickHouse and Presto are at varying stages of partial support. Support is per-feature rather than per-version, and it moves monthly — verify before acting.
Is the 10x faster DML claim for deletion vectors real?
It is vendor-reported, not independently measured, and it compares deletion vectors against copy-on-write file rewrites. If your current baseline is merge-on-read with regular compaction, expect a smaller delta. The more dependable wins are lower scan-planning latency on delete-heavy tables, fewer small objects on storage, and a materially reduced compaction bill. Measure your own workload after a compaction pass rather than trusting the headline number.
Do I need row lineage if I already run Debezium or another CDC tool?
Possibly not immediately, but row lineage changes the economics. Because _last_updated_sequence_number is recorded in manifest metadata, incremental consumers can skip whole files during scan planning instead of anti-joining snapshots. Teams running a separate change-capture layer purely to answer “what changed” can often retire it. The caveat is that every engine writing to the table must maintain lineage correctly, so restrict the writer set until you have verified each one.
Further Reading
- Apache Iceberg in production: architecture and operational deep dive — the compaction and maintenance burden this upgrade partly addresses.
- Iceberg catalogs compared: Polaris vs Nessie vs Unity — the commit path where row-ID allocation and lineage assignment happen.
- Apache Iceberg vs Paimon for lakehouse table formats — if the format choice itself is still open.
- Postgres 18 vs TimescaleDB vs ClickHouse for IoT workloads — where a lakehouse is the wrong answer entirely.
- The Apache Iceberg table specification — the primary source for format-version semantics and row lineage inheritance rules.
- The Puffin file format specification — the container and blob encoding behind deletion vectors.
By Riju — about
