ボクココ

個人開発に関するテックブログ

MongoDB のスキーマデザイン

さて、今回はアプリを書くほうのエンジニアにとっては一番大事なスキーマ設計について学んだことをまとめていこう。ここはユーザが増えるにつれて全てのエンジニアが直面する部分なので、予めそれらを予見してスキーマを設計できるようになる必要がある。

正規化と非正規化

ここがMongoDBにおいて最も悩む部分だ。どれをEmbed Document にして、どれをReference にするか、だ。これはそのアプリケーションの特性によってどちらかにするかをアプリエンジニア自身が決定しなければならない。腕の見せ所だ。

ただ、それがしっかりと理解していれば割と簡単に決められる基準はある。

Embed Document References
小さなサブドキュメント 大きなサブドキュメント
頻繁なデータ更新が無い 変わりやすいデータ
データ更新に時間がかかってもいい 一瞬のデータ反映が必要
データの増え方が小さい データの増え方が大きい
取得するのに第二のクエリを実行する必要のあるデータ 結果から除外するデータ
高速読み込み 高速書き込み

Embed Document の欠点

サブドキュメントが他のドキュメントのサブドキュメントと一致するとき

Embed Document は1つのドキュメント内にデータを抱えてしまうので、あるドキュメントとその他のドキュメントで同一のサブドキュメントが存在する場合、読み込みは特に問題ないけど、書き込み時はマッチする全てのサブドキュメントを更新する必要がある。

これは1台のDBサーバとかがダウンしたときに他のDBサーバとのデータ差分が起きる可能性があることを示しており、それらの変更を監視するcron を仕込んだりする必要が出てくるだろう。 そのバッチ処理によって他の同一サブドキュメントをcronで更新するようにするため、データが即座に反映されなくても良い場合はenbed document を使う。

本の中では、リレーションで一般的に言われる one to many を、さらに one to few に2分割して考えるべきだ、としてある。 one to few ならEmbed Document を採用するのを検討するべきだ。例えば住所とかそういうあまり変わらないと思われるもの。

データ領域の問題

Embed Document を採用する場合、さらにデータ最適化に付いて考える必要がある。既にEmbed Documentとして保存されたデータに、追加で情報を保存したい場合、元のデータ確保分を超えてしまうので、MongoDB 内でデータの移動が走る。これが走ってしまうと、ムダな領域がどんどん増えていってしまう恐れがある。(割とめんどいなぁ) これに対応するためには、max size が決まっている場合には予めその分の領域を確保してDBにデータを入れておけばよい。

{
  "_id" : ObjectId(),
  "userId" : ObjectId(),
  "tags" : [],
  "garbage" : ".............................................................................................................."
}

タグがどんどん増えていったとしても、マックスが決まっていればこうしてデータの移動を防いで最適化を行う。tagをpustした瞬間に、garbage$unsetで消して置けばおk。

 コレクションの設計

一般的に似たようなスキーマを持つドキュメントは同じコレクションに保存する。明らかに姿の違うドキュメントがあったとしても、集計する時は同じコレクション内である必要があるからだ。

Reference でたくさん参照を分ければいいというものでもない。MongoDB はJoin をサポートしていないため、必然的に複数回のクエリを投げる必要が出てくる。2回目のクエリ次第では大量のリクエストが流れてしまうし、リクエストのためのパラメータ次第ではDB内のデータを一時的に保持するためのメモリもだいぶ消費する恐れがある。

データベースの設計

一般的にはアプリごとにデータベースを分ければいいと思うんだけど、重要度に応じてデータベースを分けるのも良い戦略だ。 log, activity, user だと明らかに後者のほうが大事な情報なので、それらを分けてRAID構成にするとよい。 注意点としては複数データベースにしたときの制限だ。MongoDBは通常あるデータベースから他のデータベースにデータを直接移動させるのは不可能だ。例えばMapReduceで集計した結果を他のデータベースには保存できない。

スキーマ移行

もちろんスキーマが出来上がった後にスキーマを変えたいということは多いだろう。ここでパッと思いつくのはMySQL風に全てのカラム(フィールド)をかえたり消したりすること。だがMongoDBではそんなことをする必要は無いし、あまり推奨はされていない。もしやる場合は全てのドキュメントが更新されたかをチェックする必要がある。

一般的には version というフィールドをコレクション内に持つ。んでどのバージョンが現在のアプリケーションから受け付けるかを決める。とはいえこれも古いバージョンのスキーマをアプリケーション側でサポートする必要がある。

MongoDB を使うべきでないとき

そもそもの話。

  • MongoDBはトランザクションをサポートしていないので、それを使いたい場合
  • MonogDBはJoin をサポートしていないので、たくさんのJoinが必要なスキーマの場合
  • MongoDBがサポートしていないツールを使いたい場合。例えばWordpress のMySQKツールなど。

参考&おすすめ

以下の書籍はとてもよいです。