dotnet/orleans · error · ArgumentOutOfRangeException

Too many rows for bulk delete - max

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.

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.

Appendix: 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)