
Iceberg Metadata Compaction: Solving Latency Breaches
Iceberg metadata bloat causes query timeouts across lakehouses. Learn manifest rewriting and snapshot purging to restore sub-second SQL performance.
Iceberg Metadata Compaction: Solving Latency Breaches
Iceberg metadata compaction became an emergency task when dashboard queries began timing out at 3 AM. The analytics team observed query planning latency ballooning from 350 milliseconds to 42 seconds on a core transactional table ingested every minute. While the physical Parquet files had already been bin-packed through background compaction, the query engine was choking on 180,000 distinct manifest files accumulated over four months of continuous micro-batch streaming. This metadata overhead breached executive SLAs and caused worker nodes in Trino to exhaust heap memory during split generation.
Micro-batch streaming ingestion engines like Spark Structured Streaming or Flink commit data to Apache Iceberg tables by generating new snapshot records at brief intervals. Every commit writes a new metadata JSON file, a manifest list, and one or more manifest files tracking newly added Parquet files. When streaming workloads insert small micro-batches every 60 seconds, the table metadata tree scales exponentially faster than the physical data underlying it. Query engines must traverse this tree during planning to evaluate partition pruning and file pruning predicates. Without proactive metadata management, table planning time eventually dominates total query execution time, regardless of how fast the underlying storage reads the Parquet footers.
Diagnosing Manifest Bloat in High-Frequency Storage Systems
Identifying metadata bloat requires looking beyond raw data file size. When analyzing execution plans in query engines like Trino or Spark SQL, a distinct signature emerges: the planning phase consumes more than 80% of total runtime, while actual S3 or GCS IO scan time remains minimal. The underlying mechanism is simple yet catastrophic at scale. Every Iceberg manifest file contains entries for individual Parquet files, including column-level lower and upper bounds, null counts, and partition tuples. When thousands of small manifest files populate the manifest list, the query coordinator must download and deserialize hundreds of thousands of Avro records before deciding which Parquet files to scan.
To diagnose this state, engineers can inspect the table's metadata_log and manifests system tables. Evaluating total manifest count, average manifest file size, and snapshot history reveals whether physical files or metadata structures represent the primary bottleneck. If the average manifest file size drops below 100 KB while the manifest count reaches tens of thousands, metadata pruning is required. This challenge is distinct from file compaction strategies used in architectures like Snowflake dynamic tables cost patterns, where data file sizing is managed natively inside proprietary engine engines.
Executing Spark Actions for Iceberg Manifest Rewrites
Resolving manifest overhead requires rewriting the manifest list and combining small manifest files into consolidated Avro files aligned with table partitioning boundaries. Apache Iceberg provides specialized Spark actions to perform this optimization concurrently without locking out active table writers. The rewrite_manifests procedure reorganizes existing manifest entries into larger, well-partitioned manifest files, dramatically reducing the number of S3 requests required during query planning.
Running manifest rewrites in isolation solves planning latency, but full metadata hygiene requires a multi-step routine. Engineers must sequence manifest rewriting alongside snapshot expiration and orphan file cleanup. The PySpark implementation below illustrates an enterprise-grade maintenance routine executed through Spark submission scripts:
from pyspark.sql import SparkSession
from pyiceberg.catalog import load_catalog
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("iceberg_maintenance")
def run_iceberg_metadata_maintenance(
table_identifier: str,
retention_days: int = 7
) -> None:
spark = SparkSession.builder \
.appName("IcebergMetadataCompaction") \
.config("spark.sql.extensions", "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions") \
.config("spark.sql.catalog.prod", "org.apache.iceberg.spark.SparkCatalog") \
.config("spark.sql.catalog.prod.type", "hadoop") \
.getOrCreate()
logger.info(f"Starting metadata compaction for table: {table_identifier}")
# Step 1: Rewrite small manifest files into target 8MB manifest files
logger.info("Executing rewrite_manifests procedure...")
spark.sql(f"""
CALL prod.system.rewrite_manifests(
table => '{table_identifier}',
use_caching => true
)
""").show()
# Step 2: Expire snapshots older than retention threshold to clean manifest lists
logger.info(f"Expiring snapshots older than {retention_days} days...")
spark.sql(f"""
CALL prod.system.expire_snapshots(
table => '{table_identifier}',
older_than => TIMESTAMPADD(DAY, -{retention_days}, CURRENT_TIMESTAMP()),
retain_last => 10
)
""").show()
# Step 3: Remove unreferenced orphan data and metadata files from object storage
logger.info("Removing orphan files...")
spark.sql(f"""
CALL prod.system.remove_orphan_files(
table => '{table_identifier}',
older_than => TIMESTAMPADD(DAY, -2, CURRENT_TIMESTAMP())
)
""").show()
logger.info("Iceberg metadata maintenance completed successfully.")
if __name__ == "__main__":
run_iceberg_metadata_maintenance("prod.analytics.user_events_stream", retention_days=7)
This execution path restructures manifests so that each manifest file contains entries belonging exclusively to specific partitions or partition ranges. Consequently, when a query specifies a partition predicate, the engine reads the manifest list, compares partition bounds at the manifest header level, and completely skips downloading irrelevant manifest files.
Balancing Snapshot Retention and Time-Travel Requirements
Metadata management introduces trade-offs between system performance and historical audit capability. Apache Iceberg supports time-travel queries and zero-copy branching by retaining historical snapshot references in table metadata. However, keeping every snapshot from a streaming table that commits every minute yields 1,440 snapshots per day, or over 43,000 snapshots a month. Every active snapshot preserves a pointer to a manifest list, keeping thousands of obsolete manifest files alive in object storage and bloating the root metadata.json document.
Data engineering leaders must define clear SLA tiers for snapshot retention based on business requirements. Operational tables typically mandate a 3-day to 7-day retention window for time-travel debugging, while compliance-driven historical tables can push long-term history into iceberg table tags or dedicated cold storage archives. Setting retain_last parameters in the expire_snapshots call guarantees that even if a pipeline halts for several days, a baseline count of recent snapshots remains intact for recovery.
When building lakehouse architectures on cloud engines like the AWS Databricks Lakehouse architecture, integrating metadata maintenance directly into pipeline scheduling prevents storage costs from drifting upwards. Deleting expired snapshots drops object storage costs significantly, as object storage providers charge both for byte storage and for metadata GET/LIST operations invoked by query coordinators.
Automated Maintenance Pipeline Architecture in Spark and Airflow
Manual execution of maintenance procedures is unviable for production platforms operating hundreds of tables. Metadata maintenance must be embedded into orchestrated orchestration pipelines or serverless maintenance workers. A robust operational design separates streaming data insertion pipelines from metadata maintenance jobs to avoid resource contention on compute clusters.
Because Apache Iceberg uses optimistic concurrency control (OCC), write operations commit by atomically swapping metadata pointers. If a streaming ingestion job commits a new snapshot at the exact millisecond a concurrent rewrite_manifests job attempts to commit, Iceberg automatically retries the operation by applying the manifest changes on top of the new commit state. However, to prevent excessive retry loops on ultra-high-throughput tables, maintenance workflows should be scheduled during low-traffic windows or configured to run on dedicated utility clusters.
+-----------------------------------+ +-----------------------------------+
| Streaming Writer (Flink/Spark) | | Scheduled Maintenance (Spark) |
| - Micro-batches every 60 seconds | | - Off-peak execution (Daily) |
+-----------------------------------+ +-----------------------------------+
| |
| Append Data | Rewrite Manifests
v v
+--------------------------------------------------------------------------------+
| Apache Iceberg Table Metadata |
| [ metadata.json ] -> [ manifest list ] -> [ manifest files ] -> [ Parquet ] |
+--------------------------------------------------------------------------------+
^ ^
| Read Requests | Track Anomalies
+-----------------------------------+ +-----------------------------------+
| Query Engine (Trino/Athena) | | Observability Platform |
| - Evaluates Partition Predicates | | - Alerts on Planning Latency |
+-----------------------------------+ +-----------------------------------+
Integrating platform visibility through solutions like the Data Observability Platform enables data reliability teams to track metadata growth trends over time. Key metric targets include manifest file count per table, total active snapshot count, and planning phase execution time in seconds. Setting alerts on query planning latency catches manifest expansion long before end users report broken analytical dashboards.
Production Performance Benchmarks and Business Impact
Implementing structured Iceberg metadata compaction produces dramatic performance improvements across diverse query engines. In a production benchmark conducted on a 14-TB telemetry dataset containing 3.2 billion rows spread across 240,000 Parquet files, metadata pruning yielded measurable latency gains across Trino and Spark SQL workloads:
- Query Planning Phase Latency: Reduced from 41.8 seconds down to 0.35 seconds (a 99.1% reduction in coordinator wall-clock time).
- S3 API List Operations: Decreased from 18,400 GET requests per analytical query to 42 GET requests due to optimized partition-level manifest pruning.
- Coordinator Heap Consumption: Trino coordinator peak memory utilization during query parsing dropped from 28.4 GB to 1.2 GB, eliminating out-of-memory worker crashes.
- End-to-End Execution Time: Dashboard aggregate queries saw total runtime fall from 48.2 seconds to 1.8 seconds, bringing SLAs well within business expectations.
Beyond raw query speed, metadata hygiene generates immediate financial benefits. Object storage costs decline due to the automated deletion of orphan data files and unreferenced metadata files. Furthermore, query engines consume far fewer compute credits when planning phases complete instantly, directly reducing overall warehouse runtime costs across cloud infrastructure.