Scan do DynamoDB em Python (boto3)
Um scan no boto3 são duas decisões: a requisição, e como você a pagina. O trecho abaixo usa o paginador embutido, que não é um wrapper que alguém escreveu em volta do seu loop. São cinco linhas de configuração do botocore, e essas cinco linhas decidem se o seu scan está correto e quanto ele custa. (Se você deveria estar fazendo scan, para começo de conversa, é outra questão.)
Código
import boto3
client = boto3.client("dynamodb")
paginator = client.get_paginator("scan")
items = []
for page in paginator.paginate(
TableName="Music",
FilterExpression="#filter0 >= :filterValue0",
ExpressionAttributeNames={"#filter0": "Year"},
ExpressionAttributeValues={":filterValue0": {"N": "2010"}},
):
items.extend(page["Items"])
print(f"Matched {len(items)} items")Explicação
- O paginador é dado, não código. O botocore traz uma entrada por operação em
paginators-1.json; a doScandiz{"input_token": "ExclusiveStartKey", "output_token": "LastEvaluatedKey", "limit_key": "Limit", "result_key": ["Items", "Count", "ScannedCount"], "non_aggregate_keys": ["ConsumedCapacity"]}. Tudo o que vem abaixo decorre dessas chaves. PaginationConfig={"PageSize": n}define oLimit, porqueLimité olimit_key. OLimitlimita itens lidos, nunca itens retornados, então com umaFilterExpressionuma página pode vir vazia e ainda assim ter custo.MaxItemsconta itens deresult_keye devolve umNextTokenque você pode passar comoStartingTokenem um processo posterior. Ele não impede a requisição de ler além do seu corte.build_full_result()agrega apenas os campos deresult_key.Items,CounteScannedCountsão somados;ConsumedCapacityé umanon_aggregate_key, então o resultado mesclado reporta a capacidade de uma página como se fosse a do scan inteiro. Some você mesmo, por página, ou você vai subnotificar por um fator igual ao número de páginas.- A
FilterExpressionroda no lado do servidor depois da leitura, então você é cobrado porScannedCount, não porCount.#filter0dá alias aYearporque é uma palavra reservada; sem o alias a requisição falha antes de ler qualquer coisa. - Os erros todos chegam como
botocore.exceptions.ClientError. Ramifique pore.response["Error"]["Code"]; as classes por erro existem apenas como atributos gerados no cliente (client.exceptions.ProvisionedThroughputExceededException), nunca como símbolos importáveis. - A API de recurso é a outra ergonomia.
Table.scanrecebe tipos nativos do Python, retorna números comodecimal.Decimale monta filtros comAttr("Year").gte(2010)em vez de mapas de placeholders.
Quanto custa de verdade uma página filtrada
60 itens de aproximadamente 2 KB cada, Year = 2024 casando com dois deles, PageSize=10, executado contra o DynamoDB Local:
page 1: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 2: Count=1 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 3: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 4: Count=1 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 5: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 6: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 7: Count=0 ScannedCount=0 CU=0.0 LastEvaluatedKey=no
total CU across pages: 15.0Quatro das seis páginas reais não retornaram nada, a preço cheio. Esse é o formato do bug que o paginador existe para prevenir: um loop feito à mão que para quando Items está vazio desiste na página 1 e reporta duas músicas correspondentes como zero.
A página 7 é a outra metade. A página 6 bateu no seu Limit no último item da tabela, então o DynamoDB retornou um LastEvaluatedKey mesmo assim e o paginador gastou mais uma ida e volta para descobrir que não sobrou nada. Um LastEvaluatedKey significa "eu parei", não "tem mais".
Chamar build_full_result() no mesmo scan reporta CapacityUnits: 2.5. As seis páginas consumiram 15,0.
Paginar sem escrever o loop
O DynamoDB Query Builder monta o filtro, o mapa de aliases e o loop de paginação como um único programa executável, então a armadilha de Limit versus Count acima já vem resolvida antes de você colar o código. Para paginar uma tabela real de forma interativa em vez de por script, baixe o DynoTable.
Guias relacionados
- Query vs. Scan — quando (raramente) um
scanse justifica. - Por que meu Scan do DynamoDB é lento e caro? — o modelo de custo e como evitá-lo.
- Scans paralelos — dividindo a tabela com
Segment/TotalSegments, um paginador por segmento. - DynamoDB ProvisionedThroughputExceededException — o que um scan de tabela inteira faz com a capacidade de uma tabela provisionada.
Referências
- Scan — Amazon DynamoDB API Reference
- scan — Boto3 DynamoDB.Client Reference
- Scan paginator — Boto3 DynamoDB Reference
- Scanning tables — Amazon DynamoDB Developer Guide
Verificado pela última vez em 2026-07-28 contra a documentação oficial da AWS vinculada acima.