【DynamoDB】複合検索を可能にするテーブル設計

マッシュルーム
2024-10-16
2024-10-16

皆さんはDynamoDBって使ったことはありますか?
かなり癖のあるNoSQLデータベースです。

僕はそこそこ使っていますが、いまだに最適解な使い方をしている自信があまりない。

今回はそんなDynamoDBを使って、複雑な検索が可能となるテーブル設計を考えていきます。

目次

素直にRDBを使った方がいい

仕様の整理

テーブル設計の考え方

OKパターン(ゴリ押し)

どうしてもDynamoDBってときにだけ

 


素直にRDBを使った方がいい

例えばこんな検索フォームがあったとしましょう
dynamodb_search_form
検索して一覧をポンと出す。よくある機能ですよね。

はい、これ系のデータ抽出は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のデータ取得には強烈すぎる仕様があって、

  1. PKへの条件指定は完全一致のみが可能
  2. SKは範囲や前方一致などある程度の条件指定にも対応しているが、SKのみでの指定は不可
  3. SKに条件を指定する場合は、必ずPKにも条件を指定しなければならない
  4. PKとSK以外の項目には条件を指定できない


これらの仕様と戦わなければならない。戦っていきます。

 


テーブル設計の考え方

先ほどの商品検索フォームをベースに考えます。
dynamodb_search_form
検索項目には、

  • 商品名
  • 価格
  • 登録日

これら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が選ばれることもあるかと思うので、その時に役立てればいいなぁ

良きダイナモライフを!