こんにちは、広野です。
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行、たこの行が返ってきます。
アプリからデータを取得することを考えたときに、まずはこの制約だらけの検索条件で済むのかどうかを検証する必要があります。
ソートキーに指定可能な検索条件はこちら。
デフォルトのテーブル + テクニック でできること
このデフォルト設定の検索制約のままで、データの格納の仕方によっては検索可能な範囲が広がります。
表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#軟体動物である」という検索条件にすればよいだけです。結果は「いか」になります。
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 個までしか設定できないという制約もあります。
グローバルセカンダリインデックスでできること
今度はローカルではなくグローバルセカンダリインデックスの話になります。
例を変えて考えてみましょう。
- 表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 個まで設定可能です。ただし、ローカルセカンダリインデックスとは異なり、テーブル構築後に後から追加で設定できるところが神です。
まとめ
いかがでしたでしょうか?
Amazon DynamoDB テーブルは、構築前の設計が大事です。
検索は同時 2 項目にしかできないという制約の中で、1 項目の中に複数データを突っ込むテクニックと、検索可能な項目をすげ替える 2 種類のインデックスをフル活用して、データの持たせ方を工夫して要件に立ち向かってもらえたらと思います。
また、本記事で紹介した以外の検索テクニックはあるのですが、もうおなかいっぱいですし、だいたいの要件はここまでの理解で何とかなると思います。
逆にこれだけの機能やテクニックをフルに駆使しても実現できない検索要件であれば、Amazon DynamoDB の採用はあきらめ、RDS の採用を考えた方が賢明です。
本記事が皆様のお役に立てれば幸いです。