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ón | Idas y vueltas | Unidades de lectura | Items devueltos |
|---|---|---|---|
| tal cual | 3 | 284,5 | 8 |
.limit(25) | 25 | 288,0 | 8 |
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 400Year 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.
filterExpressioncorre después de la lectura, así que esas dos respuestas vacías cuestan el precio completo, y cualquierif (page.items().isEmpty()) breakinforma de una tabla vacía. ddb.scanPaginator(request).items()aplana las páginas en un soloIterable<Map<String, AttributeValue>>y pagina a tus espaldas, lo que colapsa el bucle anidado cuando solo quieres los Items. Es unSdkIterable, así que.stream()funciona —.items().stream().count()devuelve 8 aquí.ScanIterablevuelve 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 unaList, como hace el ejemplo.- Los números son
Stringen el builder.AttributeValue.builder().n("2010")recibe unjava.lang.String, porque DynamoDB transporta los números como texto decimal. Pasar unintno 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
- Scan de DynamoDB en Go — el mismo scan con el SDK de AWS para Go v2.
- Query de DynamoDB en Java — la lectura más barata a la que normalmente deberías recurrir.
- Query vs. Scan — cuándo (rara vez) se justifica un
Scan. - ¿Por qué mi Scan de DynamoDB es lento y caro? — el modelo de coste y cómo evitarlo.
- DynamoDB ProvisionedThroughputExceededException — lo que un scan de tabla completa le hace a la capacidad de una tabla aprovisionada.
- DynamoDB ThrottlingException — la otra limitación, y cómo la maneja el backoff exponencial.
Referencias
- Scan — Amazon DynamoDB API Reference
- Use Scan with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- DynamoDbClient — AWS SDK for Java 2.x API Reference
- ScanRequest — AWS SDK for Java 2.x API Reference
- Scanning tables — Amazon DynamoDB Developer Guide
- Reserved words in DynamoDB — Amazon DynamoDB Developer Guide