DynamoDB ConditionalCheckFailedException
TL;DR — 書き込みが持つ ConditionExpression が、現在のアイテムに対して false と評価されたため、DynamoDB は書き込みを拒否し、アイテムをそのまま残しました。これは通常 想定内 です(オプティミスティック並行性、「存在しない場合のみ作成」)。キャッチして分岐させ、やみくもに再試行しないでください。
意味
ValidationException とは異なり、リクエストは正しい形式でした。DynamoDB は条件を評価し、それが成立しなかったため、PutItem / UpdateItem / DeleteItem(または TransactWriteItems 内の単一アイテム)が拒否されました。データは変更されていません。 HTTP 400 を返し、そのままでは リトライ不可 です。
発生する理由
- 作成時の
attribute_not_exists(pk)ガード — アイテムがすでに存在する(重複挿入)。 - 更新/削除時の
attribute_exists(pk)ガード — アイテムが消えている。 - オプティミスティック並行性 —
version = :expected(またはupdatedAt)チェックで、別のライターが先に到達した。 - ビジネスルールのガード —
balance >= :amount、#status = :expectedが、保存されたアイテムともう一致しない。
修正方法
- 障害ではなく通常の結果として扱います。 例外をキャッチし、失敗した条件がフローの中で何を 意味する かを判断します(アイテムがすでに存在 → それを返す。バージョンが古い → 再読み取りして新しいバージョンで再試行)。
- 現在のアイテムを読み戻します。
ReturnValuesOnConditionCheckFailure: 'ALL_OLD'を設定すると、2度目のラウンドトリップなしで失敗の原因となったアイテムを取得できます。それは例外自体(Itemフィールド)で返ってきて、読み取りキャパシティは消費されません。 - 並行性のために再読み取り + 再計算し、新鮮なバージョンで再試行します。同じ expected 値をただ再送しないでください。
その再読み取りして比較するループこそ、DynoTable のステージングエリアが手作業の編集に対して行うことです — 書き込みをステージングし、オプティミスティックロックの競合時には、変更の隣に現在のアイテムを表示するので、何かが送信される前に解決できます。
例
import {DynamoDBClient} from '@aws-sdk/client-dynamodb';
import {DynamoDBDocumentClient, PutCommand} from '@aws-sdk/lib-dynamodb';
import {ConditionalCheckFailedException} from '@aws-sdk/client-dynamodb';
const doc = DynamoDBDocumentClient.from(new DynamoDBClient({}));
try {
await doc.send(
new PutCommand({
TableName: 'Users',
Item: {pk: 'USER#1', email: 'a@b.com'},
ConditionExpression: 'attribute_not_exists(pk)' // create-only
})
);
} catch (err) {
if (err instanceof ConditionalCheckFailedException) {
// Expected: the user already exists. Handle gracefully.
return {alreadyExists: true};
}
throw err;
}よくある質問
DynamoDB で ConditionalCheckFailedException は何が原因ですか? 書き込み(PutItem、UpdateItem、DeleteItem、または TransactWrite アイテム)が持つ ConditionExpression が、現在のアイテムに対して false と評価されました。たとえば、すでに存在するキーに対する attribute_not_exists(pk) や、もう一致しないバージョンチェックなどです。DynamoDB は書き込みを拒否し、アイテムを変更しません。
ConditionalCheckFailedException でアプリがクラッシュするのを止めるには? 例外をキャッチし、障害ではなく想定内の結果として扱います。失敗した条件は通常「誰かが先に到達した」(オプティミスティック並行性)か「アイテムがすでに存在する」を意味します。やみくもに再試行するのではなく、それに応じて分岐してください。
再現方法
すでに存在するキーに対して attribute_not_exists でガードした PutItem です。
await client.send(
new PutItemCommand({
TableName: 'orders',
Item: {pk: {S: 'ORDER#1'}, sk: {S: 'META'}},
ConditionExpression: 'attribute_not_exists(pk)'
})
);実際の出力:
ConditionalCheckFailedException: The conditional request failed
HTTP 400このメッセージは意図的に情報量がありません — 条件のどの部分が失敗したのかも、アイテムが実際に何を保持していたのかも決して教えてくれません。ReturnValuesOnConditionCheckFailure: "ALL_OLD" を渡せば現在のアイテムが error.Item で返ってくるので、推測が差分に変わります。
関連するエラー
- TransactionCanceledException — トランザクション内での 条件失敗。
- ValidationException (overview)
- Code example: Conditional write in Node.js · in Python (boto3) — 実行可能な ConditionExpression パターン。
- 学習: 条件式 · アトミックカウンター
参考資料
- Error handling with DynamoDB — Amazon DynamoDB Developer Guide
- PutItem — Amazon DynamoDB API Reference
- DynamoDB condition expression examples — Amazon DynamoDB Developer Guide
最終検証日 2026-07-13、上記にリンクした公式 AWS ドキュメントに照らして確認しました。
2026-07-26 に DynamoDB Local 2.x と AWS SDK for JavaScript v3.1095.0 で再現しました — 上記の出力はそのままの逐語です。