エラー内容
TransactionConflictException: Transaction is ongoing for the item (DynamoDB)
ユーザーの操作が約10回に1回の頻度で失敗し、このDynamoDBのエラーが発生していました。
原因
公式ドキュメント(Botocore) で、以下のような説明がありました。
A transactional conflict can occur during concurrent item-level requests on an item within a transaction. Transaction conflicts can occur in the following scenarios:
(トランザクション競合は、トランザクション内の項目に対する項目レベルの同時リクエスト中に発生する場合があります。トランザクション競合は、次のシナリオで発生する場合があります。)
つまり「同じアイテムに対して、同時にトランザクション処理が実行されるとバッティングしてエラーが発生する」ということでした。
実際にフロントエンド側で以下のように Promise.all によって2つのトランザクションを同時に実行していました。
await Promise.all([
updateItemTransaction1({
body: {
itemId: itemId
}
}),
updateItemTransaction2({
body: {
itemId: itemId
}
}),
])
このため、非同期で実行される2つの処理が、たまたま同じタイミングでDynamoDBの同じアイテムを更新しようとして、バッティングが発生していました。
そのため、毎回エラーになるわけではなく、発生頻度が低かったのはこのタイミングの問題です。
対応
以下のように、2つの処理を順番に実行するよう変更することで、トランザクションの競合を回避できました。
await updateItemTransaction1({
body: {
itemId: itemId
}
})
await updateItemTransaction2({
body: {
itemId: itemId
}
}),
逐次実行にすることで、トランザクションが競合することなく安定して処理が完了するようになりました。
補足
リトライ処理を検討してもよいかもしれません。
TransactionConflictException は一時的なエラーのため、一定の間隔で再試行すれば成功することもあるようです。