Node.js의 DynamoDB 조건부 쓰기 (AWS SDK v3)

AWS SDK v3에서 조건부 쓰기의 흥미로운 부분은 ConditionExpression이 아닙니다. 그것은 어디서나 동일하게 동작하며 DynamoDB 조건 표현식에서 다룹니다. 흥미로운 것은 실패 경로입니다. v3는 요청했을 경우 던져진 오류에 패배한 항목을 실어 주고, 요청하지 않았다면 아무것도 주지 않습니다.

코드

import {DynamoDBClient, UpdateItemCommand} from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({region: 'us-east-1'});

// Update the item only if nobody changed it since we read version 7.
const command = new UpdateItemCommand({
  TableName: 'Music',
  Key: {
    Artist: {S: 'Arturo Sandoval'},
    SongTitle: {S: 'Cubano Chant'}
  },
  UpdateExpression: 'SET #upd0 = :updValue0, #version = :newVersion',
  ConditionExpression: 'attribute_exists(#cond0) AND #version = :expectedVersion',
  ExpressionAttributeNames: {
    '#upd0': 'Genre',
    '#version': 'Version',
    '#cond0': 'Artist'
  },
  ExpressionAttributeValues: {
    ':updValue0': {S: 'Latin Jazz'},
    ':expectedVersion': {N: '7'},
    ':newVersion': {N: '8'}
  },
  ReturnValuesOnConditionCheckFailure: 'ALL_OLD'
});

try {
  await client.send(command);
  console.log('Updated to version 8');
} catch (err) {
  if (err.name === 'ConditionalCheckFailedException') {
    // With ReturnValuesOnConditionCheckFailure: 'ALL_OLD', the current item
    // rides back on the exception — no extra read to see what beat you.
    console.log('Lost the race — item is now:', err.Item);
  } else {
    throw err;
  }
}

설명

  • 조건 검사 실패는 상태 필드가 아니라 던져진 오류입니다. v3는 프로미스를 거부하므로, 쓰기 경로와 경합에서 진 경로는 서로 다른 분기가 됩니다. err.name === 'ConditionalCheckFailedException'이 판별자이고, 그 밖의 것은 반드시 다시 던져야 합니다. 코드의 else가 바로 그 역할입니다. catch에서 전부 삼켜 버리면 스로틀링을 조용히 아무 일도 하지 않은 것으로 바꿔 놓게 됩니다.
  • ReturnValuesOnConditionCheckFailure는 누가 이겼는지 볼 수 있는 유일한 방법입니다. 이것이 없으면 오류에는 메시지만 실려 오고, 결국 필요 없었을 GetItem으로 되돌아가게 됩니다. API 레퍼런스는 유효 값을 ALL_OLD | NONE으로 정하고 읽기 용량을 소비하지 않는다고 확인해 줍니다.
  • err.Item은 원시 AttributeValue 맵이며, 여러분이 보낸 Key와 같은 형태이지 일반 JavaScript 객체가 아닙니다. Version을 숫자와 비교하기 전에 @aws-sdk/util-dynamodbunmarshall을 거치세요. 그러지 않으면 {N: '9'}와 비교하게 됩니다.
  • 실패한 쓰기도 청구됩니다. 개발자 안내서는 표현식이 false로 평가되어도 쓰기 용량을 소비하며, 이전 항목과 새 항목 중 더 큰 쪽을 기준으로 산정한다고 명시합니다. 핫 키에 대한 재시도 루프는 청구서에 실제로 찍히는 항목이므로 시도 횟수에 상한을 두세요.
  • 코드의 모든 이름에 별칭이 붙어 있는데(#versionVersion, #cond0Artist) 이는 이 코드를 생성한 Expression Builder가 무조건 별칭을 붙이기 때문입니다. 여기서는 필요 이상으로 무겁지만 결코 틀리지 않으며, 그것이 이 도구가 택한 절충입니다.

예외에서 패자의 사본 읽어 내기

저장된 Version을 9로 두고, 7을 기대하는 위 코드를 실행하세요. DynamoDB Local 3.3.0은 예외를 던지고, 잡힌 오류에는 다음이 실려 있습니다:

err.name     ConditionalCheckFailedException
err.message  The conditional request failed
err.$metadata.httpStatusCode  400
err.Item     {
               Artist:     { S: 'Arturo Sandoval' },
               Year:       { N: '1994' },
               Version:    { N: '9' },
               SongTitle:  { S: 'Cubano Chant' },
               AlbumTitle: { S: 'Danzon' }
             }

Version: 9가 핵심입니다. 재시도는 :expectedVersion을 9로 설정해 곧바로 업데이트를 다시 태울 수 있고, 추가 읽기도 없고 여러분의 GetItem과 재시도 사이에 제3의 기록자가 끼어들 틈도 없습니다.

같은 명령에서 ReturnValuesOnConditionCheckFailure를 지우고 다시 실행해 보세요. 같은 name, 같은 message, 같은 400이 나오고 err.Itemundefined입니다. 아무것도 경고해 주지 않습니다. 이 파라미터는 선택 사항이라 없는 것이 오류가 아니고, err.Item을 읽는 코드는 그저 프로덕션에서 undefined를 로그로 남기기 시작합니다.

여기서의 400이 잘못된 형식의 요청을 뜻하지는 않는다는 점도 유의하세요. ValidationExceptionConditionalCheckFailedException은 상태 코드를 공유하지만 그중 버그인 것은 하나뿐이며, 그래서 분기는 상태 코드가 아니라 언제나 err.name을 기준으로 합니다.

여러분의 데이터에 대해 조건이 성공하고 실패하는 모습을, 표현식을 직접 타이핑하지 않고 대신 작성받아 지켜보려면 DynoTable을 다운로드하세요.

관련 예제

참고 자료

위에 링크된 공식 AWS 문서를 기준으로 2026-07-28에 마지막으로 검증했습니다.

Console 없이 DynamoDB 작업하기

DynamoDB로는 실행할 수 없는 진짜 SQL(JOINs, GROUP BY, 집계)을 실행하는 빠른 DynamoDB 데스크톱 클라이언트. 시각적 편집과 여러분 자신의 Bedrock 키로 동작하는 AI 에이전트를 제공합니다.

30일 무료 체험, 신용카드 불필요 — 이후 기간 제한 없는 무료 요금제.