Zenn
🥞

DynamoDBの設計の話

2025/01/22に公開

最近DynamoDBなどのNoSQLのDBを使うケースが増えていると感じます。
しかし、NoSQLなのにSQLと同じように考えてしまうことがよくあると思っています。それだと残念ながらいいDB設計にはなりません。
ということで、この記事ではDynamoDBなどのNoSQLの設計時にとりあえず意識したほうが良いのではということの私見を書きます。

パーティションキーはプライマリーキーではない

パーティションキーはプライマリーキーではありません。分かりきっていることですが、いざ設計する時に抜け落ちてることが多いように思えます。つまり、ついうっかりパーティションキーで一意性を持たせるDB設計をしてしまう、ということがよくあります。

パーティションキーを一意にするとはどういうことか考えてみましょう。
例えば、同じタイトルの漫画本の1〜3巻があり、本棚に格納するとします。この場合、本棚の棚がパーティションキーと考えることができます。さて漫画本に対してパーティションキーが一意であるということは1〜3巻を全て違う棚に格納するということです。
普通そんなことしないですよね。普通に格納するなら同じ棚に並べて格納するかと思います。つまり同じタイトルの漫画本3冊のパーティションキーは同じにするべきということです。

すなわち、パーティションキーは一意な値にするよりも、関連するデータを同じパーティションにまとめる設計にした方が効率的ということです。

DBは最後に設計する。API設計を先にした方が良い。

SQLのDBを使ったアプリの開発をするときは、どちらかというとDB設計を最初にすることが多いのではないでしょうか。しかしDynamoDBを使う場合は、DB設計を最初にするのは難しいケースが多いです。

例えば、あるシステムの「ユーザー」を以下のように定義したとします。

{
  "user_id": "ユーザーID",
  "email": "メールアドレス",
  "name": "名前",
  "department": "所属",
  "created_at": "作成日",
  "updated_at": "更新日"
}

この場合パーティションキーを何にするのが良いでしょうか?
答えは「ユースケースによって違うから判断できない」です。

  • 例えば、所属毎のユーザーの一覧表示が重要なアプリであれば、departmentがよいでしょう。
  • 例えば、所属とかはどうでも良くてユーザー全体を取り扱うのが重要なアプリなら、パーティションキーを固定文字列(例えば「user」など)にするのもよいでしょう。
  • 例えば、月毎の新規ユーザー数の集計が主要機能であれば、created_atの年月がパーティションキーになるかもしれません。
  • 例えば、ブログなどのアプリでユーザーの一覧性よりもユーザーに紐づく記事とセットで取得できることが重視されるなら、user_idをパーティションキーにすることが考えられます。

という感じでデータ構造を決めただけではDB設計は出来ません。データの使い方が重要になります。ですので、DB設計よりも前にAPIの設計が必要で、もっというとフロントエンド→API→DBの順で設計するのがよいと思います。
APIを先に設計することで、以下のようなメリットがあります:

  • データのユースケースが明確になる。
  • 必要なクエリの効率性が検討できる。
  • パフォーマンス要件が具体化される。

パーティションキー(とソートキー)は、DB設計の中でも最後に決める

前章で記載したように、結局のところパーティションキーというのは論理的なデータモデルではなく、データの使われ方に紐づきます。
つまり「何のデータを保存するか」という論理的な項目の設計を前提とした上で、「どのようにデータにアクセスするか」を定義することがパーティションキーを決めるということになります。
例えば、ユーザー情報の定義の場合、SQLであればプライマリーキーを決める事が重要で、それは論理的なデータモデルとしてのキーであり素直に考えればuser_idであることを最初に決める事ができます。しかし、DynamoDBのパーティションキーは「どのようにデータを使うか」によって変わってきますので、必要なデータを一通り定義した後にユースケースに応じて定義する必要があります。

パーティションキーはpk、ソートキーはskといった専用フィールドを追加で用意する

これも前章の内容に付随することなのですが、パーティションキーやソートキーはデータの論理的な意味を表すのではなく「どのようにデータにアクセスするか」を表すキーです。となると、論理的な意味を持つ既存のフィールドとは分離して設計したほうが、後々の開発や保守がしやすくなります。
例えば、論理的なフィールドを流用してuser_idをパーティションキーに直接指定したとします。するとそのテーブルは、ユーザーID以外をパーティションキーとして使うことができなくなります。
これにより以下のような問題が発生します:

  • データの使い方が変わったときや別のユースケースが発生した場合の制約になる。またはテーブルの作り直しになる。
  • データの分散最適化の為に値を加工したい場合に柔軟性が失われる。

一方で、専用のフィールドとしてpkやskを定義しておけば、制約が発生するフィールドは論理的なデータとしての意味を持たないので、柔軟な対応が可能になります。

  • ユースケースの変化や追加に柔軟に対応できる。
  • データの最適化に柔軟に対応できる。
  • ビジネスロジックとデータアクセスを明確に分離した設計になる。

そしてなによりこのような設計にすることでシングルテーブル設計に対応でき、よりNoSQLっぽいテーブルデザインを実現できます!!
今回はシングルテーブル設計については深堀りしませんので、とりあえず参考資料を貼っておきます。
https://aws.amazon.com/jp/blogs/news/single-table-vs-multi-table-design-in-amazon-dynamodb/
https://www.alexdebrie.com/posts/dynamodb-single-table/

非正規化するというより正規化しない

ちょっと話が変わります。
DynamoDBの説明記事とかを見るとよく「非正規化」しましょうと書いてあります。公式にも書いてあります。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-modeling-nosql.html

しかし、個人的には、ちょっと違うんじゃないかなと思います。つまり「最初から正規化しない」があるべき姿ではないかと。
例えば、飲食店で注文することを考えてみましょう。SQLでは注文テーブルと注文詳細テーブルに分けてデータを保存するのが一般的です。ですが、実際に注文する時に「注文詳細が〜」とか考える人は少ないですよね。NoSQLでは注文時点の情報(商品名、注文数、価格、etc)をそのまま1つのデータとして保存すれば良いのです。そこには「正規化」や「非正規化」というプロセスは発生しません。
「非正規化」というのは、正規化された状態から敢えてその状態を崩す状態を指します。これはSQLの設計思想にとらわれていると思います。より適切な考え方としては、必要なデータを「そのまま」保存することです。SQLに染まってしまった開発者の皆様は一旦頭をリセットして、ありのままのデータを扱うことを意識しましょう。

まとめ

  • パーティションキーには一意性を持たせない。
  • アプリの設計の中でDBは最後に設計する。フロントエンド→API→DBの順がよい。
  • DB設計の中ではパーティションキー(とソートキー)を最後に設計する。論理的なデータモデルの設計が先に必要。
  • パーティションキーやソートキーはデータの使い方を決めるキーなので、user_idなどの論理的な意味を持つキーとは別の専用フィールド(pk/skなど)として定義する。
  • 正規化などのSQLに染まった考えはリセットして、ありのままデータを扱うことを意識する。
NCDCエンジニアブログ

Discussion

ログインするとコメントできます