技術メモ・実体験
「特定ユーザーの投稿を新しい順に20件」——これをやりたくて、つい全件取得してアプリ側で並べ替えていませんか。DynamoDBではそれが遅さとコストの原因になります。
この記事でわかることDynamoDBで「最新N件」を正しく取るためのGSI(グローバルセカンダリインデックス)設計を、Amplifyの@indexとAWS SDKの両方でコードつきに書きます。
キラ
キラ
DynamoDBは「取り方を先に決めてから設計する」DB。RDBの感覚で作ると必ず詰まる。
Contents
- 結論:アクセスパターンからGSIを設計する
- なぜscanではダメなのか
- キー設計:PKで絞り、SKで並べる
- Amplifyの@indexで作る
- AWS SDKでqueryする
- まとめ
結論:アクセスパターンからGSIを設計する
結論です。「どう取りたいか(アクセスパターン)」を先に決め、それを満たすGSIを張る。「ユーザー別・新しい順」ならPK=userId / SK=createdAt のGSIを作り、降順queryで先頭からN件取ります。scanは使いません。
なぜscanではダメなのか
scanはテーブル全件を読んでからフィルタする動作です。データが増えるほど遅くなり、読み込んだ分だけ課金されます。1万件のうち20件欲しいだけでも、1万件読む料金がかかります。
⚖️ scan と query の違い scan : 全件読む → フィルタ → 遅い・高い・件数に比例して悪化 query: キーで対象だけ読む → 速い・安い・件数が増えても安定 「最新N件」は query で解くのが正解
キー設計:PKで絞り、SKで並べる
queryはパーティションキー(PK)で対象を絞り、ソートキー(SK)で並べ替えながら読むのが基本動作です。「ユーザー別・新しい順」を満たすには次のキーにします。
? GSI「byUser」のキー設計 PK : userId ← このユーザーの投稿だけに絞る SK : createdAt ← 作成日時で並べる(昇順で保持される) → 降順(ScanIndexForward=false)で読めば「新しい順」 → Limit=20 で先頭20件だけ取得
Amplifyの@indexで作る
Amplify(GraphQL)なら@indexディレクティブでGSIとクエリが自動生成されます。sortKeyFieldsに並べ替え用フィールドを指定するのがポイントです。
# schema.graphql
type Post @model @auth(rules: [{ allow: owner }]) {
id: ID!
userId: ID! @index(
name: "byUser"
sortKeyFields: ["createdAt"]
queryField: "postsByUser"
)
createdAt: AWSDateTime!
title: String!
body: String
}
amplify pushするとpostsByUserというクエリが使えるようになります。クライアントからはこう呼びます。
import { generateClient } from 'aws-amplify/api';
import { postsByUser } from './graphql/queries';
const client = generateClient();
const res = await client.graphql({
query: postsByUser,
variables: {
userId,
sortDirection: 'DESC', // 新しい順
limit: 20,
},
});
const latest = res.data.postsByUser.items;
? POINT
sortDirection: 'DESC' が「新しい順」、limit が取得件数です。続きを取るときは返ってくるnextTokenを次のリクエストに渡します。ページネーションもこれで成立します。
AWS SDKでqueryする
Amplifyを使わず生のSDKで書く場合はこうです。IndexNameでGSIを指定し、ScanIndexForward: falseで降順にします。
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, QueryCommand } from '@aws-sdk/lib-dynamodb';
const doc = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const res = await doc.send(new QueryCommand({
TableName: 'Post',
IndexName: 'byUser',
KeyConditionExpression: 'userId = :uid',
ExpressionAttributeValues: { ':uid': userId },
ScanIndexForward: false, // 降順 = 新しい順
Limit: 20,
}));
const latest = res.Items;
スカイ
スカイ
「取り方を決める→キーを決める→GSIを張る」の順番さえ守れば、scanに頼らなくて済むんだね。
? この記事のまとめ
- アクセスパターンを先に決めてからGSIを設計する
- scanは全件読むので遅く高い。最新N件はqueryで解く
- PKで絞り、SKで並べる。降順queryで新しい順
- Amplifyは@index、生SDKはIndexName+ScanIndexForward:false
Recommended
学んだことは、ブログに書くと一番定着する
エンジニアのアウトプット先として、表示速度の速いConoHa WINGでブログを始めました。月額わずか、最短10分で開設できます。
▶ ConoHa WINGでブログを始める
※ アフィリエイトリンクです

コメント