Python(boto3)의 DynamoDB TransactWriteItems
트랜잭션은 boto3의 두 API가 가장 크게 갈라지는 지점 중 하나입니다. transact_write_items는 저수준 클라이언트에만 존재하므로, Table에서 얻던 네이티브 Python 타입의 편의는 여기서 쓸 수 없습니다. 그리고 트랜잭션이 실패했을 때 필요한 것은 대부분의 boto3 코드가 들여다보지 않는 예외의 한구석에 있습니다. (트랜잭션이 무엇을 사 주는지는 어느 SDK에서나 같습니다.)
코드
import boto3
client = boto3.client("dynamodb")
# Move one award between two songs — atomically. If the first song has no
# award to give, NEITHER update happens.
try:
client.transact_write_items(
TransactItems=[
{
"Update": {
"TableName": "Music",
"Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
"UpdateExpression": "SET #upd0 = #upd0 - :one",
"ConditionExpression": "#upd0 >= :one",
"ExpressionAttributeNames": {"#upd0": "Awards"},
"ExpressionAttributeValues": {":one": {"N": "1"}},
}
},
{
"Update": {
"TableName": "Music",
"Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "A Mis Abuelos"}},
"UpdateExpression": "SET #upd0 = if_not_exists(#upd0, :zero) + :one",
"ExpressionAttributeNames": {"#upd0": "Awards"},
"ExpressionAttributeValues": {":one": {"N": "1"}, ":zero": {"N": "0"}},
}
},
]
)
print("Transaction committed")
except client.exceptions.TransactionCanceledException as e:
# One reason per action, in TransactItems order. Code "None" means that
# action was fine — some OTHER action sank the transaction.
codes = [reason["Code"] for reason in e.response["CancellationReasons"]]
print(f"Transaction canceled: {codes}") # e.g. ['ConditionalCheckFailed', 'None']설명
TransactItems—Put,Update,Delete,ConditionCheck딕셔너리의 목록이며, 모든 값이 예외 없이 DynamoDB JSON입니다. 타입이 지정된 형태가 선택 사항이 아닌 유일한 boto3 호출이고, 이 페이지 끝의 절이 존재하는 이유이기도 합니다. 상한은 CLI 페이지에 있습니다.CancellationReasons는Error안에 있지 않습니다. botocore는 모델링된 오류 필드를 응답 딕셔너리의 최상위로 끌어올리므로, 잡힌 예외의e.response에는CancellationReasons,Error,Message,ResponseMetadata가 나란히 담깁니다.e.response["Error"]아래에서 찾으면 아무것도 없으며,e.response["Error"]에는 요약 코드와 메시지만 들어 있습니다.None항목에는"Message"가 없습니다 — 성공한 작업의 사유는 키가 하나뿐인 딕셔너리{"Code": "None"}이므로, 자연스러워 보이는[r["Message"] for r in reasons]는 하필 잘 동작한 작업에서KeyError: 'Message'를 일으킵니다.r.get("Message")를 쓰세요.- 생성된 예외 클래스 — botocore는
client.exceptions.TransactionCanceledException을 런타임에 서비스 모델로부터 만들어 냅니다. 그래서 이 클래스가 클라이언트 인스턴스에 달려 있고,from botocore.exceptions import ...로 가져올 수 없습니다. 클라이언트가 스코프에 없는 헬퍼에서는botocore.exceptions.ClientError를 잡고e.response["Error"]["Code"]로 분기하세요. 생성된 클래스는 그 하위 클래스입니다. - 구조적 실수는 취소로 도착하지 않으므로, 위 코드의
except절은 그것들을 결코 보지 못합니다. 같은 항목을 겨냥한 작업 두 개는 코드가ValidationException인 맨ClientError를 일으키며, 그e.response에는CancellationReasons키가 없습니다. 어떤 작업도 실행되기 전에 트랜잭션이 거부되었기 때문입니다. 그런 것들도 같은 맥락으로 로깅하고 싶다면 바깥쪽에서ClientError를 잡으세요. - 작업에 붙인
ReturnValuesOnConditionCheckFailure: "ALL_OLD"는 밀려난 항목을 해당 작업 사유의Item키 아래에 DynamoDB JSON으로 넣어 주므로, 이미 경합에서 진 뒤에 다시get_item을 부를 필요가 없습니다. - boto3가
ClientRequestToken을 대신 채웁니다. 와이어에서 캡처해 보니, 동일한transact_write_items호출 두 번은 서로 다른 UUID를 달고 나갔습니다. 즉 이 토큰은 단일 호출을 덮을 뿐 직접 만든 catch-후-재시도 루프까지 덮지는 않습니다. 재시도가 프로세스보다 오래 살 수 있다면 안정적인 토큰을 직접 넘기세요. TransactionConflict에는 재시도하고ConditionalCheckFailed에는 절대 재시도하지 마세요 — 앞은 다른 누군가가 잠깐 항목을 붙들고 있었다는 뜻이고, 뒤는 전제 조건이 거짓이며 다음번에도 거짓일 것이라는 뜻입니다. 대부분의 핸들러가 갈라 두어야 할 코드는 이 둘뿐이며, 전체 집합은 TransactionCanceledException 페이지에서 해독합니다.- 비용 — 트랜잭션 쓰기는 같은 쓰기를 트랜잭션 밖에서 할 때의 약 두 배로 과금되며, 이는 CLI 페이지에서 측정했습니다. 항목 하나의 원자성만 필요하다면 조건부 쓰기가 절반 값에 그것을 사 줍니다.
이 호출의 리소스 API 버전은 없습니다
boto3.resource("dynamodb").Table(...)에는 transact_write_items 속성이 없습니다. resource.meta.client에만 있습니다. 그래서 Table과 네이티브 Python 타입으로 굳어진 코드베이스는 트랜잭션에서는 타입이 지정된 DynamoDB JSON으로 돌아가거나, boto3.dynamodb.types.TypeSerializer로 직접 직렬화해야 합니다:
from boto3.dynamodb.types import TypeSerializer
serialize = TypeSerializer().serialize
values = {k: serialize(v) for k, v in {":one": 1, ":zero": 0}.items()}TypeSerializer는 리소스 API와 같은 규칙을 적용하므로, float을 거부하고 소수에는 decimal.Decimal을 요구합니다. 스크립트에 리터럴만 붙여 넣으면 될 때는 DynamoDB JSON 변환기가 브라우저에서 같은 변환을 해 줍니다. 두 형태 중 어느 것도 손으로 쓰지 않고 트랜잭션이 건드리는 항목을 편집하려면 DynoTable을 다운로드하세요.
관련 예제
- Node.js의 DynamoDB TransactWriteItems — AWS SDK v3로 하는 같은 트랜잭션.
- AWS CLI를 사용한 DynamoDB TransactWriteItems — 셸에서 하는 같은 트랜잭션.
- Python의 DynamoDB 조건부 쓰기 — 2배 비용 없이 얻는 단일 항목 원자성.
- DynamoDB 트랜잭션 — 격리, 멱등성, 그리고 트랜잭션이 값어치를 할 때.
- DynamoDB TransactionCanceledException — 모든 취소 사유 코드 해독.
- "Too many actions in a TransactWriteItems call" — 100개 작업과 4 MB 트랜잭션 한도.
- "Transaction request cannot include multiple operations on one item" — 트랜잭션당 항목마다 작업 하나.
참고 자료
- TransactWriteItems — Amazon DynamoDB API Reference
- DynamoDB.Client.transact_write_items — Boto3 documentation
- Amazon DynamoDB transactions: how it works — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
위에 링크된 공식 AWS 문서를 기준으로 2026-07-28에 마지막으로 검증했습니다.