はじめに
re:Invent 直前ですね!
ここ数日、 AWS の What's New も更新がなくなり、いよいよ本番を待つのみ!といった感じでしょうか?
今回は DynamoDB の以下のアップデートを紹介してみます。
DynamoDB のマルチ属性複合キーにより、GSI で最大 8 属性(パーティションキー: 4、ソートキー: 4)を組み合わせて使用できるようになりました。という更新です。
これまで複数の属性でソート、検索を実施する場合は、各属性を文字列結合した属性を用意する必要がありましたが、これが不要になるアップデートです。
DynamoDB の簡単なおさらい
私のような、長年 RDS のみ使っていた人間からすると、DynamoDB の用語や制限に少し戸惑うので、一旦簡単におさらいします。
今回は以下のようなゲームのハイスコアを管理するテーブルを例に、DynamoDB のインデックス構造を確認していきます。
テーブル構成例
DynamoDB では、以下のようなデータの集まりを「テーブル」と呼びます。
また、1行を「アイテム」、各列(UserId、GameName など)を「属性」と呼びます。
| UserId | GameName | HighScore | ScoreDate |
|---|---|---|---|
| user1 | PuzzleQuest | 15000 | 2025-01-01 |
| user1 | SpaceShooter | 28000 | 2025-01-02 |
| user1 | RacingChamp | 22000 | 2025-01-03 |
| user2 | PuzzleQuest | 18000 | 2025-01-01 |
| user2 | SpaceShooter | 32000 | 2025-01-02 |
なお、属性はアイテムによって異なる構成にすることも可能です。
プライマリキー(Primary Key)
DynamoDB のテーブルには必ずアイテムを一意に識別できるプライマリキーが必要です。
プライマリキーは、パーティションキー(Partition Key)とソートキー(Sort Key)で構成されます。
パーティションキーのみ
1 つの属性(パーティションキー)で構成するケースです。
パーティションキーでの検索は完全一致(=)のみで、部分一致や範囲検索はできません。
DynamoDB はパーティション単位でスループット制限を設けており、特定のパーティションにアクセスが集中する「ホットパーティション」を避け、スロットリングを防ぐ必要があります。
このため、パーティションキーはワークロードの特性に合わせた項目選定が必要になります。
今回の例だと、UserIdやGameNameなどが候補になると思います。
パーティションキー + ソートキー
2 つの属性(パーティションキー、ソートキー)で構成するケースです。
ソートキーは名前の通り、パーティションの中でのデータ順を決めるキーとなります。
ソートキーがあると部分一致や、範囲検索など柔軟な検索が可能になり、欲しい情報がピンポイントで取得可能となります。
逆にソートキーがないと、同一パーティションキーのデータを全て取得し、その中から欲しい情報をスキャンする形となります。大量にデータがある場合、処理速度やコスト面で問題が出てくる可能性があります。
user1パーティションから、SpaceShooterのハイスコアを取得した場合などに利用し、このケースだとGameNameをソートキーにします。
またソートキーは1属性しか指定できないため、ソートキーとは別の条件でソート(検索)したい場合は、 LSI/GSI といった機能を利用します。
LSI (Local Secondary Index)
LSI は、テーブル作成時に指定したパーティションキーを使用し、異なるソートキーでデータをクエリできるインデックスです。
UserId(パーティションキー)+ GameName(ソートキー)で作成したテーブルの場合、LSI で UserId(パーティションキー)+ ScoreDate(ソートキー)を作成することで、スコア記録日時でソートされたクエリが可能になります。
ただし、 LSI はテーブル作成時のみ作成可能で、後から追加できません。
GSI (Global Secondary Index)
GSI は、LSI の制限であった、同じパーティションキーを利用するといった制限がなく、異なるパーティションキーとソートキーを指定できるインデックスです。
ベーステーブルが UserId(パーティションキー)+ GameName(ソートキー)の場合、GSI で GameName(パーティションキー)+ ScoreDate(ソートキー)を作成することで、ゲーム名ごとのハイスコアを日付順でクエリできます。
ただし、GSIはLSIと比べ、整合性やコスト面で考慮点があります。(今回は割愛します)
Composite Key(キー結合)
ソートキーはこれまで同時に複数の属性を指定することはできませんでした。
この対策として、検索条件に利用したい属性を文字列結合することで、一つの属性として扱う方法があります。
例えば、GameNameとScoreDateを使ってデータを取得したい場合、以下のように結合キーGameDateKeyを作成します。
| UserId | GameName | HighScore | ScoreDate | GameDateKey |
|---|---|---|---|---|
| user1 | PuzzleQuest | 15000 | 2025-01-01 | PuzzleQuest#20250101 |
| user1 | SpaceShooter | 28000 | 2025-01-02 | SpaceShooter#20250102 |
| user1 | RacingChamp | 22000 | 2025-01-03 | RacingChamp#20250103 |
| user2 | PuzzleQuest | 18000 | 2025-01-01 | PuzzleQuest#20250101 |
| user2 | SpaceShooter | 32000 | 2025-01-02 | SpaceShooter#20250102 |
GameDateKey という属性を追加し、GameName と ScoreDate を # で結合した値を格納します。
この GameDateKey を GSI のパーティションキーまたはソートキーとして指定することで、ゲーム名と日付の組み合わせで検索できます。
ただ、アプリ側でキー結合が必要だったり、データの整合性に課題がありそうですね。
...ということで、今回のアップデートは今まで一つしか指定できないパーティションキー、ソートキーを複数指定できるようなり、ホットパーティションの軽減や、 複数属性を使った検索ができるようになりました!という内容です。
新機能! マルチ属性複合キーを試してみる。
では、実際に試してみます。
今回はマネジメントコンソールで実施し、先ほど例に挙げてたゲームスコアテーブルに対して、マルチ属性複合キーを試してみます。
テーブル、アイテム作成
まずはこれまでの例の通り、 UserId(パーティションキー)+ GameName(ソートキー)でテーブルを作ります。
手動で 5 件分のデータも作成しておきます。
これで、 UserId(パーティションキー)+ GameName(ソートキー)を使った検索が可能です。
この状態で、フィルタ機能を使い、ScoreDateが2025-01-01のデータを取得しようとすると、取得はできますが、スキャンされた項目: 3となっており、user1パーティションのデータを全て取得していることがわかります。
そこで、次に GSI を作ってみます。
GSI の作成 ①
GSI はテーブル作成後にも追加できるため、作ってみます。
最大でさらに 3 個の属性を追加できます。と記載あります。これが新機能みたいです!
今回は「特定のゲームの、特定の日付のハイスコアランキングを取得したい」という用途で作ってみます。
GameName,ScoreDateをパーティションキーにし、HighScoreをソートキーにします。
これまでだとこれを実現しようとすると、GameName#ScoreDateみたいなキー結合が必要でした。
動作確認 ①
では動作確認してみます。
PuzzleQuestの2025-01-01のデータを確認してみます。
無事取得できました。
なお、パーティションキーですが、複数属性をパーティションキーに指定した場合、クエリ時に全属性の指定が必要の模様です。
GSI の作成 ②
次は、複数ソートキーも試してみます。
「特定のゲームの全期間のスコア(日付順 → スコア順)」を取得するユースケースです。
まず、検証用にPuzzleQuestのデータを追加します。
| UserId | GameName | HighScore | ScoreDate |
|---|---|---|---|
| user3 | PuzzleQuest | 12000 | 2025-01-01 |
| user4 | PuzzleQuest | 20000 | 2025-01-01 |
| user3 | PuzzleQuest | 16000 | 2025-01-02 |
| user4 | PuzzleQuest | 19000 | 2025-01-02 |
これでPuzzleQuestに対して、複数の日付、同じ日付で複数のスコアが存在する状態になります。
次に GSI を作成します。
GameNameをパーティションキーにし、ScoreDate,HighScoreをソートキーにします。
動作確認 ②
まずは、パーティションキーのみで表示してみます。
ソート順である、ScoreDate,HighScoreで並んでいますね。
次にソートキーScoreDateで絞っていきます。
ソートキーは順序が決まっており、今回の場合は最初の項目ScoreDateで絞り込むことができます。
2025-01-01分で絞れましたね。
取得件数、スキャン数ともに 2 件なので、問題なさそうです。
次にHighScoreで絞り込みます。
項目を追加すると、最初のソートキーScoreDateの条件が選択できなくなり、完全一致のみになりましたね。
これは複数ソートキーを使う場合、最後のソートキーのみ、複数の検索条件が利用可能になるという仕様によるものです。
なお、ソートキーの指定順序・検索条件の制限などの詳細は以下を参照してください。
まとめ
DynamoDB のマルチ属性複合キー対応により、GSI で複数属性を組み合わせた検索が可能になりました。
これにより、ホットパーティションの軽減や、アプリ側でのキー結合の手間が減り、設計がシンプルになるケースが増えると思います。
なお、使用時の注意点として以下があります。
- 複数属性をパーティションキーに指定した場合、クエリ時に全属性の指定が必須です
- 複数ソートキーを使用する場合、左から順に指定する必要があり、途中の属性をスキップできません
- 範囲検索(
>,<,BETWEENなど)は最後のソートキーのみ使用可能で、それ以前のソートキーは完全一致(=)のみ指定できます
DynamoDB の使い勝手が良くなって嬉しいなぁ。と感じるアップデートでした!












