A layer that organizes files in a data lake into a structured table, enabling SQL-like features like ACID and time-travel.
Table formats are the most important piece of lakehouse infrastructure that most engineers still misunderstand. The confusion starts with the name — a "table format" is NOT a file format. Parquet is a file format (how bytes are laid out in one file); Iceberg / Delta Lake / Hudi are table formats (how many files are logically organized into one table, with transactions, versioning, and schema). A table in Iceberg is a pointer to a metadata tree that points to manifest files that point to data files (Parquet). Mutating the table means writing a new metadata tree and atomically swapping the pointer. That swap is what gives you ACID on top of eventually-consistent object storage — without which the lakehouse simply doesn't work.
Before table formats (pre-2018), doing MERGE on a data lake meant stopping all readers, rewriting the whole partition, hoping nothing failed mid-write, and restarting readers. Concurrent writes corrupted data. Schema changes required a full table rewrite. With Iceberg/Delta/Hudi: append a new snapshot, swap the metadata pointer, readers see old or new but never inconsistent data. Every modern lakehouse capability — time travel, hidden partitioning, incremental reads, compaction in place, schema evolution without rewrite — is a table-format feature, not a query-engine feature. Pick the format first; the engine is secondary.
A fintech client had 80TB of Parquet in S3 partitioned by day, with 6 Spark jobs writing nightly and Athena dashboards reading continuously. Every Friday (high-volume deploy day) they'd get corrupt dashboards because two jobs wrote overlapping partitions simultaneously, and readers saw partial results. They also couldn't fix schema bugs retroactively — renaming a column meant rewriting the entire history. We migrated to Iceberg on the same S3 bucket with AWS Glue as the catalog. No more corruption (Iceberg's snapshot isolation handles concurrent writes), schema evolution became a metadata-only operation (ALTER TABLE ... RENAME COLUMN is instant, no file rewrite), and they gained time travel (SELECT * FROM table FOR VERSION AS OF 123) for debugging pipeline bugs against yesterday's data. Six engineers stopped firefighting weekend data issues. The migration itself was ~6 weeks — not trivial, but pays back every week thereafter.
A Table Format is a metadata layer that sits on top of data files (like Parquet) in a data lake, organizing them into logical tables with warehouse-like capabilities. Without a table format, a 'table' in a data lake is just a folder of files with no transactional guarantees. Table formats solve this by tracking which files belong to a table, enabling ACID transactions, time travel, and schema evolution.
This distinction is critical and often confused:
| Concept | Example | What It Does |
|---|---|---|
| File Format | Parquet, ORC, Avro | How individual data files are encoded (columnar vs row, compression) |
| Table Format | Iceberg, Delta Lake, Hudi | How files are organized into tables (metadata, transactions, versioning) |
Query Engine (Spark, Trino, Flink)
↓
Table Format Metadata Layer
- Snapshot: list of all data files in current version
- Manifest: statistics about each data file
- Schema: column names, types, evolution history
↓
Data Files (Parquet/ORC on S3/GCS/ADLS)Every write creates a new snapshot pointing to a new set of files. Old snapshots remain accessible for time travel.
| Feature | Apache Iceberg | Delta Lake | Apache Hudi |
|---|---|---|---|
| Created by | Netflix | Databricks | Uber |
| Engine Support | Spark, Trino, Flink, Snowflake, BigQuery | Spark (best), Trino, Flink | Spark, Trino, Flink |
| Partition Evolution | Yes (hidden) | No (requires rewrite) | Partial |
| Catalog Options | REST, Glue, Hive, Nessie | Unity Catalog, Hive | Hive |
| Best For | Multi-engine, open ecosystem | Databricks shops | CDC + incremental |
| Schema Evolution | Excellent | Good | Good |
| Community Momentum | Highest (2024-2026) | Strong (Databricks) | Moderate |
Table format pitfalls that break production lakehouses:
expire_snapshots + remove_orphan_files (Iceberg) or VACUUM (Delta), metadata grows unboundedly, slowing planning and inflating storage cost.rewrite_data_files, OPTIMIZE), query performance degrades 10-50x over weeks.month(ts) without adding a column, but queries still need to filter on ts — engines do the partition translation. Adding partitioning to an existing table doesn't retroactively partition existing files.A table format is a metadata layer that turns a folder of data files (Parquet/ORC on S3/GCS/ADLS) into a logical, transactional table. It tracks: which files belong to the current version of the table (via a snapshot manifest), the table's schema and its evolution history, partition layout, per-file statistics (min/max/null-count) for query planning, and a transaction log of all commits. Every write produces a new snapshot pointing at a new file list, committed atomically via a catalog. Old snapshots stay queryable for time travel until expired. The three major table formats are Apache Iceberg, Delta Lake, and Apache Hudi. They all solve the same core problem (ACID on object storage) but differ in ecosystem, governance model, and optimization strategy. A table format is what makes a "lakehouse" distinct from a "data lake."
Parquet is a **file format**: it specifies how bytes within a single file are encoded — columnar layout, compression (Snappy/ZSTD), row groups, column statistics. A `.parquet` file is self-describing and can be read standalone by Spark, Pandas, DuckDB, etc. Iceberg is a **table format**: it specifies how multiple Parquet files are organized into a logical table, with a transaction log, snapshots, schema evolution history, and per-file pruning statistics. Iceberg doesn't replace Parquet; Iceberg tables store their data AS Parquet files (or ORC/Avro), plus metadata files that describe the table structure. Analogy: Parquet is how one page of a book is typeset; Iceberg is the book's index, table of contents, and edition history.
Choose Iceberg (2026 default for most greenfield lakehouses) if: you need multi-engine support (Spark + Trino + Snowflake + Flink + DuckDB), you want engine-neutral governance, you value open/vendor-agnostic format, you're using Snowflake/AWS/Dremio alongside Spark. Choose Delta Lake if: you use Databricks as primary compute, you want the most mature Spark Structured Streaming integration, you benefit from Databricks-specific features (deletion vectors, liquid clustering, predictive optimization), single-engine (Databricks) or single-Spark shops. Choose Hudi if: your primary workload is incremental CDC upserts at high rate (every few minutes), you need record-level deduplication and compaction by default, you're building streaming-first pipelines. In 2026: Iceberg has the strongest community momentum and ecosystem; Delta is locked to Databricks-flavored tooling; Hudi is niche but strong for its use case.
If your data lake has any of: (1) concurrent writes from multiple jobs, (2) updates or deletes (not just appends), (3) schema changes over time, (4) more than one query engine accessing the same data, (5) compliance requirements (audit trail, time travel, reproducibility), (6) hundreds of partitions or thousands of files per table — then yes, you need a table format. A raw-Parquet-on-S3 "lake" works for append-only single-writer pipelines but falls apart as soon as multiple jobs, schema changes, or ACID needs appear. In 2026, treating table formats as optional is an anti-pattern; Iceberg/Delta/Hudi are as fundamental to modern lakes as schemas are to relational databases. The cost is minimal (Iceberg adds ~2-5% metadata overhead); the benefit is avoiding corrupt-data incidents that take weeks to untangle.
The trick is atomic metadata pointer swaps. Object stores (S3, GCS, ADLS) don't support multi-object transactions, but they DO support atomic single-object writes (or conditional puts). A table format keeps the table's "current state" as a single metadata file (Iceberg: the `metadata.json` pointer, Delta: the `_delta_log` latest version, Hudi: the timeline). A commit writes new data files, then writes a new metadata file, then atomically updates the table pointer (via catalog atomic compare-and-swap, or S3 conditional write). Readers always resolve the pointer first and see a consistent snapshot. Concurrent writers check the pointer before committing — if it's changed, they retry (optimistic concurrency). The result: full ACID on top of eventually-consistent object storage, with no distributed-lock service.
Time travel lets you query a table as it existed at a past snapshot. Iceberg: `SELECT * FROM t FOR VERSION AS OF 123` or `FOR TIMESTAMP AS OF '2026-01-15'`. Delta: `SELECT * FROM t VERSION AS OF 5` or `TIMESTAMP AS OF '2026-01-15'`. Hudi: `SELECT * FROM t TIMESTAMP AS OF '20260115120000'`. Use cases: (1) debugging — reproduce yesterday's dashboard numbers to figure out what changed; (2) point-in-time ML training — train models on historical feature snapshots without leakage; (3) auditability — show regulators what data looked like at transaction time; (4) rollback — restore a table to before a bad write. Caveats: time travel only works for snapshots not yet expired (default Iceberg: 5 days); it's read-only (you can't mutate past snapshots); heavy use can prevent garbage collection of old files, bloating storage. Configure `expire_snapshots` to match your retention needs.
All three formats support adding nullable columns and widening types (int → bigint) via metadata-only operations — no file rewrite. Where they differ: Iceberg assigns stable column IDs at creation and supports rename/drop/reorder/type-promotion through metadata alone — column IDs guarantee correct mapping across schema versions. Delta Lake, by default, tracks columns by position/name; renaming requires enabling "column mapping" mode at table creation (not retroactively), otherwise rename is treated as drop + add. Hudi supports add/drop but is stricter about type changes. Best practices: (1) for Delta, enable column mapping from day one if you expect schema changes; (2) for Iceberg, use IDs everywhere and treat schema as code (tests, CI); (3) never rely on column position — always by name or ID; (4) test schema migrations on a table copy before production.