Dremio is now part of SAP
Dremio Blog

48 minute read · October 27, 2026

Incremental Processing with Apache Iceberg: Snapshot Scans, Changelog Views, and Checkpoints

Alex Merced Alex Merced Head of DevRel, Dremio
Incremental Processing with Apache Iceberg: Snapshot Scans, Changelog Views, and Checkpoints
Copied to clipboard

What incremental processing with Iceberg does, in one paragraph

Incremental processing with Apache Iceberg reads a defined snapshot range and records the last successfully applied snapshot as a durable checkpoint. For append-only flows a snapshot-range append scan is sufficient. When records are updated or deleted you must use changelog-capable views or engine-specific change processing. A dependable pipeline explicitly handles exclusive start and inclusive end snapshot semantics, snapshot expiration, branch ancestry, checkpoint durability, and idempotent replay logic.

How Iceberg incremental processing works, technically

Iceberg organizes table history as a sequence of snapshots. Each snapshot references a set of data files and points to a manifest list that lists file additions and deletions. Incremental processing means selecting a contiguous range of snapshots to read and applying the deltas from that range to an external system. The common pattern is: record the last-applied snapshot id as the checkpoint, on the next run read from checkpoint+1 through current snapshot id, apply changes, then update the checkpoint after a durable commit.

Two important technical cautions to keep top of mind, because they cause obvious failures if you forget them. First, with many engines, notably Apache Spark, incremental reads are append-only. That means Spark-only incremental code will not see deletes or updates as record-level change events. Second, Iceberg snapshot range semantics are start-exclusive and end-inclusive. If your checkpoint records snapshot N, the next run must start from N+1. Missing that off-by-one causes duplicates or gaps.

Diagram: Snapshot checkpoint progression

SNAPSHOT CHECKPOINT PROGRESSION1Snapshot-range read config…2Durable checkpoint transac…3Gap detection and replay p…4Verify the resultA useful implementation has an observable result at every boundary. A successful command alone is not the acceptance test.
Snapshot checkpoint progression. Each stage has a result that can be checked before the next stage begins.

Think of checkpoints as a simple pointer into the snapshot timeline. At time T0 the checkpoint is snapshot 42. A new writer creates snapshots 43, 44, 45. The incrementer reads snapshots 43..45, applies the deltas, and atomically writes checkpoint 45. If an error occurs mid-apply, the checkpoint remains 42 and the pipeline retries from 43. The checkpoint must be durable and written only after a successful sink commit. Common failure modes include the checkpoint being written before the sink commit, or concurrent writers advancing the table while the pipeline is mid-apply.

Append scans versus changelog scans

Append scans read only file additions in a snapshot range. They are cheap because the engine can ignore deleted-file markers. Changelog scans produce row-level change events, such as insert, update, delete. The latter requires the table metadata to expose precise change information and the query engine to support it. Trino, Iceberg native readers, and some engines expose change data through specialized reads or metadata tables; Spark incremental workflows are append-only unless you explicitly use Iceberg change-table features or compute diff from metadata tables.

APPEND SCAN VERSUS CHANGELOG SCANRisk 1Spark incremental reads are append-onlyTest itRisk 2Start snapshot is exclusive and end snapshot is inclusiveBound itRisk 3Snapshot expiration can invalidate a stale checkpointMonitor it
Append scan versus changelog scan. Each technical risk needs a matching test, boundary, or operating signal.

Practically, use append scans for append-only sources like event streams that are only ever appended. Use changelog scans when the upstream writes include UPDATE or DELETE operations, for example when you run CDC or mutable dimension tables. The difference is not academic, it affects whether your sink needs to be able to upsert or delete, and whether you must preserve order within a partition during application.

Exactly-once effect with an idempotent sink

Exactly-once across an Iceberg incremental pipeline requires two things: atomic checkpointing and an idempotent or transactional sink. Atomic checkpointing means the pipeline writes the checkpoint only after the sink confirms the changes are durable. An idempotent sink means that reapplying the same snapshot range yields the same external state, or the sink can deduplicate by sequence id or snapshot id.

EXACTLY-ONCE EFFECT WITH IDEMPOTENT SINKObservecollect the signalCompareuse a baselineDiagnoselocate the boundaryActchange one variablemeasured evidenceunexpected changesmallest safe responsenew baseline
Exactly-once effect with idempotent sink. The loop turns table or catalog signals into controlled operational changes.

Common approaches to idempotency: include snapshot id metadata on each row or batch, and have the sink ignore batches that are already applied. Another pattern is to use a transactional sink (for example a database transaction or a Kafka producer with exactly-once semantics) that commits the entire batch and the checkpoint together under the same durable transaction. If your sink cannot do that, you must design idempotent application logic in the sink or a deduplication layer at read time.

Checkpoint storage and durable transaction example

Checkpoint durability is critical. A common implementation stores checkpoint state in an external durable store, for example a relational table, a durable key-value store, or the same transactional system used by the sink. The implementation must guarantee an atomic transition: the snapshot range is applied to the sink and the checkpoint value is recorded such that either both become visible or neither does.

<!--
Pseudo-Java-like pseudocode for a durable checkpoint transaction. Replace with your platform transaction primitives.
-->
try {
  beginTransaction(); // sink + checkpoint store support a two-phase or single transaction
  List<Snapshot> snapshots = listSnapshots(fromExclusive, toInclusive);
  applySnapshotsToSink(snapshots); // idempotent apply or transactional write
  writeCheckpoint(toInclusive); // durable commit marker
  commitTransaction();
} catch (Exception e) {
  rollbackTransaction();
  throw e;
}

Do not invent a custom local file as the only durable checkpoint unless you also store it in durable distributed storage. If a node dies, local state disappears and your pipeline will either reprocess from an older snapshot or fail to resume.

Worked example: Snapshot-range read configuration

This worked example shows the concrete configuration values you will need for an Iceberg snapshot-range read. The exact API calls differ by engine and version, so verify them against your engine docs. The following points are checked as of October 27, 2026: Iceberg documents snapshot-based scanning and start/end semantics on the project documentation. The examples below are representative and you should map them to your engine API calls.

Required parameters for a snapshot-range read:

  • table identifier, for example catalog.namespace.table
  • start_snapshot_id, exclusive. If checkpoint = N, set start_snapshot_id = N
  • end_snapshot_id, inclusive. Usually the current table snapshot id.
  • scan mode: append-only versus changelog (if supported by the engine)
  • partition filters or manifest filters to limit file reads

Example mapping to engines. Verify API names against the engine documentation for your installed versions. For Spark with Iceberg, the project documents snapshot queries and examples on the Iceberg website. Trino documents connector-level snapshot options in its Iceberg connector documentation.

What to verify before deploying: confirm your engine version supports snapshot range reads and whether it provides changelog semantics. Spark incremental reads remain append-only in documented behavior unless you use explicit change-table or metadata table approaches. Refer to the Iceberg Spark queries documentation for current behavior.

Worked example: Durable checkpoint transaction

Below is a step-by-step sequence you can implement with a transactional database sink that supports transactions across both data and a checkpoint table. If you lack a single transaction covering both, you must implement a two-phase commit pattern or rely on idempotent sink operations.

  • Step 1, read checkpoint: read last_applied_snapshot from checkpoint_store
  • Step 2, list snapshots: call Iceberg listSnapshots(fromExclusive = last_applied_snapshot, toInclusive = current_snapshot)
  • Step 3, prepare batches: load the file lists or row events for those snapshots and compute ordered batches suitable for your sink
  • Step 4, begin sink transaction: start a transaction on the sink and persistent checkpoint store if possible
  • Step 5, write data: apply batches to sink, track per-batch success
  • Step 6, write checkpoint: atomically write new last_applied_snapshot = toInclusive
  • Step 7, commit: commit the transaction and make the checkpoint visible
  • On failure: rollback everything and retry the whole sequence for idempotency

If you must write the checkpoint in a separate system from the sink, ensure you design retry semantics so that reprocessing a snapshot range before the checkpoint is idempotent. For example, include snapshot id metadata on all sink rows and have the sink deduplicate by snapshot id and primary key.

Worked example: Gap detection and replay procedure

Gaps occur when a checkpoint points to snapshot N but the table has advanced to M and you cannot read some intermediate snapshots, most commonly due to snapshot expiration or retention policies. Detecting and recovering from gaps is essential to avoid data loss or inconsistent state.

Detect gap conditions using the following checks:

  • Compare your checkpoint snapshot id to the table history. If checkpoint < earliest available snapshot id, you have a gap caused by snapshot expiration.
  • Check the table manifest list referenced by the earlier snapshots. If manifests are missing because of cleanup, you cannot reconstruct deltas without a materialized backup.
  • Detect non-monotonic ancestry, for example when your checkpoint points to a snapshot from a branch that no longer exists as the mainline. Verify snapshot parentage if ancestry matters for your use case.

Replay procedure when a gap is detected:

  • Fail fast and alert, do not attempt blind incremental replay.
  • Option A, full resync: read the current snapshot as a full snapshot read and rebuild sink state from scratch. This is the safest approach when history is missing.
  • Option B, reconstruct from backups: if you maintain your own archival copy of past snapshots or file-level backups, reconstruct the missing range and then proceed with normal incremental replay.
  • Option C, selective repair: for some sinks you can apply a best-effort reconciliation using business keys and partial history, but this is application-specific and risky.

Iceberg snapshot expiration can invalidate a stale checkpoint. The Iceberg project documents snapshot lifecycle and default retention behavior. Check your table retention policies and any cleanup job that may compact or expire old snapshots. If you rely on incremental processing, extend snapshot retention or export snapshots to cold storage to enable recovery.

Diagram: Expired checkpoint recovery path

EXPIRED CHECKPOINT RECOVERY PATHInventoryversions and consumersTestfeature and failure pathsCanaryone bounded workloadDecideexpand or stopA failed gate returns to inventory with evidence. It does not become a production exception.
Expired checkpoint recovery path. A reversible canary keeps an unsupported client or unsafe policy from becoming a fleet-wide incident.

Operationally, design your pipeline to detect missing snapshot artifacts early. If the checkpoint is expired you either run a full table copy or use a backup-based restore. A failure mode I have seen in production is a nightly compaction job that reduces snapshot retention to a few days. An incremental consumer that runs weekly suddenly cannot find manifests for older snapshots and fails during the listing step. The right fix was either to increase retention for tables with incremental consumers or to add an archival pipeline that copies manifests to long-term storage accessible to the consumer.

Engine differences and version checks

Not every engine exposes the same snapshot or changelog APIs. Two concrete references you must check for your environment: the Iceberg project documents Spark query behavior and snapshot operations on the Iceberg documentation site, and Trino documents its Iceberg connector snapshot and table interaction behavior. Engines may differ on whether they expose row-level delete/update events, whether they support time travel by snapshot id, and whether they can produce a changelog stream.

As of October 27, 2026 these references are authoritative for project behavior. Consult the Iceberg releases page and release blog posts for specifics about changes in snapshot handling across versions. If you are using Spark, confirm whether your Spark + Iceberg combination provides changelog semantics or only append scans. I have repeatedly seen teams assume Spark incremental reads include deletes; that assumption causes silent divergence when the source has updates.

Useful links right in this discussion: the Iceberg Spark queries documentation helps you map snapshot read examples to Spark SQL or DataFrame APIs. The Iceberg releases and 1.11.0 release blog post explain recent snapshot and metadata-table features that may be relevant. The Trino Iceberg connector documentation is useful if you use Trino for your reads. Keep these references handy when implementing or upgrading your stack.

Monitoring and metrics to measure after deployment

After deploying incremental processing you must measure both pipeline health and data correctness. Key metrics and checks:

  • Checkpoint freshness, for example time and snapshot id lag between table current snapshot and checkpoint
  • Processing throughput, in GB/s or rows/s, and comparison of expected versus observed file sizes
  • Sink idempotency errors or duplicate-keys detected during reconciliation
  • Gap detection alerts, for example when checkpoint points to a snapshot older than earliest retained snapshot
  • Manifest read errors or file-not-found counts when listing snapshots
  • End-to-end latency from snapshot creation to sink visibility

On top of numeric metrics, add periodic data quality checks: row counts by partition and business key reconciliation between source and sink. If your sink supports materialized views or primary-key validation, schedule an hourly parity check for critical partitions.

Operational failure modes and how to handle them

Here are the failure modes I see most often, with concrete mitigations.

  • Checkpoint written before sink commit. Symptom, checkpoint advanced but sink lacks data. Mitigation, always write checkpoint as part of the sink transaction or after a two-phase commit that ensures atomic visibility.
  • Snapshot expired. Symptom, listing snapshots fails or required manifests are missing. Mitigation, extend snapshot retention or maintain an external archive. If you discover an expired checkpoint, run a full resync.
  • Append-only assumption. Symptom, deletes do not appear in sink and data drifts. Mitigation, verify engine behavior. For Spark, treat incremental reads as append-only unless you explicitly use Iceberg change-table features. Consider using metadata tables or engine features that expose deletes.
  • Concurrent writers and branching. Symptom, snapshot ancestry differs and expected deltas cannot be applied. Mitigation, detect non-linear ancestry by checking snapshot parentage. Prefer using snapshots from a stable branch or mainline for incremental consumers.
  • Partial batch failures. Symptom, some batches in a snapshot range applied, others failed and checkpoint was written. Mitigation, design per-run atomicity and make the checkpoint write conditional on full success.

Rollout checklist

Use this checklist when you introduce incremental snapshot-based ingestion into production.

  • Verify engine snapshot and changelog capabilities for your installed versions, using the Iceberg Spark queries docs and the Trino connector documentation.
  • Decide on append-only versus changelog mode based on source write patterns. Document that Spark incremental reads are append-only unless you intentionally use change-table features.
  • Choose a durable checkpoint store. Implement atomic checkpoint writes with sink transactions when possible.
  • Increase snapshot retention or set up snapshot archiving for tables consumed incrementally.
  • Implement gap detection and a tested full-resync procedure.
  • Add metrics for checkpoint lag, manifest errors, throughput, and sink idempotency failures.
  • Run an end-to-end test with simulated failures, including mid-batch sink failures and snapshot expiration, and verify recovery paths.
  • Document operational runbooks for expired checkpoints and data reconciliation.

Testing and validation strategy

Testing must be practical and repeatable. I recommend these experiments in a staging cluster such as the clustering test environment or the multi-engine DML test environment to validate behavior under controlled conditions. Use the clustering test environment for functional tests and the multi-engine DML test environment for scale tests, so you exercise different failure surfaces.

Tests to run:

  • Append-only workflow: produce 1 million small files across multiple snapshots and verify the incremental consumer only reads appended files and commits them atomically.
  • Update/delete workflow: run updates and deletes against the Iceberg table and verify changelog consumers receive the expected events or that a full diff from metadata tables reproduces the net state.
  • Checkpoint durability: simulate sink failure after data write but before checkpoint write, and verify the consumer replays the expected snapshot range without duplicate effects.
  • Snapshot expiration: run a cleanup job to expire snapshots and verify that gap detection triggers and the full resync path recovers the sink.
  • Concurrent writes: create a short-lived branch and merge sequences, verify snapshot ancestry checks detect non-linear history if your consumer cannot handle it.

Automate these tests as part of CI/CD. Include tests that assert the checkpoint semantics, including off-by-one behavior for start exclusive and end inclusive ranges.

Practical decision points

When designing a pipeline you will face three practical decisions: append-only versus changelog, where to store the checkpoint, and how to ensure idempotency. Make these choices explicit.

  • Append-only versus changelog, decide by the presence of updates/deletes in the source. If you are unsure, treat the source as mutable and plan for changelog or periodic full resyncs.
  • Checkpoint store, choose a transactional store when possible. The simplest durable option is a database table with a unique constraint. If your sink supports transactions you can co-locate checkpoint writes with the sink commit.
  • Idempotency strategy, prefer sink-level transactions. If not possible, include snapshot id metadata on sink rows and deduplicate on the sink side.

For teams running Dremio as part of their stack, the Dremio guide to Apache Iceberg and the Dremio post on querying Iceberg metadata tables are useful references. The guide explains common configuration choices and shows examples for integrating Iceberg with execution engines. The post on metadata tables explains how to query internal metadata for manifest and snapshot information, which helps for gap detection and offline reconstruction.

What to measure after a deployment

Short version, measure correctness, freshness, and stability. These are the concrete metrics I track in production.

  • Snapshot lag, measured as current_table_snapshot_id minus checkpoint_snapshot_id, and its time-based equivalent
  • Manifest read errors per hour
  • Number of replays caused by checkpoint mismatches
  • Duplicates detected by sink idempotency checks
  • End-to-end latency from snapshot creation to sink commit
  • Storage usage of retained snapshots and manifest lists

Use the monitoring recommendations in the Dremio post on monitoring Iceberg table health to collect manifest-level metrics and snapshot timelines. That post walks through metrics to collect and how to expose them through metadata tables so you can automate gap detection and retention alarms.

Rollout example and a small script of checks

Before flipping a production consumer to incremental mode, run this quick checklist as a scripted verification in your staging cluster.

<!--
Pseudo-check script. Replace CLI and API calls with your platform equivalents.
-->
# 1. Check current snapshot
current_snapshot = get_table_current_snapshot(table)
# 2. Read checkpoint
checkpoint = read_checkpoint(checkpoint_store, consumer_id)
# 3. Verify checkpoint not older than retention
earliest_snapshot = get_earliest_available_snapshot(table)
assert checkpoint >= earliest_snapshot, "checkpoint expired"
# 4. Perform a dry-run list
snapshots = list_snapshots(checkpoint, current_snapshot)
validate_snapshots(snapshots)
# 5. Run a single-batch apply and rollback test
apply_snapshots_dry_run(snapshots[0])

If any step fails, do not enable incremental mode. Fix the condition and rerun. I have seen teams switch to incremental inbound consumption only to discover missing manifests at scale; that mistake costs hours of triage and a complete sink resync.

Final recommendations

Implement incremental processing by treating snapshots as the unit of work, checkpointing snapshot ids durably after sink commits, and choosing append versus changelog modes based on your source semantics. Make snapshot expiration and checkpoint recovery first-class parts of your design. Test the edge cases, automate gap detection, and use Dremio metadata and monitoring guidance to keep a reliable operational picture.

Implementation recipe: end-to-end snapshot-range reader with durable checkpoints

This section gives a concrete, runnable sequence you can follow to implement the snapshot-range read, durable checkpoint transaction, and gap detection and replay procedure already discussed. The steps assume a Spark job that uses Iceberg snapshot-range reads, a separate durable checkpoint store (for example a transactional metastore or an atomic key value store), and an idempotent sink. The recipe focuses on what to set, what to validate, and what to do when a snapshot expires. Verify behavior against the Iceberg Spark queries documentation and your engine version, because behavior and supported options vary between Iceberg releases and connectors.

High level steps, each expanded below:

  • Initialize checkpoint state and validate table snapshot history.
  • Compute exclusive start snapshot id and inclusive end snapshot id for a read range.
  • Perform snapshot-range read using your engine, writing to the sink atomically.
  • Commit durable checkpoint transaction that moves the checkpoint pointer forward.
  • Run gap detection after commit and, if gaps appear, execute replay using tombstone-aware reads.

All code examples below are pseudocode. Do not assume these exact APIs exist in your environment. Check the Iceberg Spark queries documentation and your engine's connector docs for the exact call signatures and supported options. The examples assume Spark 3.x with the Iceberg Spark runtime and a key value store that supports conditional writes for the checkpoint commit.

1) Initialize checkpoint and validate snapshot availability

Before the first run, record a checkpoint entry that points to the snapshot id you intend to start after. The convention here is the checkpoint stores the last processed snapshot id, so the next run will read snapshots greater than that id. Confirm the checkpointed id exists in the table history or is lower than the earliest retained snapshot. If the checkpoint points to an expired snapshot, you must choose whether to fail, fast forward to the earliest available snapshot, or perform a full refresh. Record the decision in your runbook.

Validation checklist:

  • Fetch current table snapshots via the table metadata API. Verify the checkpointed id is present or earlier than the earliest snapshot.
  • If expired, fetch the earliest available snapshot id and log a mismatch for human review.
  • Ensure your engine version supports snapshot-range reads as you intend. See the Iceberg Spark queries documentation and your connector docs for exact behavior. Document the version you validated.

2) Compute start and end snapshot ids for the run

Remember, for Iceberg snapshot-range reads: the start snapshot id is exclusive and the end snapshot id is inclusive. That pattern prevents double processing when you persist the last-processed snapshot id as the checkpoint. Use the checkpointed id as start_exclusive and select an end_inclusive based on time, number of commits, or a target snapshot id taken from the table metadata.

Example decision rules to pick the end snapshot id:

  • Time based: pick the latest snapshot at job start minus N seconds to avoid race conditions with in-flight commits.
  • Count based: pick the Nth snapshot since start_exclusive, useful when commits are frequent.
  • Fixed id: use a specific snapshot id when replaying a known range.

Concrete verification before read:

  • Confirm start_exclusive equals the stored checkpoint last processed id.
  • Confirm end_inclusive exists in the current snapshot list. If it does not exist because it expired between selection and read start, abort and re-evaluate mapping to the earliest retained snapshot.

3) Execute the snapshot-range read and write to an idempotent sink

Run the engine read with the computed start and end. For Spark, your call should use the engine-provided snapshot-range read option. Remember Spark incremental reads are append only in Iceberg as of some versions. That means append scans will not directly surface deletes and updates unless the engine supports changelog views or you use a snapshot-range read that includes file level changes and applies delete files. Confirm your engine version and connector implementation first.

Write results to a sink that can deduplicate or is idempotent. Ideal sinks accept an idempotency key per record or support transactional upserts. If you only have append sinks, ensure the sink deduplicates using a stable record key and snapshot id metadata.

4) Commit a durable checkpoint transaction

After the sink write completes, update the checkpoint atomically. Use a conditional write that moves the checkpoint from the prior value to the new end_inclusive only if the stored value still equals the prior checkpoint. This avoids lost updates when multiple workers compete. If your checkpoint store supports conditional compare-and-swap or single-row database transactions, use them.

Example logic:

<?php
// Pseudocode: conditional commit
old = read_checkpoint(table_id)
if old != expected_old:
    raise ConcurrencyError
write_checkpoint_if_equals(table_id, expected_old, new_end_inclusive)
?>

If the conditional commit fails, you must detect whether the sink write still corresponds to the committed checkpoint by reading the actual stored checkpoint. If another process moved the checkpoint forward to a value that includes your end_inclusive, you can consider your work already covered and skip further action. If the checkpoint moved to an earlier snapshot or a different branch, you must run the gap detection procedure below.

5) Run gap detection and, if needed, replay

Gap detection compares the checkpoint progression across time or across partitions. A simple approach uses a sequence of checkpoints with wall clock timestamps. If you expect the checkpoint to advance monotonically, detect any backward moves or holes where the checkpoint was not updated for an expected period. For per-partition processing, verify each partition checkpoint independently.

When gaps are detected, you will replay the missing snapshot range. Use a snapshot-range read that covers the missing inclusive range. If the missing range includes deletes or updates, prefer a changelog-capable read if your engine supports it, or perform a snapshot replay that re-applies file level changes. The replay must be idempotent. One reliable pattern is to include snapshot id and source run id in every written record and let the sink deduplicate by (record key, snapshot id) or keep the latest snapshot id per key.

Edge case: if a checkpoint points to an expired snapshot, you cannot build the missing diffs from metadata. In that case, consider a full table scan or an optimized manifest list scan that reconstructs current state. The choice depends on the data volume and SLA.

Failure injection test and expected behaviors

Testing failure modes is necessary to gain confidence. Below is a small test plan you can run in a staging cluster. Each test includes the fault injection, the expected behavior, and how to verify. Run these tests against the same engine and Iceberg version you plan to deploy. Some behaviors are version dependent. Verify the observed results against the Iceberg Spark queries documentation and your connector docs.

Test 1: checkpoint commit races

  • Fault injection: start two workers with the same start_exclusive, both compute the same end_inclusive, and both write to the sink concurrently. Cause one conditional checkpoint commit to fail by having the other succeed just before commit.
  • Expected behavior: one worker commits the checkpoint, the other receives a conditional write failure. The second worker must read the checkpoint and see it now points to a snapshot that includes or surpasses end_inclusive. If it does, it exits without reapplying. If not, it must perform gap detection.
  • Verification: confirm checkpoint store shows one commit. Confirm sink shows no duplicated application of the same snapshot diffs when deduplication keys are correct.

Test 2: mid-read job crash

  • Fault injection: kill the worker after it has written partial data to the sink but before committing the checkpoint.
  • Expected behavior: the checkpoint remains unchanged. The next run should detect the previous incomplete write by comparing sink write markers or by missing checkpoint progress. Replay the snapshot range and write idempotently so no duplicate application occurs.
  • Verification: validate the sink has only one effective application of each change, using record key and snapshot id grouping. Verify the checkpoint advances only after a successful idempotent write and checkpoint commit.

Test 3: snapshot expiration during gap replay

  • Fault injection: create a snapshot, then let the system expire older snapshots by triggering the cleanup policy while a checkpoint points to that old snapshot. Then attempt a replay that relies on that expired snapshot id.
  • Expected behavior: your validation should detect the checkpoint points to an expired snapshot. The replay cannot reconstruct diffs from that id. The system should either fast forward to the earliest available snapshot and perform a complete state rebuild, or abort and require manual intervention, depending on your policy.
  • Verification: confirm the runbook triggered the chosen policy. Confirm the rebuilt state matches the live table if a full rebuild was chosen.

Test 4: concurrent external compaction or rewrite

  • Fault injection: run a table compaction or rewrite operation that changes file layout while your snapshot-range reader is running.
  • Expected behavior: Iceberg table snapshots remain consistent. The snapshot-range read should return a deterministic result for the chosen end_inclusive snapshot id. If compaction produced new snapshots, they will appear in later ranges. Confirm your reader anchors to explicit snapshot ids to avoid nondeterminism.
  • Verification: compare records read for identical (start_exclusive, end_inclusive) pairs run before and after compaction. They must match if run against the same end_inclusive snapshot id.

Operating metric set and alerting thresholds

Beyond the monitoring already described, add the following actionable metrics that tie directly to snapshot-range processing, checkpoints, and replay reliability. These metrics focus on operability, not just system health.

  • Checkpoint lag, in seconds: time since last successful checkpoint commit. Alert when greater than service SLA, for example 5 minutes for near real time pipelines.
  • Checkpoint commit failures per hour: count of conditional commit failures. Alert when above a baseline, for example more than 5 per hour, indicating contention or clock skew.
  • Replay frequency: number of gap-triggered replays per hour. A small steady rate may be normal, but a spike indicates instability or frequent snapshot expiration.
  • Expired-checkpoint occurrences: count of runs that discovered a checkpoint pointing to an expired snapshot. Any occurrence should trigger an investigation for retention policy mismatch.
  • Idempotent sink dedup ratio: compare records written versus unique keys after deduplication. If ratio exceeds 1.02, investigate duplicate writes or insufficient idempotency keys.
  • Replay duration and throughput: time taken and rows processed during a replay. If replays take too long, consider increasing retention or adjusting compaction cadence.

Suggested alert thresholds are conservative. Adjust them to your workload. Always attach the checkpoint history and snapshot ids to alerts to speed diagnosis. Label metrics with table id, pipeline id, and worker region.

Decision table: choose a recovery policy for expired checkpoints

When a checkpoint points to an expired snapshot, you cannot reconstruct diffs using that snapshot id. You have three pragmatic recovery policies. Each has operational trade offs. Pick the one that matches your SLAs and data volume, and codify it in automation and runbooks.

  • Fast forward to earliest retained snapshot and rebuild current state
    • When to prefer: table is small or you can rebuild within SLA.
    • Consequences: higher compute and I/O cost for rebuilds, but avoids manual intervention.
    • Implementation notes: perform a full table scan anchored to earliest retained snapshot id, write to the sink idempotently, then commit checkpoint to that snapshot id.
  • Abort and require manual reconciliation
    • When to prefer: high-risk data where automated reconciliation may lose business semantics.
    • Consequences: human delay, but safer for complex semantics like financial ledgers.
    • Implementation notes: mark the pipeline as failed, capture metadata about snapshot range and available manifests, and require an operator to decide whether to rebuild or correct application state.
  • Partial replay with best-effort reconstruction
    • When to prefer: large tables where full rebuild is prohibitively expensive and partial correctness is acceptable temporarily.
    • Consequences: subtle correctness window where some historical changes might be missed until a backfill can run.
    • Implementation notes: replay from earliest retained snapshot up to end_inclusive where available, log missing intervals, and schedule a backfill when retention settings or offline resources allow.

Document the chosen policy for each pipeline, and automate detection so that the system either executes the policy or creates an incident that includes a recommended action and costs for a rebuild.

Rollout experiment: safe ramp and verification script

Use a canary rollout when you introduce snapshot-range processing and durable checkpoints. The plan below gives a small script you can run for each canary batch. It assumes you control a small subset of tenants or partitions that are representative of production traffic.

  • Step 0, baseline: capture current end to end latency and deduplication success for the target partition under existing processing.
  • Step 1, dry run: run the snapshot-range reader in read-only mode that writes to a test sink. Verify outputs match the production sink for the same range.
  • Step 2, patch canary: enable full processing for one partition. Use the durable checkpoint transaction with conditional commits. Monitor checkpoint lag, commit failures, and idempotent sink dedup ratio.
  • Step 3, scale up: double the number of canary partitions every 30 minutes if no alerts fire. Keep a detailed log of checkpoint ids and snapshot lists for each step.
  • Step 4, revert plan: if dedup ratio or replay frequency exceeds thresholds, stop the rollout and trigger either an automated rollback to previous reader or a full rebuild depending on your policy.

Verification script pseudocode to run after each canary batch. Replace storage and connector calls with your engine APIs before use:

<?php
// Pseudocode: post-deploy verification for a partition
last_checkpoint = read_checkpoint(pipeline, partition)
snapshots = list_table_snapshots(table)
if last_checkpoint > snapshots.latest:
    raise Alert('checkpoint beyond latest snapshot')
if last_checkpoint < snapshots.earliest:
    raise Alert('checkpoint points to expired snapshot')
// Validate sink state matches table state at last_checkpoint
sink_count = count_sink_records(partition)
expected_count = count_table_state_at_snapshot(table, last_checkpoint)
if abs(sink_count - expected_count) / expected_count > 0.01:
    raise Alert('sink mismatch')
?>

Label all verification outputs with the Iceberg version you ran these checks against. Some behaviors, such as the specifics of snapshot-range support or changelog scans, can change between releases. Check the project release notes for changes that affect your pipeline.

What to measure after deployment, precisely

This section refines the earlier monitoring section into an operational measurement plan you should run for the first 72 hours, and then continuously at lower frequency. Collect these metrics with labels: table id, pipeline id, partition id, run id, and Iceberg version. Tag each metric with the snapshot range processed (start_exclusive, end_inclusive).

  • Per-run checkpoint commit latency, median and 95th percentile. High tail latency often indicates contention or store performance issues.
  • Per-run sink write idempotency rate, measured as unique keys / written records. Ideally 1.0. Track drift over time.
  • Gap detection events per pipeline, and backlog size of missing snapshot ranges. Backlogs should trend down after an incident is resolved.
  • Fraction of runs that encountered snapshot-expired errors. If nonzero, verify retention policy alignment between producer workloads and your pipeline.
  • End to end latency from snapshot end_inclusive time to sink visibility. This measures how quickly commits become visible downstream.
  • CPU, memory, and I/O per run, to control cost when replaying. Report increases during replays separately from normal runs.

Collecting these metrics during the initial 72 hour ramp will reveal steady state and corner cases. Use the data to tighten checkpoint frequency, snapshot retention, and compaction schedules.

FAQ

Q1: Are Spark incremental reads able to capture deletes and updates?

No. Spark incremental reads are append-only in documented behavior for common Spark+Iceberg integrations. To see deletes or updates you must use change-aware features that expose row-level changes, or compute diffs using metadata tables. Verify behavior against the Iceberg Spark queries documentation for your versions.

Q2: How should I treat the start and end snapshot ids when reading a range?

Start snapshot id is exclusive and end snapshot id is inclusive. If your checkpoint records snapshot N, your next range should start at N+1 and end at the current snapshot id. Off-by-one errors lead to duplicate application or gaps.

Q3: What happens if a checkpoint points to an expired snapshot?

If the checkpoint references a snapshot that has been expired and the required manifests or files are no longer available, the incremental consumer cannot reconstruct the missing deltas. You must either run a full resync from the current snapshot or restore archived snapshots. Detect this condition by comparing checkpoint id to the earliest available snapshot id and alert immediately.

Q4: Where should I store checkpoints?

Store checkpoints in a durable, highly available store. Prefer a transactional database or the sink itself when it supports transactions. If that is not possible, use a distributed key-value store or a dedicated checkpoint table. The important property is that checkpoint writes are durable and atomic relative to sink commits.

Q5: How do I make replays idempotent?

Make replays idempotent by attaching snapshot id metadata to every applied record or batch and having the sink ignore duplicate snapshot ids. Alternatively, use a transactional sink that can rollback and commit batches atomically with the checkpoint write.

Q6: Which Dremio resources help with monitoring and metadata?

The Dremio article on monitoring Iceberg table health explains which metrics and metadata tables to expose for manifest and snapshot monitoring. The Dremio post on querying Iceberg metadata tables shows practical queries you can run against metadata tables to locate manifests, list snapshots, and detect expiration. The Dremio Apache Iceberg guide provides broader integration details and configuration examples for using Iceberg with query engines.

These guides cover adjacent implementation details that are outside this article's main scope.

Keep learning

For a deeper treatment, download Apache Iceberg: The Definitive Guide, co-authored by Alex Merced and available free from Dremio.

To put the catalog and table-format ideas into practice, explore Dremio's Apache Iceberg platform.

Sources

Additional Dremio resources cited inline: Dremio post on querying Iceberg metadata tables, Dremio guide to monitoring Iceberg table health, and Dremio Apache Iceberg guide. Use the clustering test environment for staging functional tests and the multi-engine DML test environment for scale tests in the rollout examples.

Try Dremio Cloud free for 30 days

Deploy agentic analytics directly on Apache Iceberg data with no pipelines and no added overhead.