Python에서 DynamoDB 배치 쓰기 (boto3 batch_writer)
batch_writer()는 Python이 다른 SDK보다 손이 덜 가는 유일한 DynamoDB 호출입니다. put과 delete를 버퍼에 모으고, 25개씩 BatchWriteItem 요청으로 잘라 보내며, 처리되지 않은 항목을 스스로 다시 보냅니다. 반면 대부분의 벌크 로드를 망가뜨리는 두 가지 실패로부터는 여러분을 지켜주지 않으며, 둘 다 잘못된 항목을 건넨 줄이 아니라 플러시 시점에 드러납니다.
코드
import boto3
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("Music")
songs = [
{"Artist": "Arturo Sandoval", "SongTitle": "Cubano Chant", "AlbumTitle": "Danzon", "Year": 1994},
{"Artist": "Arturo Sandoval", "SongTitle": "A Mis Abuelos", "AlbumTitle": "Danzon", "Year": 1994},
{"Artist": "Arturo Sandoval", "SongTitle": "Groovin' High", "AlbumTitle": "Swingin'", "Year": 1996},
]
with table.batch_writer() as batch:
for song in songs:
batch.put_item(Item=song)
# batch_writer buffers deletes too — target a key you're NOT also putting
# (two writes to the same key in one batch are rejected as a duplicate)
batch.delete_item(Key={"Artist": "Ella Fitzgerald", "SongTitle": "Misty"})
print(f"Buffered {len(songs)} puts + 1 delete; the batch flushes on exit")설명
- 지연된 플러시 —
batch.put_item()은 리스트에 덧붙일 뿐입니다. 버퍼가 25개에 도달하거나with블록을 빠져나가기 전까지는 아무것도 검증·직렬화·전송되지 않으므로, 잘못된 항목의 트레이스백은 그 항목을 건넨put_item호출이 아니라 플러시에서 나옵니다. 이터레이터로부터 적재하고 있다면 버퍼에 무엇이 들어갔는지 여러분 쪽에서 색인을 관리하세요. - 평범한 Python 값 — 이것은 리소스 API이므로
{"N": "1994"}가 아니라1994를 씁니다. 소수는 반드시 Decimal이어야 합니다.float는 버퍼에 받아들여졌다가 플러시에서 거부됩니다. batch_writer()는Table의 메서드입니다. 읽기 쪽 짝은 그렇지 않습니다.batch_get_item은ServiceResource에 있고table.batch_get_item은 존재하지 않습니다. 배치 읽기에는 버퍼링도, 청크 분할도, 재시도 헬퍼도 아예 없습니다.- 오류가 아니라
UnprocessedItems— 그것이 처리해 주는 유일한 재시도입니다. 스로틀링된 쓰기는 다시 보내지지만ValidationException은 그대로 전파됩니다. 대신client.batch_write_item을 쓰면 Node.js 예제처럼 그 루프 전체가 여러분 몫이 됩니다. - 서비스 한도를 들어 올리지는 못합니다. 요청당 25개 쓰기, 항목당 400 KB, 요청당 16 MB, 조건 불가, 업데이트 불가, 그리고 모든 put은 저장된 항목 전체를 대체합니다. 가드가 필요하거나 전부-아니면-전무가 필요하신가요? TransactWriteItems를 보세요.
batch_writer가 플러시에서 실제로 하는 일
put 30개를 버퍼에 넣고 그것이 어떤 호출을 하는지 지켜보세요. table.meta.client.batch_write_item을 감싸 요청 크기를 기록하면, DynamoDB Local 3.3.0을 상대로:
batch sizes sent: [25, 5]서비스 한도에서 잘린 두 번의 요청, 그리고 __exit__이 흘려보낸 나머지입니다. 그 플러시는 무조건적입니다. 블록 안에서 RuntimeError를 발생시켜도 버퍼에 있던 항목은 빠져나가는 길에 그대로 기록됩니다. 중간에 죽은 벌크 로드는 깨끗한 백지가 아니라 부분 적재를 남깁니다.
이제 두 가지 실패입니다. 같은 키를 두 번 버퍼에 넣어 보세요. 소스 데이터에 중복이 있는 순간 벌어지는 일입니다:
with table.batch_writer() as batch:
batch.put_item(Item={"Artist": "Dup", "SongTitle": "Key", "Year": 1})
batch.put_item(Item={"Artist": "Dup", "SongTitle": "Key", "Year": 2})botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the
BatchWriteItem operation: Provided list of item keys contains duplicates어느 put_item도 불평하지 않았습니다. batch_writer()는 요청하지 않는 한 중복을 제거하지 않으며, 요청하는 방법은 table.batch_writer(overwrite_by_pkeys=["Artist", "SongTitle"])입니다. 같은 두 개의 put을 그것으로 실행하면 항목은 Year: 2로 저장됩니다 — 버퍼가 키별로 마지막 쓰기를 남기므로, 두 행이 사실은 서로 다른 항목이었는데 키를 잘못 잡은 것이라면 그 중복 제거는 소리 없는 데이터 손실입니다.
두 번째는 오롯이 boto3의 것이며 DynamoDB까지 가지도 못합니다:
TypeError: Float types are not supported. Use Decimal types instead.4.5짜리 Rating은 불평 없이 버퍼에 앉아 있다가 플러시에서 터집니다. Decimal("4.5")는 {"N": "4.5"}로 정확히 왕복합니다. json.loads로 JSON에서 가격이나 평점을 읽으면 모든 숫자가 float이므로, 대부분의 임포트 스크립트에서 첫 실행부터 실패합니다. json.loads에 parse_float=Decimal을 넘기면 근원에서 해결됩니다.
네이티브 Python 값과 와이어 형식 사이를 손으로 오가고 있다면, DynamoDB JSON 변환기가 같은 항목의 양쪽을 나란히 보여 주므로 여러분의 Decimal이 실제로 무엇이 되는지 확인할 수 있습니다.
타입 매핑을 직접 작성하지 않고 CSV나 JSON에서 벌크 로드하려면 DynoTable을 다운로드하세요.
관련 예제
- Node.js의 DynamoDB BatchWriteItem — batch_writer가 감춘 수동 재시도 루프.
- AWS CLI로 하는 DynamoDB BatchWriteItem — 셸에서 하는 같은 배치 쓰기.
- Python의 DynamoDB PutItem — 이것이 묶어서 보내는 단일 항목 쓰기.
- DynamoDB의 배치 작업 — 한도, 부분 실패, 그리고 배치가 값을 하는 시점.
- "Too many items requested for the BatchWriteItem call" — 한 배치에 25개를 넘는 put/delete 요청.
- "Provided list of item keys contains duplicates" — 한 배치에서 같은 키를 건드리는 두 요청.
참고 자료
- Amazon DynamoDB guide (batch_writer) — Boto3 documentation
- BatchWriteItem — Amazon DynamoDB API Reference
- Error handling with DynamoDB — Amazon DynamoDB Developer Guide
위에 링크된 공식 AWS 문서를 기준으로 2026-07-28에 마지막으로 검증했습니다.