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の場合は、必ず μmongo
の Document Class
を使って定義をすれば、ソフトウエア側である程度データ型の管理ができる。ざっくりプロトタイピング的に作る時も、書式は結構簡単なので、利用をすすめたい。このお作法自体どうよ、とツッコミも多くもらうが、ある程度のサービスでは耐えられると思う。
また、古くからmongoを使っていて、ドキュメントデータのフォーマットにばらつきが出ている場合、データのマイニングや集計などをする時には、ODMは、結構重要。
参照: μmongo (umongo) を使ってmongodb documentデータをcsv へコンバートしてみる
まとめ
3つのポイントを押さえておけば、スキーマ設計での苦悩が軽減されると考えている。他にもっとケアーしなければならないことがあるかもしれないが、みなさんがどう考えるか聞きたいところ。あと、MongoDBを利用してSnapDishのようなサービスを開発する際は参考にしてもらえたらと。