{"record":{"id":"c23d8cd2e9e8b34a","repo":"dotnet/orleans","slug":"dynamodb-transactional-state-storage-conflict-par","errorCode":null,"errorMessage":"DynamoDB transactional state storage conflict. PartitionKey={key.PartitionKey}. {operations}","messagePattern":"DynamoDB transactional state storage conflict\\. PartitionKey=(.+?)\\. (.+?)","errorType":"exception","errorClass":"InconsistentStateException","httpStatus":null,"severity":"error","filePath":"src/AWS/Orleans.Transactions.DynamoDB/TransactionalState/DynamoDBTransactionalStateStorage.cs","lineNumber":591,"sourceCode":"                {\n                    for (var i = 0; i < this.operations.Count; i++)\n                    {\n                        var operation = this.operations[i];\n                        LogTraceBatchOpOk(logger, operation.PartitionKey, operation.RowKey, i);\n                    }\n\n                    LogTraceBatchOpOk(logger, key.PartitionKey, key.RowKey, this.operations.Count);\n                }\n\n                this.operations.Clear();\n                this.operationsSize = 0;\n            }\n            catch (TransactionCanceledException exception) when (IsStorageConflict(exception))\n            {\n                LogFailures();\n                var conflictMessage = GetConflictMessage(exception, keyPut);\n                LogWarningTransactionalStateConflict(logger, conflictMessage);\n                throw new InconsistentStateException(\n                    conflictMessage,\n                    storedEtag: \"Unknown\",\n                    currentEtag: currentETag?.ToString() ?? \"null\",\n                    exception);\n            }\n            catch\n            {\n                LogFailures();\n                throw;\n            }\n        }\n\n        private void LogFailures()\n        {\n            if (!logger.IsEnabled(LogLevel.Trace))\n            {\n                return;\n            }","sourceCodeStart":573,"sourceCodeEnd":609,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/src/AWS/Orleans.Transactions.DynamoDB/TransactionalState/DynamoDBTransactionalStateStorage.cs#L573-L609","documentation":"Thrown by BatchOperation.FlushCore when DynamoDB's TransactWriteItemsAsync returns a TransactionCanceledException whose CancellationReasons contain a 'ConditionalCheckFailed' (detected by IsStorageConflict). The library translates this into an InconsistentStateException with a detailed conflict message built by GetConflictMessage listing every operation's role, row key, condition, expected ETag and cancellation reason. It signals that the optimistic ETag precondition on the key row (or a state row) no longer held — another writer committed first.","triggerScenarios":"Produced during a transaction Confirm/Prepare when the keyPut.ConditionExpression 'ETag = :currentETag' fails because key.ETag advanced since Load(), or a state row condition failed. Triggered by two activations/silos writing the same transactional grain concurrently, or by a stale in-memory ETag after a previous failed flush.","commonSituations":"Concurrent writers on the same transactional grain across silos; a grain that did not reload after a prior InconsistentStateException; race between activation migration and an in-flight transaction; throttling delaying one writer so another wins.","solutions":["Let the Orleans transaction runtime retry — InconsistentStateException is the expected conflict signal and the framework re-drives the transaction after reloading state.","Ensure only one activation owns the grain at a time (deactivation/membership tuning) to remove competing writers.","If it persists, lower write contention on the grain (partition it) and add caller-side backoff.","Inspect the CancellationReasonCode/Message in the exception text to confirm it is 'ConditionalCheckFailed' and not another (e.g., throttling) reason."],"exampleFix":"// InconsistentStateException is expected under contention; let the tx runtime retry:\n// before\ntry { await grain.DoTx(); } catch (InconsistentStateException) { /* give up */ }\n\n// after: rely on the transaction framework's built-in retry, or wrap with a retry policy\nawait Policy\n  .Handle<InconsistentStateException>()\n  .WaitAndRetryAsync(3, i => TimeSpan.FromMilliseconds(50 * i))\n  .ExecuteAsync(() => grain.DoTx());","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Conditional-check conflict is the normal optimistic-concurrency signal; retry after reload\ntry { await grain.TxUpdate(); }\ncatch (InconsistentStateException ex)\n{\n    _logger.LogInformation(\"Transactional ETag conflict (stored={Stored}, current={Current}); will retry\",\n        ex.StoredEtag, ex.CurrentEtag);\n    throw; // let the transaction framework reload + retry, or wrap in a retry policy\n}","preventionTips":["Do not assume a transaction will succeed on the first try under contention; design for retry.","Ensure a single owning activation per grain to reduce competing writers.","Inspect CancellationReasonCode in the message to confirm it is 'ConditionalCheckFailed' (not throttling).","Apply caller-side backoff/jitter to avoid thundering-herd retries."],"tags":["dynamodb","transactions","concurrency","etag-conflict","conditional-check"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}