dotnet/orleans · error · ArgumentOutOfRangeException

Too many rows for bulk delete - max {this.StoragePolicyOptio

Error message

Too many rows for bulk delete - max {this.StoragePolicyOptions.MaxBulkUpdateRows}

What it means

Thrown by AzureTableDataManager.DeleteTableEntriesAsync when the collection size exceeds StoragePolicyOptions.MaxBulkUpdateRows (default 100). Azure Table entity-group transactions are limited to 100 entities and must share a partition, so Orleans caps the batch and rejects larger inputs with an ArgumentOutOfRangeException carrying the offending count.

Source

Thrown at src/Azure/Shared/Storage/AzureTableDataManager.cs:473

        }

        /// <summary>
        /// Deletes a set of already existing data entries in the table, by using eTag.
        /// Fails if the data does not already exist or if eTag does not match.
        /// </summary>
        /// <param name="collection">Data entries and their corresponding etags to be deleted from the table.</param>
        /// <returns>Completion promise for this storage operation.</returns>
        public async Task DeleteTableEntriesAsync(List<(T Entity, string ETag)> collection)
        {
            const string operation = "DeleteTableEntries";
            var startTime = DateTime.UtcNow;
            LogTraceTableEntries(Logger, operation, new(collection), TableName);

            if (collection == null) throw new ArgumentNullException(nameof(collection));

            if (collection.Count > this.StoragePolicyOptions.MaxBulkUpdateRows)
            {
                throw new ArgumentOutOfRangeException(nameof(collection), collection.Count,
                        "Too many rows for bulk delete - max " + this.StoragePolicyOptions.MaxBulkUpdateRows);
            }

            if (collection.Count == 0)
            {
                return;
            }

            try
            {
                var entityBatch = new List<TableTransactionAction>();
                foreach (var tuple in collection)
                {
                    T item = tuple.Entity;
                    item.ETag = new ETag(tuple.ETag);
                    entityBatch.Add(new TableTransactionAction(TableTransactionActionType.Delete, item, item.ETag));
                }

View on GitHub (pinned to fca799fa70)

Solutions

  1. Chunk the collection into batches of at most MaxBulkUpdateRows before calling DeleteTableEntriesAsync.
  2. If appropriate for your workload, raise MaxBulkUpdateRows — but never above 100 (the Azure Table transaction limit).
  3. Ensure all entities in a batch share the same PartitionKey (Azure entity-group transaction requirement).

Example fix

// before
await manager.DeleteTableEntriesAsync(allEntries); // allEntries.Count > 100 -> throws
// after
const int BatchSize = 100; // <= options.StoragePolicyOptions.MaxBulkUpdateRows
foreach (var batch in allEntries.Chunk(BatchSize).Select(c => c.ToList()))
{
    await manager.DeleteTableEntriesAsync(batch);
}
Defensive patterns

Strategy: validation

Validate before calling

int cap = Math.Min(100, options.StoragePolicyOptions.MaxBulkUpdateRows);
foreach (var batch in collection.Chunk(cap).Select(c => c.ToList()))
    await manager.DeleteTableEntriesAsync(batch);

Type guard

static bool WithinLimit(int count, int max) => count <= max;

Try / catch

catch (ArgumentOutOfRangeException ex) when (ex.Message.Contains("Too many rows for bulk delete")) { /* chunk into batches <= MaxBulkUpdateRows, same partition */ }

Prevention

When it happens

Trigger: Passing more than MaxBulkUpdateRows entries to DeleteTableEntriesAsync in one call; raising the number of deletables without chunking; lowering MaxBulkUpdateRows below an existing batch size.

Common situations: Bulk grain-state cleanup or reminder purge that gathers many rows; large partition scans producing big delete sets; tuning MaxBulkUpdateRows for other operations.

Related errors


AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13). Data as JSON: /api/errors/41c3d3bbef32ed48. Report an issue: GitHub.