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

良きダイナモライフを!