初級読了 2 分

DynamoDB のプロジェクション式

プロジェクション式 は DynamoDB の SELECT col1, col2 です。GetItemQueryScan に、アイテム全体ではなくそれらの属性だけを返すよう伝える、カンマ区切りの属性名の リストです。

DynamoDB のプロジェクション式は読み取りコストを削減しますか?

いいえ。ProjectionExpression はレスポンスのペイロードをトリムするだけで、課金される読み取りキャパシティには影響しません。DynamoDB はストレージからアイテム全体を読み込み、ディスク上のサイズで を計算してから、指定しなかった属性を出力の途中で除外します。実際に読み取りコストを削減するには、代わりにカバリング を使用してください。

  • ペイロードを削るが、読み取りコストは削らない。 DynamoDB はストレージからアイテム 全体を読み(そして課金し)、出ていく途中であなたが名指ししなかった属性を落とします。 ProjectionExpression はネットワークの最適化であって、キャパシティの最適化では ありません。
  • 公開サブセットを取得する方法。 呼び出し元が見てよい数個の属性を名指しします。残りは テーブルから一切出ません。
  • 予約語かもしれないものには #name プレースホルダーを使う。 式中の素の属性名は、 DynamoDB のおよそ570の予約語と衝突し、リクエストを失敗させます。
  • 本当の読み取り節約には、代わりにカバリングインデックスを使う。 必要な列だけを射影 する GSI は、自身の(より小さい)サイズで読まれます。

それが実際に節約するもの

SQL から来ると、SELECT a, bSELECT * より少なくスキャンすると思い込むでしょう。 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つのフィールドを必要とします。アイテム全体を取得すると、 emailAddresslastLoginIpinternalRiskScore が、決して見るべきでないコンテキストへ 旅します。公開サブセットだけを名指ししましょう。

GetItem  PK = "PROFILE#ada"  SK = "PROFILE#ada"
ProjectionExpression: displayName, avatarUrl, bio

レスポンスは3つの属性を運びます。プライベートなものはテーブルに残ります — 到着 に アプリでフィルタされるのではなく、そもそもレスポンスにシリアライズされません。それが セキュリティ上の勝ちであり、秘密がいったん境界を越えてしまうと取り消すのが難しいもの です。

この正確なリクエスト — 名前、プレースホルダー、そして SDK 呼び出し — を、 DynamoDB Expression Builder で組み立ててコピー できます。それが ProjectionExpressionExpressionAttributeNames マップを生成して くれます。

# プレースホルダーで予約語をエスケープする

ここできれいなプロジェクションが破裂します。DynamoDB は長い語のリスト — namestatuscommentsizetimestamp、その他数百 — を予約しています。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_ONLYINCLUDEALL を選びます。3 KEYS_ONLY か狭い INCLUDE のインデックスはアイテムあたり物理的に小さいため、それに対する Query はその 小さいサイズで計測されます。

それが カバリングインデックス です。クエリはインデックスだけで完全に答えられ、 ベーステーブルへの戻り旅はありません。ホットな読み取りパターンが大きなアイテムから常に 数個の属性しか必要としないときに使いましょう。

ProjectionExpressionカバリング GSI
ペイロードを削るはいはい
読み取りコストを削る**いいえはい** — インデックスのサイズで読む
追加ストレージなし射影したフィールドの2つ目のコピー
追加の書き込みコストなし書き込みがインデックスに伝播する
最適な用途プライベートフィールドの秘匿、小さな勝ち大きなアイテムから数フィールドのホット読み取り

トレードオフは正直です。インデックスは、読み取りキャパシティを節約するために、ストレージと 書き込みキャパシティを要します。重いアイテムから薄いスライスを頻繁に読むなら価値があり、 1回限りの GetItem を削るためなら価値はありません。インデックスの種類選びは GSI と LSI を、ホットパスに置く前に GSI 読み取りが古くなりうるとき を参照して ください。

落とし穴と次のステップ

  • 請求が小さくなると期待しない。 プロジェクション単体は決して RCU を変えません。数字が 動かなかったら、それは文書化された挙動であってバグではありません。
  • 予約語をプレースホルダー化する。 式中の素の namestatus はリクエストを失敗 させます — # でマップしましょう。
  • キー属性の射影は無料でしばしば有用 — DynamoDB はそれらを安価に返し、ページングや 再取得を可能にします。
  • カバリングインデックスに手を伸ばす のは、ホットなパターンが大きなアイテムから数 フィールドを読むときだけ。先に書き込み/ストレージのコストを比較しましょう。

ProjectionExpression とその属性名マップを Expression Builder で構築し、 DynoTable を試して 自分のテーブルに対してこれらのプロジェクションを実行し、 レスポンスが縮むのを見てください。


  1. AWS DynamoDB デベロッパーガイド、Working with Read and Write Operations — 読み取りキャパシティは ProjectionExpression が適用される前のアイテムサイズに基づく。https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/
  2. AWS DynamoDB デベロッパーガイド、Reserved Words in DynamoDBhttps://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
  3. AWS DynamoDB デベロッパーガイド、Attribute ProjectionsKEYS_ONLY / INCLUDE / ALL)。https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html

更新日