Edited at

MongoDBのスキーマデザインする時の3つのポイント

SnapDishではDBにMongoDBを使っている。バージョンが1.4ぐらいの時から使っていて、色々と試行錯誤をしながら使ってきた。不勉強もあり今だに負の遺産が残る中、現在開発しているサービスでMongoDBをどのように利用したらよいか再度考えてみることに。

振り返るとやはりスキーマデザインが常に一番悩むところ。ただ、悩んでいる時間が惜しいので、決断を早くするために、スキーマの設計をするにあたり一定の考え方を設けることに。

それで、できるだけシンプルにということを念頭に、今更ながらだけど 3つのポイント にまとめてみることに。

様々な意見や議論のある領域だと思うので、これを読んだ方で、「この場合はどうなる?」や「ツッコミ」などあれば、ぜひコメンントや意見を頂きたところ。今後の参考したい。また、「その考え方はこういうことだよ」という、適切な表現や言葉があれば教授してもらいたい。

まず、、、

SnapDishは料理サービスなので、料理詳細情報をどう取得するかを題材に書いてみることにした。

※Sharding環境に関しては全く考慮しいない


料理詳細ページ生成のためのスキーマイメージ

# 利用するコレクション

# dish.collection - 料理情報を保持する
# dish_comment.collection - 料理情報に対するコメント
# user.collection - ユーザー情報を保持する

# 料理写真詳細コレクションイメージ
db.dish.collection = {
"_id": ObjectId(),
"usr_id": db.user.collection._id,
# 以下の様にしたいところだが、apiなどでデータを返す場合返す時に、情報を再取得してデータを返す
#"usr": {
# "_id": ObjectId(), # ユーザーのObjectId
# "unm": "名前",
# "anm": "アカウント名",
# "img": {画像URL},
# },
"nm": "料理名",
"rcp": {レシピ}, # 無制限に肥大しないので自由にフィールド設定ができる。MongoDBの得意なところ
"img": {画像URL},
"tag": [タグ,,,],
"tm": datetime,
# 以下のようにしたいところだが、別コレクションにする
# "cmts": [{コメント},]
...
}

# 料理写真詳細へのコメントコレクションイメージ
# コメントは無制限に増えるので、別のコレクションにしたほうがよい
db.dish_comment.collection = {
"_id": ObjectId(),
"dsh_id": dish.collection._id,
"usr_id": user.collection._id,
"txt": "コメント",
"tm": datetime,
"mt": ["メンションされたusr_id",,,],
...
}

# ユーザー情報コレクションイメージ
db.user.collection =
"_id": ObjectId(), # ユーザーのObjectId
"unm": "名前",
"anm": "アカウント名",
"img": {画像URL},
...
}

上記のスキーマで料理の詳細データを取得する際以下の流れになる



  • DBアクセス 料理データを_idで取得


  • DBアクセス dsh_idを参照しているコメントデータを取得

  • 料理データとコメントデーターからuser._idを抽出


  • DBアクセス user._idを使ってユーザーのデータを取得

  • dish.collection.data と dish_comment.collection.data とユーザーデータをマージし、レスポンスデータを生成


3つのポイント


無制限に増えるフィールドは別コレクションにする

メモリーにあるデータの取得は早いので別コレクションでも全く問題はない。また、上記の場合 DBへアクセス は3回になるが、メモリキャッシュをうまく利用すると DBアクセス 回数は容易に減らせ、場合によってはレスポンスの向上はできる。なので、ドキュメントが肥大化する場合、コレクションを分けて、アプリケーション側で処理をしても問題はない。


フィールド名は短くても構わない

フィールド名も要領を食うので、フィールド名はなるべくコンパクトにする。実際、ある程度意味が推測できればそこまで問題にはなっていない。直感的にわからないという重大な課題はあるが、最終的には慣れる(強気でいまのところ通している)。ただし、仕事の現場は、場合によっては引き継ぎなどもあるので スキーマのフィールド名の説明をしたドキュメント はもちろんしっかり残しておく必要はある。


MongoDBはシンプルなクエリーでデータを取得するためのストレージとして使う

集計や検索は別のサービスに任せるか、書き込み時に非同期で別に渡すなど工夫をするとよい。単純な条件でデータを取得することにを一番重視し、それ以外のことは、別途考えるか別に任せる。やはりMongoDBは Slow-Queryが最大の地雷 で、設計段階でその可能性を軽減するということが大切だと考える。


ODM(object document mapper)を必ず使おう

非同期I/O non-blocking処理を行いたい場合(使わないとあまり恩恵はないので使ったほうが良い)は、 μmonogo を使って、document format定義を事前に行うと良い。write operationの場合は、必ず μmongoDocument Class を使って定義をすれば、ソフトウエア側である程度データ型の管理ができる。ざっくりプロトタイピング的に作る時も、書式は結構簡単なので、利用をすすめたい。このお作法自体どうよ、とツッコミも多くもらうが、ある程度のサービスでは耐えられると思う。

また、古くからmongoを使っていて、ドキュメントデータのフォーマットにばらつきが出ている場合、データのマイニングや集計などをする時には、ODMは、結構重要。

参照: μmongo (umongo) を使ってmongodb documentデータをcsv へコンバートしてみる


まとめ

3つのポイントを押さえておけば、スキーマ設計での苦悩が軽減されると考えている。他にもっとケアーしなければならないことがあるかもしれないが、みなさんがどう考えるか聞きたいところ。あと、MongoDBを利用してSnapDishのようなサービスを開発する際は参考にしてもらえたらと。


関連記事