DynamoDB Scan en Go (AWS SDK v2)
NewScanPaginator es un cursor, no una colección: empieza optimista, acaba agotado y toma su tamaño de página de un sitio que el ScanInput que tienes delante no muestra. Para saber cuándo evitar Scan por completo, mira Query vs. Scan.
Código
package main
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
func main() {
ctx := context.TODO()
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
if err != nil {
log.Fatalf("load config: %v", err)
}
client := dynamodb.NewFromConfig(cfg)
paginator := dynamodb.NewScanPaginator(client, &dynamodb.ScanInput{
TableName: aws.String("Music"),
FilterExpression: aws.String("#filter0 >= :filterValue0"),
ExpressionAttributeNames: map[string]string{
"#filter0": "Year",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":filterValue0": &types.AttributeValueMemberN{Value: "2010"},
},
})
var items []map[string]types.AttributeValue
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
log.Fatalf("scan: %v", err)
}
items = append(items, page.Items...)
}
fmt.Printf("Matched %d items\n", len(items))
}Qué devuelve realmente HasMorePages()
Contra un conjunto de prueba de 600 canciones de unos 3,9 KB cada una, de las cuales 8 cumplen Year >= 2010 y quedan las últimas al ordenar:
fresh HasMorePages(): true (before any request)
page 1: len(page.Items)=0 ScannedCount=271 CU=128.5 LastEvaluatedKey set
page 2: len(page.Items)=0 ScannedCount=271 CU=128.5 LastEvaluatedKey set
page 3: len(page.Items)=8 ScannedCount= 58 CU= 27.5 LastEvaluatedKey nil
exhausted HasMorePages(): false
NextPage() after exhaustion: err = "no more pages available"HasMorePages() es true antes de la primera llamada, así que el bucle siempre se ejecuta al menos una vez. No puedes usarlo para comprobar si una tabla está vacía. Luego dos páginas devolvieron cero Items mientras el scan seguía en marcha, lo que significa que if len(page.Items) == 0 { break } informa de un resultado vacío en una tabla que contiene 8 coincidencias. Y el paginador es de un solo uso: tras el bucle el valor está gastado, y NextPage devuelve un error en vez de reiniciarse. Volver a escanear significa volver a llamar a NewScanPaginator.
Limit vive en dos sitios, y gana la opción
Go reparte el tamaño de página entre la struct de entrada y las opciones propias del paginador. Pon ambos y la opción tiene prioridad:
ScanInput.Limit = 500, ScanPaginatorOptions.Limit = 25 -> ScannedCount 25Asignar in.Limit = aws.Int32(500) y esperar páginas de 500 Items es un fallo silencioso cuando una opción funcional de otro sitio ya puso 25. Si solo tocas ScanInput, el paginador lo respeta.
Medido el 2026-07-28 contra DynamoDB Local (amazon/dynamodb-local) con aws-sdk-go-v2/service/dynamodb v1.62.1 sobre go1.26.5.
Explicación
page.Itemses[]map[string]types.AttributeValue, no tu struct.attributevalue.UnmarshalListOfMaps(items, &songs)convierte el lote en una sola llamada y transformaYearen unintde Go; las etiquetas de structdynamodbavcontrolan el mapeo.- Los números son cadenas en el cable. El valor en crudo es
&types.AttributeValueMemberN{Value: "2010"}— el miembroNcontiene unstringde Go, así questrconvse sienta a ambos lados de cualquier aritmética salvo que hagas unmarshal a una struct tipada. FilterExpressionse ejecuta después de la lectura, y por eso las dos páginas vacías cuestan igualmente 128,5 unidades cada una. La referencia de la API es explícita en que filtrar "does not consume any additional read capacity units", y el corolario es que tampoco ahorra ninguna.#filter0es obligatorio, no cuestión de estilo.Yeares una palabra reservada de DynamoDB; sin alias devuelveValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year.Segment/TotalSegmentsdan a cada goroutine su propia porción de la tabla, y cada una necesita su propio paginador. El paralelismo recorta el tiempo de reloj y gasta la misma capacidad.
Hazlo visualmente
El constructor de consultas de DynamoDB emite la forma del programa entero — filtro, mapas de nombres y valores, y el bucle del paginador — para que las partes que mide esta página se generen en vez de recordarse.
Para ver cuántos Items toca de verdad un filtro antes de lanzar el scan contra un servicio, descarga DynoTable y ejecútalo contra la tabla en una cuadrícula.
Ejemplos relacionados
- DynamoDB Scan en Java — el mismo scan con AWS SDK for Java 2.x.
- DynamoDB Query en Go — la lectura más barata a la que deberías recurrir normalmente.
- 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 — el otro throttling, y cómo lo maneja el backoff exponencial.