This lesson on Apache Iceberg: Spec, Partition Evolution & Catalogs is hands-on and example-driven. You will be able to explain how Iceberg partitioning enables file pruning, declare hidden partition specs with transforms like months and bucket, and evolve a table's partition layout without rewriting old data. You will also know how to choose partition transforms and target file sizes for real query patterns.
What You'll Be Able To Do
- Explain how Iceberg pruning uses manifest metadata to skip irrelevant data files.
- Declare hidden partitioning with PARTITIONED BY and built-in transforms such as months, days, bucket, and truncate.
- Evolve a partition spec using ALTER TABLE DROP and ADD PARTITION FIELD without rewriting existing files.
- Choose time-based, bucket, or truncate transforms based on query predicates and column cardinality.
- Target partition sizes between 100 MB and 1 GB to avoid small-file overhead and ineffective pruning.
- Query Iceberg tables using raw columns while the engine applies partition transforms automatically.
Detailed Concept Walkthrough
1. Partitioning And Pruning
Partitioning organizes a table into files that can be skipped when a query does not need them. Pruning is the read-path optimization that uses metadata to avoid scanning irrelevant files.
- Mechanism: Partitioning maps rows to data files based on column values or transforms. When a query has a predicate, the engine compares it to partition metadata and scans only matching files.
- Under the Hood: Iceberg stores partition values in manifest files, not in directory names. The engine reads manifests first, so it can eliminate files before opening Parquet data.
- Execution Flow: A query for last Tuesday's sales first evaluates the date predicate, applies the partition transform, matches manifest entries, and then reads only the data files whose partition values overlap the predicate.
- Best Practice: Partition on columns that appear in common WHERE clauses. Time-based partitioning is usually the safest starting point for analytic workloads.
-- Without partitioning, the engine scans every file to find last Tuesday
SELECT * FROM sales WHERE sale_date = DATE '2025-03-11';
-- With Iceberg partitioning, metadata enables file pruning
CREATE TABLE sales (
sale_date DATE,
region STRING,
amount DOUBLE
)
USING iceberg
PARTITIONED BY (months(sale_date), region);
Key Takeaway: Partitioning is a performance contract: organize data so the engine can skip files.
2. Hidden Partitioning
Iceberg keeps partition information in the metadata layer, so data files can live anywhere and users query raw columns. This removes the rigid physical directory structure of older table formats.
- Mechanism: The table's partition spec declares transforms such as months(sale_date) and region. Iceberg computes the transformed partition values and records them in manifest files.
- Under the Hood: There are no required month=03 directories. Data files are tracked by metadata, and the engine uses manifest entries to decide which files to read.
- Syntax Rule: Declare partitioning with PARTITIONED BY (months(sale_date), region) in the CREATE TABLE statement. Query with normal predicates on sale_date and region.
- Best Practice: Do not add derived columns like sale_month. Iceberg applies the transform automatically, so extra columns are unnecessary and can drift from the spec.
CREATE TABLE prod.db.sales (
sale_date DATE,
region STRING,
amount DOUBLE
)
USING iceberg
PARTITIONED BY (months(sale_date), region);
-- Query uses raw columns; Iceberg applies the transform for pruning
SELECT * FROM prod.db.sales
WHERE sale_date = DATE '2025-03-15' AND region = 'US';
Key Takeaway: Hidden partitioning decouples physical layout from query syntax.
3. Partition Transforms
Partition transforms convert raw column values into partition values. Iceberg provides built-in time transforms, bucket, and truncate to match different data shapes.
- Mechanism: months(sale_date) produces a month number such as 627 for March 2025. days(sale_date) produces a day-level value, and years or hours provide coarser or finer time granularity.
- Under the Hood: The manifest stores the transformed value, not the raw date. At read time, Iceberg transforms the query predicate the same way and compares it to manifest entries.
- Syntax Rule: Use years(col), months(col), days(col), hours(col) for time. Use bucket(N, col) for hashed even distribution. Use truncate(N, col) to group similar string prefixes.
- Best Practice: Bucket high-cardinality join keys like customer_id so related rows land in the same files. Truncate high-cardinality product codes to keep partition counts manageable.
-- Time transforms for timestamp columns
PARTITIONED BY (years(event_time), months(event_time), days(event_time), hours(event_time))
-- Bucket high-cardinality customer_id for even distribution
PARTITIONED BY (bucket(16, customer_id))
-- Truncate product_code to first 7 characters to group similar codes
PARTITIONED BY (truncate(7, product_code))
Key Takeaway: Choose the transform that matches your predicate pattern and cardinality.
4. Partition Evolution
Partition evolution lets you change a table's partition spec without rewriting existing data. New data uses the new spec, while old files keep their original partitioning.
- Mechanism: ALTER TABLE DROP PARTITION FIELD and ADD PARTITION FIELD update the table metadata. The change applies to future writes, not to existing data files.
- Under the Hood: The query engine reads manifests from all partition specs and stitches results together. Pruning efficiency depends on the spec used when each file was written.
- Execution Flow: If April is monthly and May is daily, a query for April 14 to May 9 scans the whole April monthly partition but only the first nine daily May partitions.
- Best Practice: Evolve partitioning when current partitions become unwieldy. Rewrite old data only if the old layout causes unacceptable query performance.
-- Start with monthly partitions
ALTER TABLE prod.db.sales DROP PARTITION FIELD months(sale_date);
ALTER TABLE prod.db.sales DROP PARTITION FIELD region;
ALTER TABLE prod.db.sales ADD PARTITION FIELD days(sale_date);
ALTER TABLE prod.db.sales ADD PARTITION FIELD region;
-- New data uses days(sale_date); old monthly files remain unchanged
Key Takeaway: Partition evolution changes metadata for new writes, not existing files.
5. Choosing Partitioning And File Size
Partitioning is a performance optimization, so it must follow query patterns. Target partitions between 100 MB and 1 GB to balance pruning and overhead.
- Mechanism: Time-based partitioning is the safest bet when data has timestamps because most analytic queries filter by time. Bucket and truncate rescue high-cardinality columns.
- Under the Hood: Too many partition columns create many small partitions and small files. Small files increase metadata overhead, open costs, and I/O inefficiency.
- Best Practice: Aim for 100 MB to 1 GB per partition. Start with time plus one low-cardinality column, then tune based on real query SLAs.
- Nuance: Partitioning on multiple columns can be powerful but is a sharp knife. Monitor file sizes and avoid over-partitioning.
-- Good starting point: monthly time plus region
PARTITIONED BY (months(sale_date), region)
-- Avoid over-partitioning: many columns can create tiny files
-- PARTITIONED BY (days(sale_date), region, bucket(64, customer_id), truncate(7, product_code))
Key Takeaway: Partition for your queries, not for every column.
Topics Covered in Apache Iceberg: Spec, Partition Evolution & Catalogs
- Partitioning And Pruning (0:00 - 1:00) — Partitioning lets the query engine skip files that do not match a predicate, which is called pruning.
- Old Rigid Directories (1:00 - 2:00) — Older table formats used physical directories for partitions, making scheme changes expensive and rigid.
- Hidden Partitioning (2:00 - 3:00) — Iceberg moves partition information into metadata and manifest files so data files can live anywhere.
- Partition Transforms (3:00 - 4:00) — Built-in transforms like months, days, bucket, and truncate create partition values without extra columns.
- Manifest Pruning Example (4:00 - 5:00) — A manifest entry stores transformed partition values, letting a March 2025 US query scan exactly one file.
- Partition Evolution (5:00 - 6:00) — You can drop and add partition fields to change the spec for new data without rewriting old files.
- Evolution Visual (6:00 - 7:00) — A query spanning April monthly and May daily partitions scans the whole April partition but prunes May days.
- Choosing Partitioning (7:00 - 8:00) — Choose transforms based on query patterns, avoid over-partitioning, and target 100 MB to 1 GB partitions.
Spark Lakehouse Cheat Sheet
-
PARTITIONED BY (months(sale_date), region)— Declares Iceberg hidden partition specCREATE TABLE sales (...) USING iceberg PARTITIONED BY (months(sale_date), region); -
months(sale_date)— Time transform for month-level pruningPARTITIONED BY (months(sale_date)) -
days(sale_date)— Time transform for daily partitionsALTER TABLE sales ADD PARTITION FIELD days(sale_date); -
bucket(N, customer_id)— Hash distributes high-cardinality values evenlyPARTITIONED BY (bucket(16, customer_id)) -
truncate(N, product_code)— Groups similar string prefixes into partitionsPARTITIONED BY (truncate(7, product_code)) -
ALTER TABLE ... DROP PARTITION FIELD— Removes a partition field from specALTER TABLE sales DROP PARTITION FIELD months(sale_date); -
ALTER TABLE ... ADD PARTITION FIELD— Adds new partition field for new dataALTER TABLE sales ADD PARTITION FIELD days(sale_date); -
Manifest files— Store partition values for pruning
Comparison Table
| Aspect | Hive-style partitioning | Iceberg hidden partitioning |
|---|---|---|
| Partition location | Physical directories | Metadata in manifest files |
| Changing scheme | Rewrite or copy data | Update partition spec |
| Query syntax | Filter partition columns | Filter raw columns |
| Evolution scope | Whole table rebuild | New data only |
| Pruning metadata | Directory listing | Manifest partition values |
Common Pitfalls
- Mistake: Partitioning by too many columns. Avoid: limit to high-value predicates and target 100 MB to 1 GB partitions.
- Mistake: Expecting partition evolution to rewrite old files. Avoid: rewrite old data only if its layout hurts query performance.
- Mistake: Referencing partition transforms in WHERE clauses. Avoid: filter raw columns and let Iceberg apply transforms automatically.
- Mistake: Using daily partitions for low-volume data. Avoid: choose coarser time transforms like months or years.
- Mistake: Assuming bucket or truncate changes query syntax. Avoid: still filter the raw column in your WHERE clause.
FAQs
- What is hidden partitioning? Iceberg stores partition values in metadata rather than physical directories, so you query raw columns and the engine prunes files automatically.
- Does partition evolution rewrite existing data? No. The new spec applies only to new writes; old files keep their original partitioning until you explicitly rewrite them.
- How does Iceberg prune files? It reads manifest files that store partition values, compares them to query predicates, and skips data files that cannot contain matching rows.
- Which partition transform should I start with? Use time-based transforms like months or days if your queries filter by time; use bucket or truncate for high-cardinality columns.
- What file size should partitions target? Aim for 100 MB to 1 GB per partition. Too small adds overhead; too large reduces pruning effectiveness.
- Can I query old and new partitions together? Yes. The engine reads manifests from all partition specs and stitches results into one coherent result set.