1. 結論(この記事で得られること)
DynamoDBのUpdateItemで条件式を書くとき、「なぜかエラーになる」「思った条件で更新されない」という経験、ありませんか?
私も3年前、本番環境で条件式のミスによる予期しない更新を起こしかけて、コードレビューで先輩に止められた経験があります。
この記事では以下が得られます:
- 条件式(ConditionExpression)の正しい書き方と頻出エラーパターン
- UpdateExpressionとConditionExpressionの使い分け
- 属性が存在しない場合の安全な扱い方
- AIを使った高速デバッグ手法(有料部分)
- 本番運用での失敗パターンと対策(有料部分)
10分で読めて、明日から実務で使える内容に絞りました。
2. 前提(環境・読者層)
想定読者
- DynamoDBを触り始めて数ヶ月〜1年程度
- UpdateItemの基本は知っているが、条件式で詰まったことがある
- 本番環境でのミスを未然に防ぎたいエンジニア
動作環境
- AWS SDK for JavaScript v3(コード例はTypeScript)
- DynamoDB(リージョン・容量モードは問わず)
- Node.js 18以上推奨
boto3やJavaのSDKでも考え方は共通です。
3. Before:よくあるつまずきポイント
3-1. 「attribute_exists」と「attribute_not_exists」の罠
// ❌ NG:よくある間違い
await docClient.update({
TableName: 'Users',
Key: { userId: 'user123' },
UpdateExpression: 'SET #status = :newStatus',
ConditionExpression: 'attribute_exists(#status)', // 既存属性がある場合のみ更新
ExpressionAttributeNames: {
'#status': 'status'
},
ExpressionAttributeValues: {
':newStatus': 'active'
}
});
何がダメか?
- 「attribute_exists」は「その属性がitemに存在するか」をチェックする
- 初回登録時やステータス未設定のユーザーでは更新が失敗する
- 「ConditionalCheckFailedException」が投げられるが、原因がログからわかりにくい
私も昔、これで「特定ユーザーだけ更新できない」というバグを半日追いかけました。
3-2. 比較演算子での型不一致
// ❌ NG:数値比較のつもりが文字列比較になっている
ConditionExpression: '#version = :expectedVersion',
ExpressionAttributeValues: {
':expectedVersion': '5' // ← DynamoDB上はNumber型なのに文字列で指定
}
- エラーにはならないが、条件が常にfalseになり更新が通らない
- 型の不一致は実行時まで分からない(TypeScriptでも防げない)
3-3. 論理演算の優先順位ミス
// ❌ NG:意図しない評価順序
ConditionExpression: '#status = :active OR #status = :pending AND #retryCount < :maxRetry'
// ↑ ANDが先に評価されるため、意図と異なる動作になる
これも実際にレビューで指摘したケースです。括弧を使わないと事故ります。
4. After:基本的な解決パターン
4-1. 属性の存在チェックを正しく行う
// ✅ OK:属性がない場合はデフォルト値で作成、ある場合は条件付き更新
await docClient.update({
TableName: 'Users',
Key: { userId: 'user123' },
UpdateExpression: 'SET #status = :newStatus, #updatedAt = :now',
ConditionExpression: 'attribute_not_exists(#status) OR #status = :oldStatus',
ExpressionAttributeNames: {
'#status': 'status',
'#updatedAt': 'updatedAt'
},
ExpressionAttributeValues: {
':newStatus': 'active',
':oldStatus': 'pending',
':now': Date.now()
}
});
ポイント
- 「OR」で「新規作成」と「既存更新」の両方に対応
- 楽観ロック的な使い方(期待する古い値をチェック)
4-2. 型を明示的に統一する
// ✅ OK:数値型として明示
ExpressionAttributeValues: {
':expectedVersion': 5, // Numberとして扱われる
':maxRetry': 3
}
AWS SDK v3では型推論が効くが、念のため確認
import { marshall } from '@aws-sdk/util-dynamodb';
// 型が不安なら明示的に変換
const values = marshall({
':expectedVersion': 5
}, { removeUndefinedValues: true });
4-3. 論理演算は括弧で明示
// ✅ OK:意図通りの評価順序
ConditionExpression: '(#status = :active OR #status = :pending) AND #retryCount < :maxRetry'
ここは必ずレビューでチェックします。自分も見落としやすいので。
4-4. よく使う安全パターン集
// パターン1:楽観ロック(バージョン番号チェック)
ConditionExpression: '#version = :currentVersion',
UpdateExpression: 'SET #data = :newData, #version = #version + :inc',
ExpressionAttributeValues: {
':currentVersion': 5,
':newData': { /* ... */ },
':inc': 1
}
// パターン2:在庫減算(負にならないチェック)
ConditionExpression: '#stock >= :quantity',
UpdateExpression: 'SET #stock = #stock - :quantity'
// パターン3:初回のみ作成(既存なら何もしない)
ConditionExpression: 'attribute_not_exists(PK)'
特に在庫管理などの数値操作では、条件式が必須です。