DynamoDB のプロジェクション式
プロジェクション式 は DynamoDB の SELECT col1, col2 です。GetItem、Query、
Scan に、アイテム全体ではなくそれらの属性だけを返すよう伝える、カンマ区切りの属性名の
リストです。
DynamoDB のプロジェクション式は読み取りコストを削減しますか?
いいえ。ProjectionExpression はレスポンスのペイロードをトリムするだけで、課金される読み取りキャパシティには影響しません。DynamoDB はストレージからアイテム全体を読み込み、ディスク上のサイズで を計算してから、指定しなかった属性を出力の途中で除外します。実際に読み取りコストを削減するには、代わりにカバリング を使用してください。
- ペイロードを削るが、読み取りコストは削らない。 DynamoDB はストレージからアイテム
全体を読み(そして課金し)、出ていく途中であなたが名指ししなかった属性を落とします。
ProjectionExpressionはネットワークの最適化であって、キャパシティの最適化では ありません。 - 公開サブセットを取得する方法。 呼び出し元が見てよい数個の属性を名指しします。残りは テーブルから一切出ません。
- 予約語かもしれないものには
#nameプレースホルダーを使う。 式中の素の属性名は、 DynamoDB のおよそ570の予約語と衝突し、リクエストを失敗させます。 - 本当の読み取り節約には、代わりにカバリングインデックスを使う。 必要な列だけを射影 する GSI は、自身の(より小さい)サイズで読まれます。
それが実際に節約するもの
SQL から来ると、SELECT a, b は SELECT * より少なくスキャンすると思い込むでしょう。
DynamoDB ではその直感は間違いです。読み取りの
キャパシティユニット は、プロジェクションが適用
される前の、ディスク上のアイテムのサイズ を 4 KB 単位に切り上げて計算されます。AWS は
明示的です。ProjectionExpression はリクエストが消費する読み取りキャパシティを変えません。1
つまりプロジェクションが節約するのは2つで、どちらも実在しますが、どちらも読み取りの下流に あります。
- ワイヤー上のバイト。 6 KB のアイテムが2つの小さな属性として返れば、レスポンスは
ごく小さくなります。数百アイテムを返す
Queryでは、それが急速に積み上がります。 - クライアント側の作業。 デシリアライズが少なく、メモリに保持するものが少なく、ログや API レスポンスに誤って漏らすものが少なくなります。
それが 節約しない のは RCU です。それが落とし穴です。請求を削ろうとプロジェクションに 手を伸ばし、変化がなく、DynamoDB が壊れていると結論する人がいます。壊れていません — 間違ったレバーを測ったのです。
公開ユーザープロフィールを射影する
ユーザーディレクトリを運用しているとします。各プロフィールは1つのアイテムで、人をハンドルで 取得できるようキーイングされています。
PK = "PROFILE#ada" (partition key)
SK = "PROFILE#ada" (sort key — single-item collection)
アイテムは太っています。アカウントの公開の顔に加えて、プライベートで運用上の属性の山を 運びます。
{
"PK": "PROFILE#ada",
"SK": "PROFILE#ada",
"displayName": "Ada L.",
"avatarUrl": "https://cdn.example.com/u/ada.png",
"bio": "Builds things.",
"emailAddress": "ada@example.com",
"passwordResetToken": "…",
"billingCustomerId": "cus_…",
"lastLoginIp": "…",
"internalRiskScore": 0.02
}公開プロフィールカードは3つのフィールドを必要とします。アイテム全体を取得すると、
emailAddress、lastLoginIp、internalRiskScore が、決して見るべきでないコンテキストへ
旅します。公開サブセットだけを名指ししましょう。
GetItem PK = "PROFILE#ada" SK = "PROFILE#ada"
ProjectionExpression: displayName, avatarUrl, bio
レスポンスは3つの属性を運びます。プライベートなものはテーブルに残ります — 到着 後 に アプリでフィルタされるのではなく、そもそもレスポンスにシリアライズされません。それが セキュリティ上の勝ちであり、秘密がいったん境界を越えてしまうと取り消すのが難しいもの です。
この正確なリクエスト — 名前、プレースホルダー、そして SDK 呼び出し — を、
DynamoDB Expression Builder で組み立ててコピー
できます。それが ProjectionExpression と ExpressionAttributeNames マップを生成して
くれます。
# プレースホルダーで予約語をエスケープする
ここできれいなプロジェクションが破裂します。DynamoDB は長い語のリスト — name、status、
comment、size、timestamp、その他数百 — を予約しています。2 射影しようと
している属性がそのうちの1つなら、式中の生の名前は拒否されます。
プロフィールに status 属性("active"、"suspended")もあるとします。これは失敗
します。
ProjectionExpression displayName, status
status は予約されています。修正は 式属性名 — 実名にマップされた # プレフィックスの
プレースホルダーです。
ProjectionExpression displayName, #s
ExpressionAttributeNames { "#s": "status" }
同じ仕組みがネストした属性に届きます。マップから単一のフィールドを、あるいはリストの 1要素を引き出すには、ドキュメントパス構文を使い — どのセグメントも予約されている可能性が あるので — 各セグメントをプレースホルダー化します。
ProjectionExpression #addr.#city, tags[0]
ExpressionAttributeNames { "#addr": "address", "#city": "city" }
実用的なルール:すべてをプレースホルダー化する。およそ570の予約語のどれを踏んでいるか 覚えておく必要は一切なく、式はどちらにせよ同じように読めます。
カバリングインデックスがプロジェクションに勝るとき
ペイロードだけでなく読み取りコストを本当に削る必要があるなら、レバーは、読み取る属性だけを
射影する グローバルセカンダリインデックス です。GSI はデータの別個のコピーで、その
射影には KEYS_ONLY、INCLUDE、ALL を選びます。3 KEYS_ONLY か狭い
INCLUDE のインデックスはアイテムあたり物理的に小さいため、それに対する Query はその
小さいサイズで計測されます。
それが カバリングインデックス です。クエリはインデックスだけで完全に答えられ、 ベーステーブルへの戻り旅はありません。ホットな読み取りパターンが大きなアイテムから常に 数個の属性しか必要としないときに使いましょう。
ProjectionExpression | カバリング GSI | |
|---|---|---|
| ペイロードを削る | はい | はい |
| 読み取りコストを削る | **いいえ | はい** — インデックスのサイズで読む |
| 追加ストレージ | なし | 射影したフィールドの2つ目のコピー |
| 追加の書き込みコスト | なし | 書き込みがインデックスに伝播する |
| 最適な用途 | プライベートフィールドの秘匿、小さな勝ち | 大きなアイテムから数フィールドのホット読み取り |
トレードオフは正直です。インデックスは、読み取りキャパシティを節約するために、ストレージと
書き込みキャパシティを要します。重いアイテムから薄いスライスを頻繁に読むなら価値があり、
1回限りの GetItem を削るためなら価値はありません。インデックスの種類選びは
GSI と LSI を、ホットパスに置く前に
GSI 読み取りが古くなりうるとき を参照して
ください。
落とし穴と次のステップ
- 請求が小さくなると期待しない。 プロジェクション単体は決して RCU を変えません。数字が 動かなかったら、それは文書化された挙動であってバグではありません。
- 予約語をプレースホルダー化する。 式中の素の
nameやstatusはリクエストを失敗 させます —#でマップしましょう。 - キー属性の射影は無料でしばしば有用 — DynamoDB はそれらを安価に返し、ページングや 再取得を可能にします。
- カバリングインデックスに手を伸ばす のは、ホットなパターンが大きなアイテムから数 フィールドを読むときだけ。先に書き込み/ストレージのコストを比較しましょう。
ProjectionExpression とその属性名マップを
Expression Builder で構築し、
DynoTable を試して 自分のテーブルに対してこれらのプロジェクションを実行し、
レスポンスが縮むのを見てください。
- AWS DynamoDB デベロッパーガイド、Working with Read and Write Operations — 読み取りキャパシティは
ProjectionExpressionが適用される前のアイテムサイズに基づく。https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ ↩ - AWS DynamoDB デベロッパーガイド、Reserved Words in DynamoDB。https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html ↩
- AWS DynamoDB デベロッパーガイド、Attribute Projections(
KEYS_ONLY/INCLUDE/ALL)。https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html ↩