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
- Chunk the collection into batches of at most MaxBulkUpdateRows before calling DeleteTableEntriesAsync.
- If appropriate for your workload, raise MaxBulkUpdateRows — but never above 100 (the Azure Table transaction limit).
- 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
- Always chunk bulk deletes to <= MaxBulkUpdateRows (<=100).
- Ensure each batch shares one PartitionKey.
- Adjust MaxBulkUpdateRows only within Azure's 100-entity limit.
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
- Value cannot be null. (Parameter 'collection')
- Value cannot be null. (Parameter 'createClientCallback')
- Value cannot be null. (Parameter 'connectionString')
- Value cannot be null. (Parameter 'serviceUri')
- Value cannot be null. (Parameter 'tokenCredential')
AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13).
Data as JSON: /api/errors/41c3d3bbef32ed48.
Report an issue: GitHub.