Back to blog

Snapshot Retention and Time Travel: A Guide

Iceberg snapshots enable time travel, rollback, and audit — but accumulate indefinitely unless managed. A practical guide to retention strategies, expiration safety, and automated lifecycle management.

LakeOps snapshot management — table snapshots list with time travel, rollback, and retention controls

Every write to an Apache Iceberg table creates a snapshot — a complete, immutable view of the table at that moment. Snapshots are how Iceberg provides time travel, instant rollback, audit trails, and concurrent reads without locks.

They also accumulate indefinitely unless explicitly managed. A streaming table with 1-minute checkpoints creates 1,440 snapshots per day. Each pins references to data files that cannot be garbage-collected until the snapshot is expired. After a month without expiration, a single table can hold tens of terabytes of logically dead but physically billable data on S3.

The challenge: keep enough snapshots for time travel and rollback safety — expire enough to control storage costs and metadata overhead. This guide covers how it all works, what it costs, and how to manage it — both through an autonomous control plane like LakeOps and through manual SQL procedures.

LakeOps dashboard — lake-wide KPIs, storage, CPU, and optimization activity
LakeOps dashboard: lake-wide health KPIs, storage trends, and optimization activity across all connected catalogs — the operational overview that surfaces snapshot-driven storage waste before it compounds.
LakeOps monitoring — operations coverage, readiness, and maintenance timeline
Operations monitoring: coverage percentage, table readiness status, and maintenance execution timeline — continuous visibility into which tables are managed and which need attention.
LakeOps events — audit trail of expiration, compaction, and rewrite operations
Table events: every snapshot expiration, compaction, and manifest rewrite logged with timestamps, duration, and impact — the full audit trail of what happened and when.
LakeOps tables — health status, sizes, and monitoring across the lake
Lake-wide table health: every table classified by status (Healthy, Warning, Critical), size, record count, and last modified — tables with excessive snapshot accumulation surface as warnings before costs compound.
LakeOps insights — table health severity issues surfaced proactively
Proactive insights: excessive snapshots, manifest bloat, and file fragmentation surfaced at four severity levels — problems identified before they impact queries or storage costs.

How snapshots and time travel work

The snapshot chain

Every commit — INSERT, UPDATE, DELETE, MERGE, compaction — creates a new snapshot. A snapshot points to a manifest list → manifest files → data files. It represents the complete, consistent table state at that commit. Snapshots are immutable and form an append-only chain.

Time travel

Query any retained snapshot without restoring backups or copying data:

sql
1-- By timestamp2SELECT * FROM prod.db.events3TIMESTAMP AS OF '2026-08-01 10:00:00';4 5-- By snapshot ID6SELECT * FROM prod.db.events7VERSION AS OF 8712345678901234567;8 9-- By tag or branch name10SELECT * FROM prod.db.events11VERSION AS OF 'audit-checkpoint-q2';

The query resolves data files from that point in time using the snapshot's schema. No data movement, no separate backup infrastructure.

Rollback

Rollback changes the current table state to a previous snapshot — a metadata-only operation that completes in milliseconds regardless of table size:

sql
1CALL system.rollback_to_snapshot('prod.db.events', 8712345678901234567);2CALL system.rollback_to_timestamp('prod.db.events', TIMESTAMP '2026-08-01 10:00:00');

Use cases: corrupted ETL load, incorrect DELETE, bad MERGE logic, failed schema migration.

Tags and branches

Tags — immutable pointers to specific snapshots. Survive expiration. Use for compliance checkpoints and ML reproducibility:

sql
1ALTER TABLE prod.db.events2CREATE TAG q2_audit AS OF VERSION 8712345678901234567 RETAIN 365 DAYS;

Branches — isolated mutable workstreams. Use for Write-Audit-Publish (WAP):

sql
1ALTER TABLE prod.db.events CREATE BRANCH staging_load RETAIN 7 DAYS;2INSERT INTO prod.db.events.branch_staging_load SELECT * FROM raw.incoming;3-- Validate, then fast-forward main if clean4CALL system.fast_forward('prod.db.events', 'main', 'staging_load');

What snapshots cost

Snapshot costs compound silently across three dimensions.

Pinned data files. When compaction rewrites 1,000 small files into 10 large ones, old snapshots still reference the original 1,000. Those files can't be deleted until every referencing snapshot is expired. A 500 GB table with daily compaction and no expiration accumulates ~500 GB/week of pinned dead data. After 3 months: 6.5 TB stored, $138/month wasted — for one table. Across 200 tables, pinned storage easily exceeds 100 TB.

Metadata growth. Each snapshot adds a metadata entry (JSON) and manifest list (Avro). At 10,000+ snapshots, metadata totals hundreds of MB — inflating catalog operations and slowing snapshot resolution.

Planning impact. The metadata file that engines parse to find the current snapshot bloats proportionally. At 50,000+ accumulated snapshots, this becomes noticeable.

LakeOps table metrics — records, distribution, and detailed activity over time
Table metrics: record counts, file size distribution, and write activity over time — the visibility that reveals whether snapshot accumulation is driving storage growth.

Retention by workload type

Streaming tables (Flink, Kafka Connect, Spark Streaming) — commit every 1–5 minutes. Time travel is for debugging: "what happened an hour ago?" Retention: 3–7 days, retain_last 25–50.

Batch tables (daily/hourly ETL) — commit 1–24 times/day. Time travel is for recovery: "roll back to yesterday." Retention: 7–30 days, retain_last 5–10.

Compliance tables — need specific snapshots preserved for months/years, but not all of them. Use tags on checkpoints (end-of-quarter, audit milestones) with long retention. Expire everything else on a normal 7-day cycle. Tagged snapshots survive expiration automatically.

The expiration cascade

Snapshot expiration is the first step in a maintenance sequence where each operation enables the next:

  1. 1.Expire snapshots → dereferences old data files
  2. 2.Remove orphan files → physically deletes unreferenced files (reclaims storage)
  3. 3.Compact → merges remaining small files with sort order (query performance)
  4. 4.Rewrite manifests → consolidates metadata (planning speed)

Running these out of order wastes compute. Compacting before expiring rewrites files that should be deleted. Orphan cleanup before expiration misses newly-dereferenced files. The sequence matters.

Path 1: Intelligent automation with LakeOps

LakeOps is an autonomous control plane for Apache Iceberg that manages the full table lifecycle — including snapshot retention, expiration, and all downstream operations. Here's what it does for snapshot management specifically.

Policy-based retention at any scope

Define retention rules at catalog, namespace, or table level — more specific policies override broader ones. Set organization-wide defaults once, then make targeted exceptions:

  • Catalog-wide: 7 days, retain_last = 10
  • Streaming namespace: 3 days, retain_last = 50
  • Compliance tables: 30 days + tagged checkpoints with 2-year retention

No per-table cron jobs. No forgotten tables silently accumulating months of dead data.

LakeOps policies — scheduled maintenance operations across the lake
Organization-wide policies: compaction, snapshot expiration, orphan cleanup, and manifest rewrites — each with its own schedule, scope, and status toggle. Define once, enforce continuously across every table.

Conflict-aware expiration

LakeOps monitors query activity across all connected engines (Spark, Trino, Flink, Snowflake, Athena, DuckDB) and never expires snapshots that active readers depend on. If a long-running Spark job is reading snapshot N, that snapshot survives until the job completes. This eliminates FileNotFoundException failures that make teams hesitant to run aggressive expiration.

Adaptive Maintenance

When enabled, Adaptive Maintenance bundles snapshot expiration, compaction, manifest rewriting, and orphan cleanup into a single data-driven operation. LakeOps monitors table activity — commit frequency, file accumulation, snapshot velocity — and runs the right operations at the right time. No fixed schedules, no manual tuning. The system adapts as workloads change.

LakeOps adaptive maintenance — compaction, expiry, manifest rewrite, and orphan cleanup configured per table
Adaptive Maintenance configuration: compaction, snapshot expiry, manifest rewrite, and orphan cleanup managed as a coordinated bundle — LakeOps monitors table signals and executes the right operations at the right time.

Snapshot visibility and rollback

Full snapshot browser per table: every snapshot with ID, timestamp, operation type, and references. Browse history, compare states, tag versions for compliance, rollback with a single click, and see exactly how much storage each retention window costs.

LakeOps table snapshots — list with tag, branch, and rollback controls
Snapshot browser: complete history with snapshot IDs, timestamps, and operations — tag important versions, create branches, or rollback to any snapshot directly.

Coordinated sequence and audit trail

Expiration always runs before orphan cleanup, which runs before compaction, which runs before manifest rewrites. Each step's completion triggers the next. Every execution is logged with full before/after metrics.

LakeOps events — audit trail of maintenance operations with duration and impact
Operations audit trail: every snapshot expiration, compaction, orphan cleanup, and manifest rewrite logged with timestamps, duration, and status — full visibility into what ran, when, and what it achieved.

Setup and results

Connect catalogs (AWS Glue, REST/Polaris/Nessie/Lakekeeper, S3 Tables) in ~10 minutes. No agents, no data movement. Tables are discovered, classified, and managed according to your policies.

LakeOps catalogs — connected Glue, DynamoDB, REST, and S3 Tables
Connected catalogs: AWS Glue, DynamoDB-backed, REST (Polaris/Nessie/Lakekeeper), and S3 Tables — all discovered and managed from a single control plane.
LakeOps results — 95% faster compaction, 12x faster queries, 80% cost reduction
Production results: 95% faster compaction (Rust engine vs Spark), 12x faster queries (sort-order optimization), 80% total cost reduction (storage + compute + maintenance combined).

Path 2: Manual management with SQL

Every operation LakeOps automates is also achievable with SQL procedures and scheduling.

expire_snapshots

sql
1-- Basic: expire older than 7 days, keep at least 102CALL system.expire_snapshots(3  table => 'prod.db.events',4  older_than => TIMESTAMP '2026-07-26 00:00:00',5  retain_last => 106);7 8-- Production: streaming mode for large tables9CALL system.expire_snapshots(10  table => 'prod.db.events',11  older_than => TIMESTAMP '2026-07-26 00:00:00',12  retain_last => 10,13  stream_results => true,14  max_concurrent_deletes => 5015);

Parameters:

  • older_than — expire snapshots before this timestamp. Set ≥ 2x your longest query duration
  • retain_last — minimum snapshots to keep regardless of age. Floor: never < 2 in production
  • stream_results — streaming deletion (avoids OOM for large tables)
  • max_concurrent_deletes — controls S3 API pressure

Table-level properties

Set retention in table properties so any maintenance system enforces it automatically:

sql
1ALTER TABLE prod.db.events SET TBLPROPERTIES (2  'history.expire.max-snapshot-age-ms' = '604800000',  -- 7 days3  'history.expire.min-snapshots-to-keep' = '10'4);

The full maintenance sequence

sql
1-- 1. Expire snapshots (always first)2CALL system.expire_snapshots(3  table => 'prod.db.events',4  older_than => TIMESTAMP '2026-07-26 00:00:00',5  retain_last => 106);7 8-- 2. Remove orphans (wait 72+ hours — protects in-progress writes)9CALL system.remove_orphan_files(10  table => 'prod.db.events',11  older_than => TIMESTAMP '2026-07-23 00:00:00'12);13 14-- 3. Compact15CALL system.rewrite_data_files(table => 'prod.db.events', strategy => 'sort');16 17-- 4. Rewrite manifests18CALL system.rewrite_manifests('prod.db.events');

Monitoring health

sql
1-- Snapshot count and age2SELECT COUNT(*) as total_snapshots,3       MIN(committed_at) as oldest,4       MAX(committed_at) as newest5FROM prod.db.events.snapshots;6 7-- Pinned storage estimate8SELECT 9  (SELECT COUNT(*) FROM prod.db.events.files) as current_files,10  (SELECT COUNT(*) FROM prod.db.events.all_data_files) as all_files_ever;

Alert when: snapshot count > 1,000, oldest > 30 days (non-compliance), or all_files/current_files > 3x.

Safety rules

  • Set older_than ≥ 2x your longest query. A read that opened a snapshot can fail if it's expired mid-scan
  • Set retain_last ≥ 5 for batch, ≥ 25 for streaming. Default of 1 leaves no rollback target
  • Wait 72+ hours before orphan cleanup. In-progress writes may have uncommitted files
  • Tag compliance snapshots explicitly — they survive expiration automatically

Time travel patterns

Debugging: Compare current vs historical state to identify when data went wrong:

sql
1SELECT 'current' as v, COUNT(*) as rows FROM prod.db.orders2UNION ALL3SELECT 'yesterday', COUNT(*) FROM prod.db.orders4TIMESTAMP AS OF '2026-08-01 00:00:00';

ML reproducibility: Tag training snapshots so model experiments are repeatable indefinitely:

sql
1ALTER TABLE prod.db.features2CREATE TAG training_v2 AS OF VERSION 8712345678901234567 RETAIN 365 DAYS;

Incremental processing: Read only changes since last checkpoint:

sql
1SELECT * FROM prod.db.events.changes2WHERE _change_type IN ('insert', 'update_after');

Write-Audit-Publish: Write to a branch, validate, merge only if clean — bad data never reaches production.

Common mistakes

Never expiring. After 6 months, a 500 GB table holds 3+ TB of pinned data. AWS bill increases 6x with no visible cause.

Expiring too aggressively. retain_last = 1 leaves no rollback target. One bad write and the table is unrecoverable without backup infrastructure that Iceberg was supposed to replace.

Same policy everywhere. A streaming table creating 1,440 snapshots/day needs different retention than a batch table creating 1/day.

Orphan cleanup before expiration. Misses newly-dereferenced files. Requires a second pass. Always expire first.

Summary

Snapshots are the foundation of Iceberg's time travel, rollback, and audit capabilities. The goal is not to eliminate them — it's to retain exactly as many as needed and expire the rest systematically.

The framework:

  • Streaming: 3–7 days, retain_last 25–50
  • Batch: 7–30 days, retain_last 5–10
  • Compliance: tag specific checkpoints, expire everything else normally
  • Sequence: expire → orphan cleanup → compact → manifest rewrite
  • Safety: retain_last ≥ 2, older_than ≥ 2x longest query, orphan cleanup after 72h

For teams managing more than a handful of tables, an autonomous control plane like LakeOps handles this continuously — workload-aware policies, conflict-safe expiration, coordinated sequencing, and full snapshot visibility — so time travel works when needed without silent cost accumulation.

Watch demoYouTube ↗
LakeOps walkthrough — catalog connection, table health analysis, snapshot management, and autonomous optimization.

---

Further reading:

Related articles

Found this useful? Share it with your team.