Scan de DynamoDB en Java (SDK de AWS v2)

scanPaginator gestiona LastEvaluatedKey por ti, y el único mando al que casi todo el mundo recurre después — .limit(...) — hace el mismo scan más lento y algo más caro. Para saber cuándo evitar Scan por completo, mira 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) convierte 3 peticiones en 25 y cuesta más

El juego de prueba tiene 600 canciones de unos 3,9 KB cada una, y 8 de ellas coinciden. Ejecuta el ejemplo tal cual y luego ejecútalo otra vez con .limit(25):

Forma de la peticiónIdas y vueltasUnidades de lecturaItems devueltos
tal cual3284,58
.limit(25)25288,08

La capacidad se factura por página, redondeada al alza a un límite de 4 KB, así que trocear una lectura de 1 MB en 24 pequeñas paga el redondeo 24 veces. La petición número 25 es la otra sorpresa: la página 24 terminó la tabla y aun así devolvió un LastEvaluatedKey, así que el paginador preguntó una vez más y obtuvo scannedCount=0. DynamoDB señala «no hay más datos» omitiendo esa clave, no devolviendo una página corta, y en un límite impuesto por Limit todavía no lo sabe.

.limit(...) es un mando para suavizar la capacidad de un trabajo en segundo plano que no quieres que limite una tabla en vivo. No es una forma de abaratar ni de acortar un scan.

El alias no es estilístico

Quita #filter0 y filtra directamente sobre Year, y el SDK saca esto a través de awsErrorDetails():

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

Year es una de las 573 palabras reservadas. Como todo error de paginador en este SDK, llega en la primera iteración del bucle for, no cuando llamas a scanPaginator(request), así que el try tiene que envolver el bucle.

Medido el 2026-07-28 contra DynamoDB Local (amazon/dynamodb-local) con software.amazon.awssdk:dynamodb 2.49.4 en OpenJDK 26.0.1.

Explicación

  • Las dos primeras páginas devuelven cero Items. Con este filtro los resultados por página son 0, 0 y 8, a 128,5, 128,5 y 27,5 unidades de lectura. filterExpression corre después de la lectura, así que esas dos respuestas vacías cuestan el precio completo, y cualquier if (page.items().isEmpty()) break informa de una tabla vacía.
  • ddb.scanPaginator(request).items() aplana las páginas en un solo Iterable<Map<String, AttributeValue>> y pagina a tus espaldas, lo que colapsa el bucle anidado cuando solo quieres los Items. Es un SdkIterable, así que .stream() funciona — .items().stream().count() devuelve 8 aquí.
  • ScanIterable vuelve a ejecutar el scan en cada iteración. Es perezoso, no cacheado: recorrer el mismo objeto dos veces envía las peticiones dos veces y factura dos veces. Vuélcalo una vez a una List, como hace el ejemplo.
  • Los números son String en el builder. AttributeValue.builder().n("2010") recibe un java.lang.String, porque DynamoDB transporta los números como texto decimal. Pasar un int no compila.
  • .segment(...) / .totalSegments(...) reparten un scan de tabla completa entre workers, cada uno con su propio paginador. Eso divide el tiempo de reloj y gasta la misma capacidad.

Hazlo visualmente

El comprobador de palabras reservadas pasa tus nombres de atributo contra la lista completa de AWS y te devuelve el mapa ExpressionAttributeNames, que es más rápido que descubrir que Year, Name, Size y Status están todas cogidas a razón de un ValidationException cada vez.

Para probar un filtro contra una tabla real antes de cablearlo en un ScanRequest, descarga DynoTable y pagina por los resultados en una cuadrícula.

Ejemplos relacionados

Referencias

Construye esta solicitud visualmente

Compón esta operación en el Generador de consultas de DynamoDB gratuito —condición de clave, filtro, índice, Limit, orden de clasificación y un bucle de paginación— y cópiala de vuelta como un programa ejecutable para SDK v3, CLI o boto3.

Abrir el Generador de consultas de DynamoDB

Trabaja con DynamoDB sin la Consola

Un cliente de escritorio rápido para DynamoDB que ejecuta el SQL real que DynamoDB no puede — JOINs, GROUP BY, agregaciones — con edición visual y un agente de IA con tus propias claves de Bedrock.

Prueba gratuita de 30 días, sin tarjeta — después, el plan Free sin límite de tiempo.