"Apache Iceberg vs Parquet" sounds like a choice between two file formats. It isn't. An Iceberg table stores its data in Parquet files. Iceberg is a table format: a set of metadata files that say which Parquet files make up the table right now, what the schema is, and what the table looked like at every past commit.
So the useful question is: do your Parquet files need a table format on top? For many small teams the honest answer is no. A note on where this comes from: I run plain Parquet with DuckDB in production. I have not run Iceberg in production. To get numbers instead of opinions, I built Iceberg tables on my laptop with PyIceberg 0.12.0, PyArrow 25.0.1 and DuckDB 1.5.6, on the local filesystem with a SQLite catalog. Every figure below comes from those runs.
Apache Iceberg vs Parquet: a file and a table
A Parquet file is self-contained. It knows its own columns, types and row groups, and nothing about any other file. A "table" made of plain Parquet is a folder and a naming convention. The engine lists the folder and reads what it finds.
An Iceberg table adds three layers above the same kind of files. After 50 small appends, my test table looked like this on disk:
warehouse/shop/events/ data/ 00000-0-061edf85-....parquet 50 Parquet files, 450 KB metadata/ 00050-c499c071-....metadata.json the current table state (50 KB) snap-616794116452149308-....avro manifest list: one per snapshot ....avro manifests: lists of data files + stats Reading goes top down. The catalog points at the current metadata.json. That file holds the schema, the partition spec and the list of snapshots. Each snapshot points at a manifest list, which points at manifests, which list the data files with per-column statistics. A commit writes new files and then swaps the catalog pointer in one step. That swap is what makes a commit atomic.
The format is versioned. The Iceberg spec has versions 1 to 3 adopted and version 4 in development. Version 3 adds a variant type, nanosecond timestamps, geospatial types, default values, deletion vectors and row lineage. The Java reference implementation reached 1.12.0 on 30 September 2026.
What plain Parquet already gives you
My book's position is that plain Parquet plus DuckDB is enough for a lot of real work, and I still hold it. Each file carries its own schema, so DuckDB can read a folder whose files gained a column over time:
SELECT * FROM read_parquet('data/*.parquet', union_by_name = true); -- files written before the new column return NULL for it That handles the most common change, adding a column. The book is just as plain about where it stops. Renames, type changes and dropped columns still break consumers. There is no versioning, no time travel and no atomic commit across many files. Its rule: for single-writer, append-only work, plain Parquet. For multi-writer warehouses, reach for a table format.
This is the first part. The full post — including the rest of the working details — is on my site: Iceberg vs Parquet: do you need a table format?
