【DynamoDB】「最新のN件」を最安・爆速で取得する。GSI(ソートキー)の正しい設計手順

エンジニアの記録
技術メモ・実体験

「特定ユーザーの投稿を新しい順に20件」——これをやりたくて、つい全件取得してアプリ側で並べ替えていませんか。DynamoDBではそれが遅さとコストの原因になります。

この記事でわかること

DynamoDBで「最新N件」を正しく取るためのGSI(グローバルセカンダリインデックス)設計を、Amplifyの@indexとAWS SDKの両方でコードつきに書きます。

PROFILE 20代ITエンジニア|React Native(Expo)× AWS Amplifyでアプリ開発中|Raw Ambition 運営
キラ
キラ
DynamoDBは「取り方を先に決めてから設計する」DB。RDBの感覚で作ると必ず詰まる。
Contents
  1. 結論:アクセスパターンからGSIを設計する
  2. なぜscanではダメなのか
  3. キー設計:PKで絞り、SKで並べる
  4. Amplifyの@indexで作る
  5. AWS SDKでqueryする
  6. まとめ
結論:アクセスパターンから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でブログを始める
※ アフィリエイトリンクです
Next
次回:AppSyncの@authでowner認可(他人のデータが見える/見えない問題)
AWS実践シリーズ

コメント

タイトルとURLをコピーしました