Scan do DynamoDB em Java (AWS SDK v2)

O scanPaginator cuida do LastEvaluatedKey por você, e o botão que a maioria das pessoas gira em seguida — .limit(...) — deixa o mesmo scan mais lento e um pouco mais caro. Para saber quando evitar o Scan por completo, veja Query vs. Scan.

Código

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
import software.amazon.awssdk.services.dynamodb.model.ScanResponse;

public class ScanExample {
    public static void main(String[] args) {
        try (DynamoDbClient ddb = DynamoDbClient.builder()
                .region(Region.US_EAST_1)
                .build()) {

            Map<String, String> names = new HashMap<>();
            names.put("#filter0", "Year");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":filterValue0", AttributeValue.builder().n("2010").build());

            ScanRequest request = ScanRequest.builder()
                    .tableName("Music")
                    .filterExpression("#filter0 >= :filterValue0")
                    .expressionAttributeNames(names)
                    .expressionAttributeValues(values)
                    .build();

            List<Map<String, AttributeValue>> items = new ArrayList<>();
            for (ScanResponse page : ddb.scanPaginator(request)) {
                items.addAll(page.items());
            }
            System.out.println("Matched " + items.size() + " items");
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
        }
    }
}

.limit(25) transforma 3 requisições em 25 e custa mais

O conjunto de teste tem 600 músicas de aproximadamente 3,9 KB cada, e 8 delas correspondem ao filtro. Rode o exemplo como está escrito, depois rode de novo com .limit(25):

Formato da requisiçãoIdas e voltasUnidades de leituraItens retornados
como está escrito3284,58
.limit(25)25288,08

A capacidade é cobrada por página, arredondada para cima em um limite de 4 KB, então fatiar uma leitura de 1 MB em 24 pequenas paga o arredondamento 24 vezes. A 25ª requisição é a outra surpresa: a página 24 terminou a tabela e ainda assim retornou um LastEvaluatedKey, então o paginador perguntou mais uma vez e recebeu scannedCount=0. O DynamoDB sinaliza "não há mais dados" omitindo essa chave, não retornando uma página curta, e em um limite de Limit ele ainda não sabe.

.limit(...) é um botão de suavização de capacidade para um job em segundo plano que você não quer que faça throttle em uma tabela ao vivo. Não é um jeito de tornar um scan mais barato ou mais curto.

O alias não é estilístico

Remova #filter0 e filtre por Year diretamente, e o SDK expõe isto através de awsErrorDetails():

DynamoDbException / ValidationException /
Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year
/ http 400

Year é uma das 573 palavras reservadas. Como todo erro de paginador neste SDK, ele chega na primeira iteração do laço for, não quando você chama scanPaginator(request), então o try precisa envolver o laço.

Medido em 2026-07-28 contra o DynamoDB Local (amazon/dynamodb-local) com software.amazon.awssdk:dynamodb 2.49.4 no OpenJDK 26.0.1.

Explicação

  • As duas primeiras páginas retornam zero itens. Com este filtro os resultados por página são 0, 0 e 8, a 128,5, 128,5 e 27,5 unidades de leitura. A filterExpression roda depois da leitura, então essas duas respostas vazias custam preço cheio, e qualquer if (page.items().isEmpty()) break reporta uma tabela vazia.
  • ddb.scanPaginator(request).items() achata as páginas em um único Iterable<Map<String, AttributeValue>> e pagina nos bastidores, o que colapsa o laço aninhado quando você só quer os itens. É um SdkIterable, então .stream() funciona — .items().stream().count() retorna 8 aqui.
  • O ScanIterable re-executa o scan a cada iteração. Ele é preguiçoso, não cacheado: iterar sobre o mesmo objeto duas vezes envia as requisições duas vezes e cobra duas vezes. Esvazie-o em uma List uma única vez, como o exemplo faz.
  • Números são String no builder. AttributeValue.builder().n("2010") recebe uma java.lang.String, porque o DynamoDB transporta números como texto decimal. Passar um int não compila.
  • .segment(...) / .totalSegments(...) dividem um scan de tabela inteira entre workers, cada um com seu próprio paginador. Isso divide o tempo de relógio e gasta a mesma capacidade.

Faça isso visualmente

O verificador de palavras reservadas roda os nomes dos seus atributos contra a lista completa da AWS e devolve o mapa ExpressionAttributeNames, o que é mais rápido do que descobrir que Year, Name, Size e Status estão todos ocupados um ValidationException de cada vez.

Para testar um filtro contra uma tabela real antes de encaixá-lo em um ScanRequest, baixe o DynoTable e navegue pelos resultados em uma grade.

Exemplos relacionados

Referências

Monte esta solicitação visualmente

Componha esta operação no Construtor de Consultas do DynamoDB gratuito — key condition, filtro, índice, Limit, ordem de classificação e um laço de paginação — e copie de volta como um programa executável para SDK v3, CLI ou boto3.

Abrir o Construtor de Consultas do DynamoDB

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.