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

  1. Remove max_concurrent_deletes from the procedure call — it has no effect here.
  2. Tune bulk-delete parallelism via the FileIO's own properties (e.g. S3FileIO delete batching/threads in its documented configuration).
  3. 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

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


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/e31a87cf8bd263a8. Report an issue: GitHub.