Go での DynamoDB Query(AWS SDK v2)
Query は 1 つのパーティションを読み、必要ならソートキーで絞り込みます(それが正しい選択となる場面は Query と Scan の比較が扱い、使える演算子はキー条件式が網羅しています)。AWS SDK for Go v2 が上乗せするのは QueryPaginator で、LastEvaluatedKey のループを for ループに変えてくれる一方、その過程で「止まる」機会をそっと奪います。
コード
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.NewQueryPaginator(client, &dynamodb.QueryInput{
TableName: aws.String("Music"),
KeyConditionExpression: aws.String(
"#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)"),
ExpressionAttributeNames: map[string]string{
"#hashKey": "Artist",
"#rangeKey": "SongTitle",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":hashKeyValue": &types.AttributeValueMemberS{Value: "Arturo Sandoval"},
":rangeKeyValue": &types.AttributeValueMemberS{Value: "C"},
},
})
var items []map[string]types.AttributeValue
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
log.Fatalf("query: %v", err)
}
items = append(items, page.Items...)
}
fmt.Printf("Found %d items\n", len(items))
}解説
Limit はページネータが読む量を制限しません。これがお金のかかる誤解です。上の入力に Limit: aws.Int32(5) を設定し、約 60 KB のアイテム 30 件からなるパーティションに対して実行した結果です。
page 1: Count=5 CU=37.0 page 5: Count=5 CU=37.0
page 2: Count=5 CU=37.0 page 6: Count=5 CU=37.0
page 3: Count=5 CU=37.0 page 7: Count=0 CU=0.0
page 4: Count=5 CU=37.0 total: 30 items, 222.0 units30 件すべてが返ってきました。Limit は ページサイズ であり、ページネータの仕事はページが尽きるまで問い合わせ続けることなので、両者はきれいに打ち消し合います。AWS はこれを「the maximum number of items to evaluate (not necessarily the number of matching items)」と定義しています(2026-07-28 取得)。最初の 5 件だけが欲しいなら、1 ページ目の後に自分でループを break してください。
小さなページはむしろ少し高くつきます。同じパーティションを Limit なしで読むと 2 ページで 220.0 ユニット、Limit: 5 では 7 回の呼び出しで 222.0 ユニットでした。各ページは自分のバイト合計を次の 4 KB 境界まで切り上げるので、ページが増えれば丸めも増え、加えてラウンドトリップのレイテンシが 6 回分余計にかかります。「読む量を減らそう」と Limit を下げても、どちらも手に入りません。
このループは、データがある回数より必ず 1 回多く呼び出します。上の 7 ページ目はゼロ件を返しました。DynamoDB は Limit に達したときは後続の有無にかかわらず LastEvaluatedKey を返し、HasMorePages() はそれを信じます。つまり Limit 付きのページネータは無駄なリクエストで終わり、ページごとの副作用(プログレスバー、バッチのフラッシュ、ログ行)は空のページに対して 1 回発火します。len(page.Items) でガードしてください。
HasMorePages() は最初のリクエストの前から true です。for ループにそもそも入れるよう true で初期化されているので、これはループ条件であって「データがあるか」のチェックではありません。クエリを投げるべきかどうかの判断に使うと、常にイエスと答えます。
エラーはページ単位で表面化し、部分的な読み取りは実在する状態です。NextPage は他の呼び出しと同じくラップされた smithy のエラーを返すので、文字列一致ではなく *types.ProvisionedThroughputExceededException などに対して errors.As でアンラップしてください。スニペットの log.Fatalf は、すでに集めたページを捨ててしまいます。サービスの中では通常、items を保持してどこまで進めたかを報告したいはずです。
パーティションを逆向きに読む唯一の方法が ScanIndexForward です。ソートキーの降順には ScanIndexForward: aws.Bool(false) を設定します。それ以外の「並べ替え」はありません。順序はソートキーから来るもので、別の順序が必要なら別のインデックスが必要です。IndexName: aws.String("...") はクエリ全体をそのインデックスに移します。
2 つのパッケージがこれを短くします。github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression は expression.Key("Artist").Equal(expression.Value("Arturo Sandoval")) からキー条件と 2 つのプレースホルダーマップを組み立て、手書きの #hashKey/:hashKeyValue の組と、それに伴う予約語のリスクを取り除きます。attributevalue.UnmarshalListOfMaps(page.Items, &songs) は 1 ページをそのまま []Song に変えます。
ビジュアルに行う
2 つのプレースホルダーマップこそ、手で打つより生成する価値のある部分です。無料の DynamoDB 式ビルダーは、対応する ExpressionAttributeNames と ExpressionAttributeValues とともにキー条件を組み立て、Go のリテラルとして出力するので、名前と値がずれることはありません。
自分のテーブルに対してクエリを実行するには — キー条件のフォーム、スクロールに合わせてページ送りするグリッド、リクエストを Go としてコピーし直す機能 — DynoTable をダウンロードしてください。
関連する例
- Java での DynamoDB Query — AWS SDK for Java 2.x による同じクエリ。
- Go での DynamoDB Scan — パーティションにキーで入れないとき。
- ページネーション —
LastEvaluatedKey、ExclusiveStartKey、そしてLimitがページサイズではない理由。 - 「Query condition missed key schema element」 — キー条件が誤った属性を指しているか、パーティションキーを飛ばしている。
- 「Query key condition not supported」 — contains や 2 つ目のソートキー条件など、キー条件では使えない演算子。
参考資料
- Query — Amazon DynamoDB API Reference
- Use Query with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- dynamodb package — AWS SDK for Go v2 (pkg.go.dev)
- expression package — AWS SDK for Go v2 (pkg.go.dev)
- Querying tables — Amazon DynamoDB Developer Guide
2026-07-28 に、go1.26.5 と aws-sdk-go-v2/service/dynamodb v1.62.1 を用い、約 60 KB のアイテム 30 件からなるパーティションに対して、ポート 9000 の DynamoDB Local(amazon/dynamodb-local)で測定しました。ページごとの件数とキャパシティの読み取り値はそのままの出力です。DynamoDB Local はドキュメントどおりの丸め規則を適用します。絶対値は挙動の形を示すデモとして扱い、サイジングの前に実際のサービスで測定してください。