DynamoDBのキー設計とセカンダリインデックス

Amazon DynamoDBのパーティションキーとソートキー、ホットキー対策、ローカル・グローバルセカンダリインデックスの使い分けと制約を解説します。

学習順Step 89 / 101サービスデータベース試験ドメイン開発

DynamoDBのキー設計とセカンダリインデックス

Amazon DynamoDBはキー・値型とドキュメント型のデータを扱うNoSQLデータベースです。テーブルの主キーを使った検索が基本となるため、データをどのキーで識別し、どのアクセスパターンに対応するかが重要です。主キーの選び方は、アイテムの配置だけでなく、要求が分散するか、検索に余計な読み取りが必要になるかにも影響します。

DynamoDBのテーブルとインデックスは、AWSが管理する内部パーティションに分散して保存されます。パーティションの配置や必要な分割、複数アベイラビリティーゾーン(AZ)への複製はサービス側が管理します。利用者がSSDやAZを選んでデータを配置するのではありません。一方、どの属性を主キーにするか、アクセスが特定のキーに集中しないかは、アプリケーション側で考える必要があります。

主キーがデータの配置と検索を決める

DynamoDBのテーブルには、次のいずれかの主キーを定義します。

主キーの形式 構成 使い方
パーティションキーのみ パーティションキー1つ キー値で1つのアイテムを特定する
複合主キー パーティションキーとソートキー パーティションキーで対象のグループを選び、ソートキーでそのグループ内を特定・範囲検索する

パーティションキーはアイテムの配置先を決めるために使われます。DynamoDBはキー値を内部でハッシュ化し、データを分散します。パーティションはユーザーが直接操作する物理装置ではなく、ストレージや処理能力を管理するためのサービス内部の単位です。データ量や負荷に応じた物理配置はDynamoDBに任せられます。

ソートキーは複合主キーに含める属性です。同じパーティションキーを持つアイテムをソートキー順に扱うため、完全一致に加えて、大小比較や範囲指定などの検索に利用できます。複合主キーではパーティションキー単独の値を共有できますが、パーティションキーとソートキーの組み合わせは一意でなければなりません。

たとえば、IoTアプリケーションでセンサーごとの計測値を保存するなら、sensorId をパーティションキー、measuredAt をソートキーにできます。特定センサーの最新値や時間範囲内の計測値を読み取る場合、センサーIDを指定したうえで時刻の条件を追加します。すべてのセンサーの指定時刻を横断検索する使い方なら、この主キーだけでは適合しないため、別のインデックスやデータモデルを検討します。

DynamoDBのパーティションキーとソートキーでデータを分散・検索する仕組み

主キーは「一意に識別できる属性は何か」だけで決めるのではなく、アプリケーションが必要とする読み書きのパターンから設計します。DynamoDBの Query はパーティションキーの値を指定して検索します。複合主キーの場合は、そのグループ内でソートキーにも条件を指定できます。任意の属性を毎回検索条件にできるわけではありません。

パーティションキーとアクセス集中

パーティションキーの候補は、値の種類や数(カーディナリティ)に加えて、各値にどれほどリクエストが集まるかを見て選びます。異なる値にアクセスが分散するキーは、データと要求を広く分散しやすくなります。一方、すべての書き込みや読み込みが少数のキー値に向かうと、テーブル全体に余裕があっても一部のキーに負荷が集中することがあります。この状態はホットキー、またはホットパーティションの問題として扱われます。

数値や時刻が連続していること自体が、直ちにホットキーを意味するわけではありません。DynamoDBはパーティションキーをハッシュ化するため、値が連番でも多くの異なるキー値へ均等にアクセスするなら、分散につながる場合があります。問題になりやすいのは、たとえば全イベントが同じ eventType や同じ日付のパーティションキーに書き込まれるなど、同一のキー値に大量のアクセスが集まる設計です。時刻の値がパーティションキーに含まれていても、すべての要求が単一の「現在」キーを共有する形なら偏りが発生し得ます。

書き込みが一つのキー値に集まるワークロードでは、キーにバケット番号やランダムなサフィックスを追加し、書き込み先を複数のパーティションキーへ分散させる方法があります。ただし、元のキーに属するデータは複数のバケットに分かれます。そのデータをすべて取得するには各バケットへ Query を行い、アプリケーション側で結果をまとめるなどの処理が必要です。分散方式を採用する前に、書き込み負荷の削減と、読み取り時の追加処理を比較してください。

ホットキー対策は、キーをランダム化すれば終わりではありません。キー値ごとの要求量、読み書きの比率、時間帯による変化、読み取り時に必要な集約方法を併せて確認します。テーブル全体のキャパシティを増やしても、キー単位に集中したアクセスパターンは解消されないことがあります。

セカンダリインデックスで別の検索パターンに対応する

主キー以外の属性を使ってアイテムを検索するには、セカンダリインデックスを設計します。DynamoDBには、ローカルセカンダリインデックス(LSI)とグローバルセカンダリインデックス(GSI)があります。

比較項目 LSI GSI
パーティションキー テーブルと同じ テーブルとは異なる属性を設定できる
ソートキー テーブルのソートキーとは異なる属性を使う 任意。テーブルとは異なる属性を指定できる
作成できる時点 テーブル作成時のみ テーブル作成時、または作成後
検索範囲 テーブルと同じパーティションキーのアイテム群 GSIのパーティションキーでグループ化したアイテム
読み込み整合性 結果整合性または強い整合性 結果整合性のみ
主な設計上の制約 パーティションキーごとのアイテムコレクション合計10 GB 個別のGSIキャパシティ、反映遅延、インデックス容量を考慮する

DynamoDBのテーブルからローカル・グローバルセカンダリインデックスへ分岐する検索構造

どちらのインデックスでも、検索に必要なキー属性のほかに、インデックスへ複製する属性(プロジェクション)を選べます。取得したい属性がインデックスに含まれない場合は、テーブル本体への追加読み込みが必要になることがあります。検索パターンと返却項目を基に、インデックスに格納する属性を決めます。

ローカルセカンダリインデックス(LSI)

LSIは、テーブルと同じパーティションキーを使い、別のソートキーで同じアイテム群を並べ替えて検索するインデックスです。テーブル作成時にのみ定義でき、後から既存テーブルへ追加したり削除したりできません。

先ほどのセンサー計測値テーブルで sensorId をパーティションキー、measuredAt をソートキーにしたとします。特定センサーの計測値を時刻範囲で探すアクセスパターンが主キーに対応します。さらに、そのセンサーの測定結果を qualityScore の順に調べたいなら、同じ sensorId をパーティションキーとし、qualityScore をソートキーとするLSIを候補にできます。この場合もまず対象のセンサーを指定し、そのセンサーのアイテム群を別の順序で検索します。

LSIを使うテーブルには、重要なサイズ制約があります。LSIを持つテーブルでは、同じパーティションキーの値を共有するテーブル本体とすべてのLSIのアイテムを合わせたアイテムコレクションは、最大10 GBです。あるセンサーに大量の履歴が蓄積する設計では、そのセンサーのアイテムコレクションが上限に達する可能性があります。パーティションキーごとのデータ量が偏らないか、成長後のサイズも見積もります。

この制約はLSIを持たないテーブル一般に同じ形で適用される上限ではありません。LSIを選ぶときは、ソート順を増やせる利点と、アイテムコレクション単位の上限・作成時点の制約を一緒に評価してください。バックアップからテーブルを復元する運用があっても、既存テーブルへ自由にLSIを後付けできるようになるわけではありません。テーブル作成・復元で利用できるオプションは、実行する操作の公式仕様を確認します。

LSIへの書き込みも、テーブル本体のアイテム更新に伴って維持されます。そのため、格納する属性や更新するアイテムのサイズが増えると、書き込み処理量や保存容量に影響します。プロビジョンドキャパシティではLSIの読み書きにテーブルのキャパシティを使い、LSI用のキャパシティを個別には設定しません。オンデマンドの場合も、書き込みや保存データ量が料金に関係します。これに対してGSIは、プロビジョンドモードでインデックスごとに読み込み・書き込みキャパシティを設定します。

グローバルセカンダリインデックス(GSI)

GSIは、テーブルの主キーとは異なるパーティションキーを定義し、必要なら別のソートキーも指定して、新たな検索パターンに対応するインデックスです。たとえば、sensorId を主キーのパーティションキーとするテーブルに、すべてのセンサーから届く警告を severity で探すアクセスパターンを加えたい場合、severity をGSIのパーティションキー、measuredAt をソートキーにできます。こうすると、テーブルの主キーに依存せず、重要度ごとに警告アイテムを検索できます。

GSIはテーブルの作成後にも追加できます。追加時には既存アイテムを読み取ってインデックスへ反映する処理(バックフィル)が行われ、インデックスが利用可能になるまで状態を確認します。大規模テーブルでは作成が即時に終わるとは限らず、バックフィルの読み取りや、その間のテーブル負荷も考慮します。LSIのようにテーブル作成時だけに限定されない点が、両者の大きな違いです。

GSIの更新はテーブルへの書き込みと同期的に完了するとは限りません。そのため、GSIを使った Query は結果整合性読み込みのみをサポートし、強い整合性読み込みは指定できません。書き込み直後の変更がすぐにGSIの検索結果へ現れる必要がある処理では、GSIの反映遅延を前提にするか、ベーステーブルを主キーで読み取る方法などを検討します。

プロビジョンドキャパシティでは、GSIには読み込み・書き込みキャパシティをそれぞれ設定します。GSIの書き込み能力が不足するとインデックス更新が滞り、構成によってはベーステーブルへの書き込みにもスロットリングが波及する場合があります。テーブル本体だけでなく、各GSIの書き込み量とキャパシティも監視してください。オンデマンドキャパシティでは要求に応じて処理されますが、GSIへの要求もリクエスト量や料金の対象になります。

GSIのインデックスキー属性を持たないアイテムは、そのGSIに登録されません。この特性をスパースインデックスとして利用すると、特定の属性を持つアイテムだけを検索するインデックスを構成できます。また、インデックスに投影する属性を絞れば、インデックスの保存サイズや読み取り量を抑えられます。必要な応答属性、テーブル本体を再読込する頻度、インデックスのサイズを比較して設計します。

Query、Scanとコスト・性能のトレードオフ

セカンダリインデックスは、適切なキー条件を指定した Query を可能にするためのものです。LSIまたはGSIのパーティションキーを指定して対象を絞り、必要ならソートキーの条件も加えます。テーブルやインデックスに対する**Scan は全件走査**です。インデックスを対象に Scan しても、インデックス内のアイテムを端から調べる処理であり、検索対象がキー条件で絞り込まれるわけではありません。全件走査を頻繁な検索の代わりにせず、アクセスパターンを表すキーと Query を設計します。

インデックスを増やすと、検索方法が増える一方でコストや更新作業も増えます。

  • 書き込み処理: 対象アイテムがインデックスキーやプロジェクション属性を変更すると、関連するインデックスも更新されます。インデックス数と更新対象が増えるほど、必要な書き込み処理量も増加します。GSIは別の書き込みキャパシティを持ち、LSIの書き込みはテーブルの書き込みキャパシティを使用します。
  • ストレージ: インデックスはキーと選択した属性を保持するため、テーブル本体に加えて保存領域を使います。ALL プロジェクションのように属性を広く複製する設計では、インデックスサイズが大きくなりやすくなります。
  • 読み取り: Queryで対象範囲を絞ると、テーブル全件を読む設計より必要なアイテムを効率よく取得できます。ただし、読み取り料金やキャパシティは返却サイズ、読み取り整合性、プロジェクション、キャパシティモードなどに左右されます。インデックスを利用すれば読み取りが無条件に無料または一定量になるわけではありません。
  • 運用: GSI作成時のバックフィルや、インデックスごとのスロットリング・サイズを監視する必要があります。LSIを選んだ場合は、パーティションキーごとのアイテムコレクションが10 GBを超えないように設計段階から管理します。

セカンダリインデックスを多く使う必要があるなら、まず検索パターンを整理し、各インデックスがどの要求を解決するかを確かめます。DynamoDBで複数のアクセスパターンを実現するために、複数のGSIや単一テーブル設計を利用すること自体は不適切ではありません。一方、結合、任意の条件による検索、複雑な集計、リレーショナル制約が中心で、効率化のためのインデックスを大量に維持しなければならない場合は、RDSやAuroraのようなリレーショナルデータベースとコスト・運用を比較します。

データの種類ごとに別テーブルを作るか、一つのテーブルに複数のエンティティを格納するかにも一律の正解はありません。DynamoDBでは、必要なアクセスパターンを基にテーブル構造を決めることが重要です。関連データを同じテーブルに置く設計は、同時取得やトランザクションに役立つ場合がありますが、アクセスパターンに合わないアイテムを無理に同居させる必要もありません。

設計の進め方

キーとインデックスを決めるときは、先にアプリケーションが必要とする問い合わせを列挙します。各問い合わせに対して、どのキー値を指定し、範囲条件が必要か、結果整合性を許容するか、読み取る属性は何かを確認します。その上でパーティションキーの負荷分布、LSIのアイテムコレクション上限、GSIの更新とキャパシティを検討します。

確認すること 判断の例
主キーで必要な検索を表せるか パーティションキーだけでよいか、グループ内の範囲検索にソートキーが要るか
負荷が少数のキー値に集中しないか 人気のある単一キーに集中する書き込みを分散できるか。分割後の読み取りも許容できるか
同じパーティション内の別の順序が要るか LSIの追加検索が必要か、10 GB制約と作成時の定義に適合するか
主キーとは別のアクセスパターンが要るか GSIの別キー、結果整合性、バックフィル時間、独立したキャパシティが許容できるか
走査ではなくキー検索にできるか Scan を頻繁に使わず、インデックスの Query で要求対象を絞れるか
維持費用に見合うか 書き込み、ストレージ、読み取り、運用負荷を含めて別のDBモデルとも比較したか

AWS公式ドキュメント

関連トピック

重要ポイント

  • ▸DynamoDBの主キーはパーティションキー単独、またはパーティションキーとソートキーの組み合わせで定義し、同じパーティションキーのアイテムはソートキーで検索・範囲指定できる
  • ▸パーティション配置と処理能力の管理はDynamoDBが行うが、アクセスパターンに対するパーティションキーの選択はアプリケーション側の設計事項である
  • ▸ホットキー対策では、連番かどうかだけでなく、同一キー値への読み書き集中と値の分布を評価する。キーを分割する場合は複数キーへの読み取りも設計する
  • ▸LSIはテーブルと同じパーティションキーと別のソートキーを使い、テーブル作成時に定義する。LSIを持つテーブルでは、パーティションキーごとのアイテムコレクションに10 GBの上限がある
  • ▸GSIは別のパーティションキーと任意のソートキーで別の検索パターンを作り、テーブル作成後にも追加できる。GSIのQuery結果は結果整合性読み込みである
  • ▸インデックスはQueryを効率化する一方、追加ストレージと書き込み処理・料金を伴う。Scanはインデックスを対象にしても全件走査であり、キー条件のQueryとは異なる

このトピックの学習を完了しますか?

完了状態はいつでも切り替えられます