apache/iceberg · info
max_concurrent_deletes only works with FileIOs that do not s
Error message
max_concurrent_deletes only works with FileIOs that do not support bulk deletes. This table is currently using {} which supports bulk deletes so the parameter will be ignored. See that IO's documentation to learn how to adjust parallelism for that particular IO's bulk delete. What it means
WARN from ExpireSnapshotsProcedure.call: the caller passed max_concurrent_deletes, but the table's FileIO implements SupportsBulkOperations, which manages its own bulk-delete parallelism. The option only tunes Tasks.foreach concurrency for non-bulk IOs, so it is ignored.
Source
Thrown at spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/procedures/ExpireSnapshotsProcedure.java:150
"max_concurrent_deletes should have value > 0, value: %s",
maxConcurrentDeletes);
return modifyIcebergTable(
tableIdent,
table -> {
ExpireSnapshots action = actions().expireSnapshots(table);
if (olderThanMillis != null) {
action.expireOlderThan(olderThanMillis);
}
if (retainLastNum != null) {
action.retainLast(retainLastNum);
}
if (maxConcurrentDeletes != null) {
if (table.io() instanceof SupportsBulkOperations) {
LOG.warn(
"max_concurrent_deletes only works with FileIOs that do not support bulk deletes. This "
+ "table is currently using {} which supports bulk deletes so the parameter will be ignored. "
+ "See that IO's documentation to learn how to adjust parallelism for that particular "
+ "IO's bulk delete.",
table.io().getClass().getName());
} else {
action.executeDeleteWith(executorService(maxConcurrentDeletes, "expire-snapshots"));
}
}
if (snapshotIds != null) {
for (long snapshotId : snapshotIds) {
action.expireSnapshotId(snapshotId);
}
}
if (streamResult != null) {View on GitHub (pinned to 86d9c8fc54)
Solutions
- Remove max_concurrent_deletes from the procedure call — it has no effect here.
- Tune bulk-delete parallelism via the FileIO's own properties (e.g. S3FileIO delete batching/threads in its documented configuration).
- Switch the table to a non-bulk FileIO only if you genuinely need the executor-based delete concurrency.
Example fix
// before CALL iceberg.system.expire_snapshots(table => 'db.t', max_concurrent_deletes => 20); // after: bulk IO handles parallelism itself CALL iceberg.system.expire_snapshots(table => 'db.t');
Defensive patterns
Strategy: validation
Validate before calling
boolean bulk = spark.table(tableName).io() instanceof org.apache.iceberg.io.SupportsBulkOperations; if (!bulk) { /* then max_concurrent_deletes is meaningful */ } Type guard
if (table.io() instanceof SupportsBulkOperations) { /* skip max_concurrent_deletes */ } Prevention
- Check whether the table's FileIO supports bulk deletes before tuning delete concurrency
- Use the bulk IO's own properties for delete parallelism
- Drop the parameter when migrating tables to S3FileIO
When it happens
Trigger: CALL iceberg.system.expire_snapshots(max_concurrent_deletes => N) on a table whose io() is e.g. S3FileIO/HadoopFileIO implementing SupportsBulkOperations.
Common situations: Copy-pasted expire_snapshots tuning from S3a/HadoopFileIO setups onto S3FileIO tables; users unaware bulk deletes (S3 DeleteObjects) control their own concurrency via the IO's properties.
Related errors
- Ignoring provided staging location as new manifests will be
- max_concurrent_deletes only works with FileIOs that do not s
- max_concurrent_deletes only works with FileIOs that do not s
- Procedure ${ident} not found
- Couldn't load table '%s' in catalog '%s'
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/e31a87cf8bd263a8.
Report an issue: GitHub.