prestodb/presto · error · PrestoException
ICEBERG_INVALID_SNAPSHOT_ID
ICEBERG_INVALID_SNAPSHOT_ID
Error message
Invalid snapshot [%s] for table: %s
What it means
This error is thrown when a query explicitly references an Iceberg snapshot by ID (e.g. 'table@snapshotId' or FOR VERSION AS OF), but the given snapshot ID does not exist in the table's metadata. IcebergUtil.resolveSnapshotIdByName looks the ID up in the loaded Table's snapshot list and throws ICEBERG_INVALID_SNAPSHOT_ID if it is absent. It protects queries from silently resolving to the wrong version of the table.
Source
Thrown at presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergUtil.java:386
.map(ManifestFile::partitionSpecId)
.map(specId -> table.specs().get(specId))
.collect(toImmutableSet()))
.orElseGet(() -> ImmutableSet.copyOf(table.specs().values())); // No snapshot, so no data. This case doesn't matter.
return table.spec().fields().stream()
.filter(field -> field.transform().isIdentity() &&
partitionSpecs.stream()
.allMatch(partitionSpec -> partitionSpec.getFieldsBySourceId(field.sourceId()).stream()
.anyMatch(partitionField -> partitionField.transform().isIdentity())))
.map(field -> IcebergColumnHandle.create(table.schema().findField(field.sourceId()), typeManager, PARTITION_KEY))
.collect(toImmutableList());
}
public static Optional<Long> resolveSnapshotIdByName(Table table, IcebergTableName name)
{
if (name.getSnapshotId().isPresent()) {
if (table.snapshot(name.getSnapshotId().get()) == null) {
throw new PrestoException(ICEBERG_INVALID_SNAPSHOT_ID, format("Invalid snapshot [%s] for table: %s", name.getSnapshotId().get(), table));
}
return name.getSnapshotId();
}
if (name.getBranchName().isPresent()) {
String branchName = name.getBranchName().get();
SnapshotRef branchRef = table.refs().get(branchName);
if (branchRef != null && branchRef.isBranch()) {
return Optional.of(branchRef.snapshotId());
}
throw new PrestoException(NOT_FOUND, format("Branch '%s' does not exist in table %S", branchName, table));
}
if (name.getTableType() == IcebergTableType.CHANGELOG) {
return Optional.ofNullable(SnapshotUtil.oldestAncestor(table)).map(Snapshot::snapshotId);
}
return tryGetCurrentSnapshot(table).map(Snapshot::snapshotId);View on GitHub (pinned to 55bb57d202)
Solutions
- Re-list the table's valid snapshots (SELECT snapshot_id FROM "table$snapshots") and use an existing snapshot ID
- If the old snapshot was expired, query the current table state without the @snapshotId / FOR VERSION AS OF clause
- Correct the snapshot ID if it was copied from another table or mistyped
- Increase snapshot expiration retention (expire_snapshots older_than) if historical snapshots must remain queryable
Example fix
// before SELECT * FROM catalog.db.t FOR VERSION AS OF 8745035778589976427; -- expired // after SELECT snapshot_id FROM "catalog.db.t$snapshots"; -- pick a valid ID SELECT * FROM catalog.db.t FOR VERSION AS OF 9126358462717461923;
Defensive patterns
Strategy: validation
Validate before calling
// Before issuing FOR VERSION AS OF, verify the snapshot exists
List<Long> valid = /* SELECT snapshot_id FROM "db.t$snapshots" */;
if (!valid.contains(requestedSnapshotId)) {
throw new IllegalArgumentException("Snapshot " + requestedSnapshotId + " not in t$snapshots");
} Type guard
boolean snapshotExists(Table table, long id) {
return table.snapshot(id) != null;
} Try / catch
try {
return resolveSnapshotIdByName(table, name);
} catch (PrestoException e) {
if (e.getErrorCode().getCode() == ICEBERG_INVALID_SNAPSHOT_ID.getCode()) {
// fall back to current snapshot
return Optional.of(table.currentSnapshot().snapshotId());
}
throw e;
} Prevention
- Resolve snapshot IDs from "table$snapshots" at runtime instead of hard-coding them
- Account for expire_snapshots retention: old IDs become invalid over time
- Never reuse snapshot IDs across different tables
- Pin historical query results by copying data, not by retaining fragile snapshot references
When it happens
Trigger: Calling resolveSnapshotIdByName with an IcebergTableName whose snapshotId is present but is not a known snapshot of the table — e.g. a hard-coded or stale snapshot ID from a previous metadata version, an ID from a different table, or one expired by snapshot expiration (expire_snapshots).
Common situations: Re-running a saved query with FOR VERSION AS OF after snapshots were expired by retention jobs; copying a snapshot ID from a dev table to a prod table; a long-running pipeline caching snapshot IDs that were garbage-collected by Iceberg's snapshot expiration.
Related errors
- ICEBERG_INVALID_SNAPSHOT_ID
- INVALID_MATERIALIZED_VIEW_PROPERTY
- NOT_SUPPORTED
- NOT_SUPPORTED
- SCHEMA_NOT_EMPTY
AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04).
Data as JSON: /api/errors/e6d23091cf9f46e7.
Report an issue: GitHub.