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

  1. Re-list the table's valid snapshots (SELECT snapshot_id FROM "table$snapshots") and use an existing snapshot ID
  2. If the old snapshot was expired, query the current table state without the @snapshotId / FOR VERSION AS OF clause
  3. Correct the snapshot ID if it was copied from another table or mistyped
  4. 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

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


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/e6d23091cf9f46e7. Report an issue: GitHub.