DynamoDB ConditionalCheckFailedException
요약 — 쓰기가 현재 항목에 대해 false로 평가되는 ConditionExpression을 지녔으므로 DynamoDB가 쓰기를 거부하고 항목을 그대로 두었습니다. 이는 보통 예상된 동작입니다(낙관적 동시성, "존재하지 않으면 생성"). 이를 catch해 분기하세요. 무작정 재시도하지 마세요.
무엇을 의미하는가
ValidationException과 달리 요청은 올바른 형식이었습니다. DynamoDB가 조건을 평가했는데 성립하지 않아 PutItem / UpdateItem / DeleteItem(또는 TransactWriteItems 내부의 단일 항목)이 거부되었습니다. 데이터는 변경되지 않았습니다. HTTP 400을 반환하며 그대로는 재시도할 수 없습니다.
왜 발생하는가
- 생성 시
attribute_not_exists(pk)가드 — 항목이 이미 존재합니다(중복 삽입). - 업데이트/삭제 시
attribute_exists(pk)가드 — 항목이 사라졌습니다. - 낙관적 동시성 — 다른 작성자가 먼저 도달한
version = :expected(또는updatedAt) 확인. - 비즈니스 규칙 가드 — 저장된 항목과 더 이상 일치하지 않는
balance >= :amount,#status = :expected.
어떻게 해결하는가
- 결함이 아니라 정상적인 결과로 처리하세요. 예외를 catch하고 실패한 조건이 흐름에서 무엇을 의미하는지 결정하세요(항목이 이미 존재 → 반환; 버전이 오래됨 → 다시 읽고 새 버전으로 재시도).
- 현재 항목을 다시 읽으세요.
ReturnValuesOnConditionCheckFailure: 'ALL_OLD'를 설정하면 두 번째 왕복 없이 실패를 일으킨 항목을 얻을 수 있습니다 — 예외 자체(Item필드)에 함께 반환되며 읽기 용량이 소비되지 않습니다. - 동시성을 위해 다시 읽고 재계산한 다음 새 버전으로 다시 시도하세요 — 같은 예상값을 그냥 재전송하지 마세요.
이 다시-읽고-비교하는 루프는 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;
}FAQ
DynamoDB에서 ConditionalCheckFailedException은 무엇 때문에 발생하나요? 쓰기(PutItem, UpdateItem, DeleteItem 또는 TransactWrite 항목)가 현재 항목에 대해 false로 평가되는 ConditionExpression을 지닌 경우입니다 — 예를 들어 이미 존재하는 키에 attribute_not_exists(pk)를 걸었거나, 더 이상 일치하지 않는 버전 확인이 있는 경우입니다. DynamoDB는 쓰기를 거부하고 항목을 변경하지 않습니다.
ConditionalCheckFailedException이 앱을 중단시키지 않게 하려면 어떻게 하나요? 예외를 catch하고 결함이 아니라 예상된 결과로 처리하세요. 조건 실패는 보통 "다른 누군가가 먼저 도달했다"(낙관적 동시성)거나 "항목이 이미 존재한다"는 뜻입니다 — 무작정 재시도하지 말고 그에 따라 분기하세요.
재현하기
이미 존재하는 키에 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으로 돌아오며, 추측이 diff로 바뀝니다.
관련 오류
- TransactionCanceledException — 트랜잭션 내부 에서 실패한 조건.
- ValidationException(개요)
- 코드 예제: Node.js의 조건부 쓰기 · Python(boto3) — 실행 가능한 ConditionExpression 패턴.
- 학습: 조건 표현식 · 원자적 카운터
참고 자료
- Error handling with DynamoDB — Amazon DynamoDB Developer Guide
- PutItem — Amazon DynamoDB API Reference
- DynamoDB condition expression examples — Amazon DynamoDB Developer Guide
위에 링크된 공식 AWS 문서를 기준으로 2026-07-13에 마지막으로 검증했습니다.
2026-07-26에 DynamoDB Local 2.x와 AWS SDK for JavaScript v3.1095.0으로 재현했습니다 — 위 출력은 그대로 옮긴 것입니다.