Python(boto3)의 DynamoDB UpdateItem

이 호출에서 boto3는 클라이언트를 둘 주는데, 둘은 숫자가 무엇인지에 대해 의견이 다릅니다. 아래의 저수준 client는 DynamoDB JSON을 주고받으며, 거기서 모든 숫자는 따옴표에 싸인 문자열입니다. resource("dynamodb").Table(...)은 네이티브 Python 객체를 받고 float은 대놓고 거부하며, 숫자를 decimal.Decimal로 돌려줍니다. 이 페이지에서 진짜 내려야 할 결정은 둘 중 어느 쪽을 고르느냐입니다.

코드

import boto3

client = boto3.client("dynamodb")

response = client.update_item(
    TableName="Music",
    Key={"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
    UpdateExpression="SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2",
    ExpressionAttributeNames={"#upd0": "Genre", "#upd1": "Year", "#upd2": "Awards"},
    ExpressionAttributeValues={":updValue0": {"S": "Latin Jazz"}, ":updValue1": {"N": "1994"}, ":updValue2": {"N": "1"}},
    ReturnValues="ALL_NEW",
)

print(response["Attributes"])  # the item after the update

설명

  • 절 문법은 boto3의 소관이 아닙니다. UpdateExpression은 boto3가 그대로 전달하는 불투명한 문자열이며, 오직 DynamoDB만이 이를 파싱하므로 실수의 대가는 왕복 한 번입니다. 여기서 ADD는 읽기-수정-쓰기 경합을 없애는 원자적 증가이고, ConditionExpressionattribute_exists(Artist)를 넣으면 upsert가 업데이트 전용이 되며, 나머지는 업데이트 표현식에 있습니다.
  • 응답의 최상위 키는 정확히 둘입니다: AttributesResponseMetadata. 확인할 상태 필드도, 행 개수도 없습니다. 호출이 반환되었다면 성공한 것입니다. ResponseMetadata에는 로그 한 줄에 넣고 싶은 RequestIdHTTPStatusCode가 담깁니다.
  • ReturnValues="UPDATED_NEW"가 알뜰한 선택지입니다. 표현식이 건드린 속성만 반환하므로, 큰 항목에서는 카운터 하나만 읽는 것과 레코드 전체를 되돌려 보내는 것의 차이가 됩니다.
  • 오류는 botocore.exceptions.ClientError로 도착하며, e.response["Error"]["Code"]로 분기합니다. 별칭을 빠뜨리면 Invalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: Year 메시지와 함께 ValidationException이 발생합니다. 타입이 지정된 하위 클래스도 존재하지만 botocore가 클라이언트 인스턴스에 생성해 주는 속성(client.exceptions.ConditionalCheckFailedException)으로만 있고 임포트 가능한 심벌로는 결코 없으므로, 클라이언트가 스코프에 없는 헬퍼 함수는 코드 문자열을 써야 합니다.

Decimal이냐 DynamoDB JSON이냐, 하나를 고르세요

리소스 API는 요청이 만들어지기도 전에 float을 거부하며, 무엇을 원하는지 정확히 알려 주는 메시지를 냅니다:

TypeError: Float types are not supported. Use Decimal types instead.

이것은 DynamoDB가 아니라 boto3 자체의 타입 검사입니다. 리소스 API로 Decimal("4.5")를 저장한 뒤 같은 속성을 두 클라이언트로 각각 읽어 보면 이렇게 나옵니다:

resource Rating: Decimal('4.5') Awards: Decimal('2')
client Rating: {'N': '4.5'} Awards: {'N': '2'}

어느 쪽도 틀리지 않았습니다. 서로 다른 계약일 뿐입니다. Decimal은 DynamoDB가 실제로 저장하는 정밀도를 지키고 산술을 의식하게 만들지만, int를 기대한 코드에 Decimal("1") * 2가 등장하는 대가가 따릅니다. 저수준 클라이언트는 문자열을 건네고 파싱은 여러분에게 맡기며, 위 코드가 하는 것이 바로 그것입니다.

여기서 따라 나오는 규칙은 이렇습니다. 한 코드 경로에서 둘을 섞지 마세요. Table.put_item으로 쓰고 client.get_item으로 읽은 항목은 다른 형태로 돌아오며, 버그는 덜 테스트한 쪽 분기에서 드러납니다.

TTL 속성에 대한 참고

Python 코드베이스에서 가장 흔한 숫자 SET은 TTL입니다. Unix epoch를 담은 SET expires_at = :t이지요. DynamoDB는 그 속성을 단위로 읽습니다. 대신 int(time.time() * 1000)을 쓰면 값이 1785269450912가 되는데, 초로 해석하면 58542년에 떨어지므로 항목은 절대 삭제되지 않고 아무도 불평하지 않습니다. DynamoDB TTL 변환기는 epoch를 두 단위 모두로 되읽어 어느 쪽을 썼는지 알려 줍니다. 그다음 실제 테이블에서 저장된 값을 읽어 보려면 DynoTable을 다운로드하세요.

관련 가이드

참고 자료

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

Console 없이 DynamoDB 작업하기

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

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