apache/iceberg · warning
Delete failed for {} file: {}
Error message
Delete failed for {} file: {} What it means
In the per-file (non-bulk) delete path, Tasks retries each deletion 3 times (stopping on NotFoundException); if a file still fails after retries, this WARN 'Delete failed for {fileType} file: {file}' is logged per file. The operation continues with the remaining files; the failed file may become an orphan that a later cleanup run must remove.
Source
Thrown at core/src/main/java/org/apache/iceberg/FileCleanupStrategy.java:131
"Bulk deletion failed for {} of {} {} file(s)",
e.numberFailedObjects(),
pathsToDelete.size(),
fileType,
e);
} catch (RuntimeException e) {
LOG.warn("Bulk deletion failed", e);
}
} else {
Consumer<String> deleteFuncToUse = deleteFunc == null ? defaultDeleteFunc : deleteFunc;
Tasks.foreach(pathsToDelete)
.executeWith(deleteExecutorService)
.retry(3)
.stopRetryOn(NotFoundException.class)
.stopOnFailure()
.suppressFailureWhenFinished()
.onFailure(
(file, thrown) -> LOG.warn("Delete failed for {} file: {}", fileType, file, thrown))
.run(deleteFuncToUse::accept);
}
}
protected boolean hasAnyStatisticsFiles(TableMetadata tableMetadata) {
return !tableMetadata.statisticsFiles().isEmpty()
|| !tableMetadata.partitionStatisticsFiles().isEmpty();
}
protected Set<String> expiredStatisticsFilesLocations(
TableMetadata beforeExpiration, TableMetadata afterExpiration) {
Set<String> statsFileLocationsBeforeExpiration = statsFileLocations(beforeExpiration);
Set<String> statsFileLocationsAfterExpiration = statsFileLocations(afterExpiration);
return Sets.difference(statsFileLocationsBeforeExpiration, statsFileLocationsAfterExpiration);
}
private Set<String> statsFileLocations(TableMetadata tableMetadata) {View on GitHub (pinned to 86d9c8fc54)
Solutions
- Inspect the logged exception for the specific file to determine the storage-side cause (403 vs 5xx vs timeout).
- Re-run snapshot expiration later — file cleanup is idempotent and already-deleted files stop retrying via NotFoundException.
- Verify IAM/storage permissions for the exact prefix of failing files.
- Lower concurrency (deleteExecutorService) if deletions are being throttled.
Defensive patterns
Strategy: retry
Validate before calling
// verify FileIO can delete in the target prefix before large cleanups String probe = prefix + "/__probe_" + UUID.randomUUID(); io.deleteFile(probe);
Try / catch
try {
table.expireSnapshots().execute();
} catch (RuntimeException e) {
LOG.warn("Snapshot expiration finished with per-file delete warnings", e);
} Prevention
- Check per-file WARN causes for 403 vs throttling and fix IAM or rate limits accordingly.
- Re-run expiration — it is idempotent; already-deleted files stop via NotFoundException.
- Cap deleteExecutorService concurrency if the object store throttles.
- Rely on remove-orphan-files as a periodic safety net.
When it happens
Trigger: expireSnapshots/deleteFiles runs with a custom deleteFunc or default delete path, and deleting a single path throws (transient S3/GCS error, permission denied, throttling) on all 3 retries; NotFoundException short-circuits as the file is already gone.
Common situations: Object-store eventual consistency where the file was already removed by a concurrent expiration; IAM policies denying DeleteObject on some prefixes; network blips during large snapshot expiration.
Related errors
- Bulk deletion failed
- Failed to create file: %s
- Failed to delete: %s
- Failed to close encryption manager
- %s does not expose configuration properties
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/6bd8abe25001475c.
Report an issue: GitHub.