皆さんはDynamoDBって使ったことはありますか?
かなり癖のあるNoSQLデータベースです。
僕はそこそこ使っていますが、いまだに最適解な使い方をしている自信があまりない。
今回はそんなDynamoDBを使って、複雑な検索が可能となるテーブル設計を考えていきます。
目次
● 仕様の整理
素直にRDBを使った方がいい
例えばこんな検索フォームがあったとしましょう
検索して一覧をポンと出す。よくある機能ですよね。
はい、これ系のデータ抽出はDynamoDBめっちゃ苦手です。
MySQLやOracleなどRDBを使用した方が幸せになれます。
商品名 LIKE "%リンゴ%"
登録日 BETWEEN "2024-10-01" AND "2024-12-31"
こんな感じで簡単に取得できますよね。
複合検索を取り入れる場合は、素直にRDBを使用しましょう。NoSQLではしんどいです。
でも、どうしてもDynamoDBじゃないとダメ!ってときがありますよね。
- VPCを挟ませたくない、インフラをシンプルにしたい
- RDSやEC2だと1時間単位でコストが掛かって割に合わない
主な理由はこんな感じだと思います。
小規模なシステムやあまりPV数も高くないようなサービスの場合は、1時間に数ドルも払いたくないよね。
仕様の整理
まずはDynamoDBの仕様を再確認しましょう。
DynamoDBにおけるプライマリキーは2種類あって、
- パーティションキー(PK)
- ソートキー(SK)
この2つが存在し、組み合わせは、
- PKのみ
- PK + SK
この2通りが指定可能。
ここまでは「RDBの複合プライマリキーと同じだよね」って思いそうですが、DynamoDBのデータ取得には強烈すぎる仕様があって、
- PKへの条件指定は完全一致のみが可能
- SKは範囲や前方一致などある程度の条件指定にも対応しているが、SKのみでの指定は不可
- SKに条件を指定する場合は、必ずPKにも条件を指定しなければならない
- PKとSK以外の項目には条件を指定できない
これらの仕様と戦わなければならない。戦っていきます。
テーブル設計の考え方
先ほどの商品検索フォームをベースに考えます。
検索項目には、
- 商品名
- 価格
- 登録日
これら3つが存在しています。
ではでは、商品テーブルを考えていきましょう。
まずはNG設計から
▼商品テーブル(NG)
項目名 | 備考 |
商品ID | PK |
商品名 | |
価格 | |
登録日 |
なんとなく大丈夫そうに見えますが、この設計では何も検索できないです。
なぜなら、
4.PKとSK以外の項目には条件を指定できない
この仕様に引っ掛かってしまい、商品ID以外の項目には条件を付与することができません。
RDBだと簡単なのにね
ではどうやって商品名とかに条件を付与するのかというと、SKを設定するしかないのです。
▼商品テーブル(SK追加)
項目名 | 備考 |
商品ID | PK |
商品名 | SK |
価格 | |
登録日 |
これで商品名に対しては条件を付与できるようになりましたが、ここでも問題が発生します。
1.PKへの条件指定は完全一致のみが可能
3.SKに条件を指定する場合は、必ずPKにも条件を指定しなければならない
3の仕様が重くのしかかり、SKのみでは検索できません。
これだけでもしんどいのですが、1の仕様がエンジニアにとどめを刺してきます。
OKパターン(ゴリ押し)
複合検索が可能となるテーブル設計の一例です。
▼商品テーブル
項目名 | 備考 |
商品ID | PK |
検索キー | "SEARCH" という固定値をセット |
商品名 | |
価格 | |
登録日 |
▼商品名検索用-GSI
項目名 | 備考 |
検索キー | PK |
商品名 | SK |
商品ID |
▼価格検索用-GSI
項目名 | 備考 |
検索キー | PK |
価格 | SK |
商品ID |
▼登録日検索用-GSI
項目名 | 備考 |
検索キー | PK |
登録日 | SK |
商品ID |
いきなりテーブルが増えましたが、ぞれぞれ解説します。
まずは商品テーブルに「検索キー」という固定値が入る項目を追加しました。
これには後述するGSIのPKとしての役割を与えます。
データのイメージはこんな感じです。
商品ID(PK) | 検索キー | 商品名 | 価格 | 登録日 |
00001 | SEARCH | りんご | 100 | 2024-05-01 |
00002 | SEARCH | いちご | 80 | 2024-05-10 |
00003 | SEARCH | ぶどう | 300 | 2024-06-01 |
00004 | SEARCH | みかん | 110 | 2024-06-30 |
00005 | SEARCH | すいか | 250 | 2024-07-01 |
そして、3つのGSIを作成しました。
それぞれ検索キーをPKとし、検索項目をSKとしたGSIとなります。
この形を取ることで、DynamoDBの仕様を全てクリアできます。
例えば商品名で前方一致検索を行う場合は、商品名検索用-GSIに対して、
検索キー = "SEARCH"
商品名 LIKE "りんご%"
※分かりやすくSQLベースで書いています。
これでリンゴから始まる商品IDを取得することができます。
そう、GSIのPKを全て固定値に設定するという手法を取りました。
これはホットパーティションといって、あまり褒められた方法ではないのですが、複合検索を行うにはこれ以外の方法が見当たらないです。
だからこそ、GSIには全てのデータをセットするのではなく、商品IDのみを設定するなど、少しでもデータ容量を軽くする設計が大切となります。
この設計により、例えば価格と登録日の2つに条件が指定された場合でも、それぞれのGSIから条件にヒットした商品IDを取得し、その商品IDをもとに商品テーブルからデータを一覧取得することが可能となります。
1.価格検索用-GSIより、価格が150円未満の商品IDを取得する
価格検索用-GSI.検索キー = "SEARCH"
価格検索用-GSI.価格 < 150
2.登録日検索用-GSIより、登録日が "2024-05-01" ~ "2024-06-30" までの商品IDを取得する
登録日索用-GSI.検索キー = "SEARCH"
登録日索用-GSI.登録日 BETWEEN "2024-05-01" AND "2024-06-30"
3.商品テーブルより、1と2で取得した商品IDをもとにデータを取得する
商品テーブル.商品ID IN (1と2の商品ID)
▼取得結果
商品ID(PK) | 検索キー | 商品名 | 価格 | 登録日 |
00002 | SEARCH | いちご | 80 | 2024-05-10 |
00004 | SEARCH | みかん | 110 | 2024-06-30 |
どうしてもDynamoDBってときにだけ
繰り返しになりますが、DynamoDBに複合条件検索は不向きです。
こんな回りくどいことをするのなら、RDBを使用した方が絶対に楽です。
でも色々な理由でDynamoDBが選ばれることもあるかと思うので、その時に役立てればいいなぁ
良きダイナモライフを!