Amazon DynamoDB のテーブル設計で悩んだら最初に読もう -これだけ知ればある程度の検索には対応できる-

こんにちは、広野です。

Amazon DynamoDB は NoSQL と言われる類のデータベースですが、検索条件の指定に限りがあり、また検索条件を増やすための設定が構築後に追加できない箇所があります。そのため、最初のテーブル設計の時点で実際に使用する検索条件を意識して構築しないと、最悪データベースの作り直しになってしまいます。

そこで、簡易な検索要件であればこれだけ押さえておけば対応できる!という例をつくってみました。Amazon DynamoDB のテーブル設計でお悩みの方に読んで頂けたらと思います。

本記事では、クエリーによる検索について言及します。フィルターは全件取得の後に条件でデータをフィルターするので、データ数や実行回数が多いと課金が高額になる恐れがあります。また、データ量が多いとレスポンスが一気に遅くなるので、実用的でなくなるケースがあります。あくまで、データ量が少ないときの最終手段としてのみフィルターは使えると考えましょう。

デフォルトのテーブルでできること

まず、パーティションキー、ソートキーにする項目を決めます。
パーティションキーのみ、またはパーティションキーとソートキーの組み合わせでデータが一意になるようにする必要があります。

以下の例は、パーティションキーとソートキーの組み合わせで一意になるケースです。

  • 表1
パーティションキー ソートキー 属性1 属性2
A001 100 いか 軟体動物
A001 200 たこ 軟体動物
A001 300 まぐろ
B001 100 ネッシー 恐竜
B002 101 めだか

以下は、パーティションキーのみの例。

  • 表2
パーティションキー 属性1 属性2
A001 いか 軟体動物
B001 ネッシー 恐竜
B002 めだか

さて、ここで DynamoDB への検索可能な仕様をおさらいします。

  • パーティションキーおよびソートキーにしか検索条件をかけられません。属性に対して検索をかけることはできません。
  • パーティションキーは必ず検索条件に指定する必要があります。ソートキーのみを検索条件にすることはできません。
  • パーティションキーには、完全一致条件のみ指定できます。
    この例では、例えば「Bから始まる」というような部分一致条件は指定できません。
  • 一方、ソートキーには「1から始まる」というような部分一致条件が指定できます。それでも数えるほどのパターンのみですが。もちろん完全一致も指定できます。

指定した条件にマッチした行のデータを1つまたは複数返してくれる、というのが検索でできることです。

表1、表2それぞれに対して「パーティションキーが A001 のもの」という条件で検索すると、
表1 では 3行、表2 では 1行のデータが返ってきます。

表1に対して、「パーティションキーが A001 で、ソートキーが 2 から始まるもの」という条件で検索すると、1行、たこの行が返ってきます。

ここまでが、オプション設定がないデフォルトの DynamoDB テーブルでできる検索です。
アプリからデータを取得することを考えたときに、まずはこの制約だらけの検索条件で済むのかどうかを検証する必要があります。

ソートキーに指定可能な検索条件はこちら。

KeyConditions (レガシー) - Amazon DynamoDB
Amazon DynamoDB のレガシー KeyConditions パラメータの詳細について説明します。

デフォルトのテーブル + テクニック でできること

このデフォルト設定の検索制約のままで、データの格納の仕方によっては検索可能な範囲が広がります。
表1 を例にして考えてみましょう。

  • 表1
パーティションキー ソートキー 属性1 属性2
A001 100 いか 軟体動物
A001 200 たこ 軟体動物
A001 300 まぐろ
B001 100 ネッシー 恐竜
B002 101 めだか

このデータから、「パーティションキーが A001 で、属性2 が軟体動物」であるデータを検索したいとします。
しかし、仕様上、属性を検索条件にすることはできません。既存のソートキーも必要で変えられない、としましょう。

そこで、属性2 をソートキーにぶち込んでしまいます。

  • 表1-T
パーティションキー ソートキー 属性1 属性2 属性3
A001 100#軟体動物 いか 軟体動物 100
A001 200#軟体動物 たこ 軟体動物 200
A001 300#魚 まぐろ 300
B001 100#恐竜 ネッシー 恐竜 100
B002 101#魚 めだか 101

どうでしょう。
これで、部分一致条件を指定可能なソートキーに、属性2のデータが入りました。
元々ソートキーに入っていたデータが扱いづらくなったので、属性3 に同じデータを入れています。

こうすると、「パーティションキーが A001 で、ソートキーに #軟体動物 を含む」という条件をかけることができ、めでたく「いか」と「たこ」のデータを取得できます。

もちろん、元々あったソートキー「100」や「200」で検索したいケースも、「ソートキーが 100# で始まる」という条件で問題なく検索できます。

さらに、元々のソートキーが「100」かつ属性2が「軟体動物」という複合的な検索もできることになりますね。単に「ソートキーが 100#軟体動物である」という検索条件にすればよいだけです。結果は「いか」になります。

このように、ソートキーに検索条件にしたい項目のデータを、区切り文字付きで格納することで解決するケースがあります。結構頭を使うことになりますが、ソートキーに3、4個の項目を放り込むケースもあります。
RDBMS に慣れている人は、データの正規化こそ正義だ、とつい考えてしまいますが、DynamoDB の世界ではその常識は通用しません。そもそもテーブルの結合もできないので正規化とは真逆を行きますし、この例のように同じデータが複数列にまたがるのは当たり前と言ってもよいでしょう。例にはしませんでしたが、パーティションキーに複数項目を区切り文字付きで格納させるケースもあります。

ローカルセカンダリインデックスでできること

さて、ローカルセカンダリインデックスという名前が出てきました。
先ほどの「テクニック」で登場した検索条件と全く同じ例で考えながらその機能を説明します。

  • 表1
パーティションキー ソートキー 属性1 属性2
A001 100 いか 軟体動物
A001 200 たこ 軟体動物
A001 300 まぐろ
B001 100 ネッシー 恐竜
B002 101 めだか

このデータから、「パーティションキーが A001 で、属性2 が軟体動物」であるデータを検索したいとします。
しかし、仕様上、属性を検索条件にすることはできません。既存のソートキーも必要で変えられない、としましょう。

ここで、属性2 をローカルセカンダリインデックスにしてしまいます。

ローカルセカンダリインデックスは、データはそのまま、パーティションキーはそのままで、ソートキーを別の属性に変更することができます。
ただし、元のテーブルは残したままに、ソートキーを変更しただけで全く同じデータを持つもう1つの仮想テーブル(射影)を作るようなイメージです。射影したものに検索をかけるときには元のテーブル名ではなく、このローカルセカンダリインデックスを使うときの別名を指定して検索をかけることになります。

  • 表1-L
パーティションキー ローカルセカンダリインデックスという名の別ソートキー 属性1 属性(元ソートキー)
A001 軟体動物 いか 100
A001 軟体動物 たこ 200
A001 まぐろ 300
B001 恐竜 ネッシー 100
B002 めだか 101

ローカルセカンダリインデックスを指定したら、このようになりました。

これで、「パーティションキーが A001 で、ローカルセカンダリインデックスが 軟体動物」であるデータを検索することができ、めでたく「いか」と「たこ」のデータを取得できます。

元々ソートキーだった項目は属性に成り下がってしまっているため、「100」や「200」のデータでは検索できなくなっています。

このローカルセカンダリインデックスは設定するだけで簡単に使えますが、とんでもない制約があります。

テーブルの初回構築時にしか設定できない という制約です。

つまり、テーブルを構築するときには、ローカルセカンダリインデックスが必要か否かの判断が終わっている必要があります。設計段階で、そこまで確定していなければならないのは結構キツイですが、それが現実です。

1つのテーブルにつき、最大 5 個までしか設定できないという制約もあります。

こんなローカルセカンダリインデックスですが、必要とする機会はあります。というか使えるものはフルに使わないと DynamoDB への検索は成り立たないです。とにかく設計段階から検索要件を漏れなく洗い出すことが重要なポイントです。

グローバルセカンダリインデックスでできること

今度はローカルではなくグローバルセカンダリインデックスの話になります。
例を変えて考えてみましょう。

  • 表1
パーティションキー ソートキー 属性1 属性2
A001 100 いか 軟体動物
A001 200 たこ 軟体動物
A001 300 まぐろ
B001 100 ネッシー 恐竜
B002 101 めだか

このデータから、「属性2 が魚」であるデータを検索したいとします。
しかし、仕様上、属性を検索条件にすることはできません。ローカルセカンダリインデックスを属性2に設定したとしても、パーティションキーが異なるため、このデータの例では片方のデータしか取得できません。

ここで、属性2 をグローバルセカンダリインデックスにしてしまいます。

グローバルセカンダリインデックスは、データはそのままで、パーティションキーを別の属性に変更することができます。
ただし、実際のテーブルとは別に、パーティションキーを変更し全く同じデータを持つもう1つの仮想テーブル(射影)を作るようなイメージです。

  • 表1-G1
グローバルセカンダリインデックスという名のパーティションキー 属性1 属性(元パーティションキー) 属性(元ソートキー)
軟体動物 いか A001 100
軟体動物 たこ A001 200
まぐろ A001 300
恐竜 ネッシー B001 100
めだか B002 101

グローバルセカンダリインデックスを指定したら、このようになりました。

これで、「グローバルセカンダリインデックスという名のパーティションキーが 魚」であるデータを検索することができ、めでたく「まぐろ」と「めだか」のデータを取得できます。

元々パーティションキーだった項目は属性に成り下がってしまっているため、「A001」や「B001」などのデータでは検索できなくなっています。

グローバルセカンダリインデックスには、もう1つの設定の仕方があります。
上の例では、パーティションキーだけをグローバルセカンダリインデックスに指定しましたが、ソートキーもセットで指定することができます。

また表1 に戻って考えてみましょう。

  • 表1
パーティションキー ソートキー 属性1 属性2
A001 100 いか 軟体動物
A001 200 たこ 軟体動物
A001 300 まぐろ
B001 100 ネッシー 恐竜
B002 101 めだか

このデータから、「属性2 が 魚 で、属性1 に “ぐ” が含まれる」データを検索したいとします。
しかし、仕様上、属性を検索条件にすることはできません。

もはやここまでパーティションキーとソートキーを無視した検索条件になると、何のためのパーティションキーとソートキーなんだ?と設計を疑ってしまいたくなりますが、実際にはこのような検索をしたくなりときが有り得ます。そもそもデータ検索が可能な列を2つしか設定できないことに無理がありますので。

ここで、属性2 をグローバルセカンダリインデックスのパーティションキーに、属性1 をグローバルセカンダリインデックスのソートキーにしてしまいます。

  • 表1-G2
グローバルセカンダリインデックスという名のパーティションキー グローバルセカンダリインデックスという名のソートキー 属性(元パーティションキー) 属性(元ソートキー)
軟体動物 いか A001 100
軟体動物 たこ A001 200
まぐろ A001 300
恐竜 ネッシー B001 100
めだか B002 101

グローバルセカンダリインデックスとして新たにパーティションキーとソートキーを指定したら、このようになりました。

これで、「グローバルセカンダリインデックスという名のパーティションキーが 魚」かつ「グローバルセカンダリインデックスという名のソートキーに “ぐ” が含まれる」データを検索することができ、めでたく「まぐろ」のデータを取得できます。

元々パーティションキー、ソートキーだった項目は属性に成り下がってしまっているため、「A001」「B001」、「100」「200」などのデータでは検索できなくなっています。

グローバルセカンダリインデックスは、ローカルセカンダリインデックスと同じく 1 つのテーブルにつき 20 個まで設定可能です。ただし、ローカルセカンダリインデックスとは異なり、テーブル構築後に後から追加で設定できるところが神です。

後から追加で検索条件が必要になった!というときに真っ先に適用を考えたくなるのがグローバルセカンダリインデックスです。しかし 20 個までしか設定できないという制約上、なるべく奥の手として取っておきたいので、可能な限り構築前にローカルセカンダリインデックスで解決しておく、もしくは 1 項目の中に複数データを格納させるテクニックを駆使しておく方が後々の拡張性の余地を残しておくことになるでしょう。

まとめ

いかがでしたでしょうか?

Amazon DynamoDB テーブルは、構築前の設計が大事です。
検索は同時 2 項目にしかできないという制約の中で、1 項目の中に複数データを突っ込むテクニックと、検索可能な項目をすげ替える 2 種類のインデックスをフル活用して、データの持たせ方を工夫して要件に立ち向かってもらえたらと思います。

また、本記事で紹介した以外の検索テクニックはあるのですが、もうおなかいっぱいですし、だいたいの要件はここまでの理解で何とかなると思います。

逆にこれだけの機能やテクニックをフルに駆使しても実現できない検索要件であれば、Amazon DynamoDB の採用はあきらめ、RDS の採用を考えた方が賢明です。

本記事が皆様のお役に立てれば幸いです。

著者について
広野 祐司

AWS サーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリとコンテンツづくりに勤しんでいます。React で SPA を書き始めたら快適すぎて、他の言語には戻れなくなりました。サーバーレス & React 仲間を増やしたいです。AWSは好きですが、それよりもAWSすげー!って気持ちの方が強いです。
取得資格:AWS 認定は12資格、ITサービスマネージャ、ITIL v3 Expert 等
2020 - 2023 Japan AWS Top Engineer 受賞
2022 - 2023 Japan AWS Ambassador 受賞
2023 当社初代フルスタックエンジニア認定
好きなAWSサービス:AWS Amplify / AWS AppSync / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする
クラウドに強いによるエンジニアブログです。
SCSKは専門性と豊富な実績を活かしたクラウドサービス USiZE(ユーサイズ)を提供しています。
USiZEサービスサイトでは、お客様のDX推進をワンストップで支援するサービスの詳細や導入事例を紹介しています。
AWSクラウドソリューションデータベース
シェアする
タイトルとURLをコピーしました