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_itemServiceResource에 있고 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.loadsparse_float=Decimal을 넘기면 근원에서 해결됩니다.

네이티브 Python 값과 와이어 형식 사이를 손으로 오가고 있다면, DynamoDB JSON 변환기가 같은 항목의 양쪽을 나란히 보여 주므로 여러분의 Decimal이 실제로 무엇이 되는지 확인할 수 있습니다.

타입 매핑을 직접 작성하지 않고 CSV나 JSON에서 벌크 로드하려면 DynoTable을 다운로드하세요.

관련 예제

참고 자료

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

Console 없이 DynamoDB 작업하기

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

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