DynamoDB BatchGetItem em Python (boto3)

batch_get_item busca até 100 itens por chave primária em uma única requisição. O while request_items: no bloco abaixo é todo o idioma do boto3: o DynamoDB devolve as sobras em uma resposta bem-sucedida, e um dicionário vazio é falsy, então o loop termina sozinho. Os limites e as regras de resultado parcial estão em operações em lote no DynamoDB; esta página é sobre a chamada do boto3 e os erros que ela levanta.

Código

import time

import boto3

client = boto3.client("dynamodb")

request_items = {
    "Music": {
        "Keys": [
            {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
            {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "A Mis Abuelos"}},
            {"Artist": {"S": "Ella Fitzgerald"}, "SongTitle": {"S": "Misty"}},
        ]
    }
}

items = []
attempt = 0

while request_items:
    response = client.batch_get_item(RequestItems=request_items)
    items.extend(response["Responses"].get("Music", []))

    # A partial result is NOT an error: throttling, a >16 MB response, or an
    # internal failure returns the leftovers in UnprocessedKeys. Retry them
    # with exponential backoff.
    request_items = response["UnprocessedKeys"]
    if request_items:
        attempt += 1
        time.sleep(min(0.1 * 2**attempt, 5))

print(f"Fetched {len(items)} items")

Explicação

  • response["UnprocessedKeys"] está sempre lá. Em um lote totalmente atendido a chave existe e contém {}, então request_items = response["UnprocessedKeys"] é seguro de indexar e o dicionário vazio falsy é o que encerra o while. Responses é a que exige cuidado: uma tabela cujas chaves todas erraram fica ausente dele, e é por isso que o bloco usa .get("Music", []).
  • ConsistentRead e ProjectionExpression vão dentro do dicionário de cada tabela, ao lado de "Keys", não junto de RequestItems. O boto3 vai enviar alegremente uma chave fora de lugar e deixar o serviço rejeitá-la.
  • Este é o client de baixo nível, então os valores são JSON do DynamoDB ({"S": ...}, {"N": ...}). A API de resource tem batch_get_item no ServiceResource, não em Table. boto3.resource("dynamodb").batch_get_item(...) aceita valores nativos do Python; table.batch_get_item não existe. Essa assimetria surpreende quem recorre a ela depois de usar table.batch_writer(), que é um método de Table.
  • O backoff se aplica apenas a UnprocessedKeys. Um ValidationException é um bug na requisição, e repeti-la só queima tempo de relógio.

Os dois erros que esta chamada levanta, literalmente

Ambos são erros do lado do cliente que nenhum retry conserta, e ambos aparecem como um simples botocore.exceptions.ClientError. Contra o DynamoDB Local 3.3.0, str(e):

An error occurred (ValidationException) when calling the BatchGetItem operation: Too many items requested for the BatchGetItem call
An error occurred (ValidationException) when calling the BatchGetItem operation: Provided list of item keys contains duplicates

O primeiro são 101 chaves, o segundo é a mesma chave listada duas vezes. Repare no que você não pode escrever para capturá-los:

except client.exceptions.ValidationException:  # AttributeError

O botocore modela 34 classes de exceção nomeadas no client do DynamoDB, e ValidationException não é uma delas. ConditionalCheckFailedException e ProvisionedThroughputExceededException são, e é por isso que a página de escrita condicional consegue capturar por classe e esta não. Existe até uma DuplicateItemException modelada, e ela não é o que uma chave duplicada em um lote devolve. Então uma leitura em lote precisa ramificar pelo código:

except ClientError as e:
    if e.response["Error"]["Code"] == "ValidationException":
        raise  # a bug in the request; retrying will not help

O caso da chave duplicada é o que morde no código real, porque uma lista de chaves montada a partir de um resultado de Query ou de uma tabela de junção repete naturalmente. Deduplique antes de enviar, lembrando que dois dicionários só são iguais se todos os atributos de chave coincidirem.

O tamanho é o outro motivo pelo qual um lote de 100 não continua sendo um lote de 100: cada item é arredondado para cima para 4 KB na cobrança e contado contra os 16 MB da resposta, então 100 itens de 300 KB voltam como cerca de 52, com o resto em UnprocessedKeys. A calculadora de tamanho de item te dá o valor por item para multiplicar.

Para trazer um conjunto de chaves de volta e inspecionar o que retornou antes de escrever o loop, baixe o DynoTable.

Exemplos relacionados

Referências

Verificado pela última vez em 2026-07-28 contra a documentação oficial da AWS vinculada acima.

Trabalhe com o DynamoDB sem o Console

Um cliente desktop rápido para DynamoDB que roda o SQL de verdade que o DynamoDB não consegue — JOINs, GROUP BY, agregações — com edição visual e um agente de IA com suas próprias chaves do Bedrock.

Teste grátis de 30 dias, sem cartão de crédito — depois o plano Grátis sem limite de tempo.