後悔しないもんごもんごの使い方 〜アプリ編〜
DESCRIPTION
BPStudy 71回で発表した、MongoDBのアプリ寄りの使い方の話。TRANSCRIPT
後悔しないもんごもんごの使い方
~アプリ編~松下 雅和(@matsukaz)
自己紹介
•松下 雅和•サーバ寄りのエンジニア•Twitter: @matsukaz•あだな:まつかず•DevLOVE スタッフ
今日の話•開発しやすいってほんと?•おすすめライブラリ(Java/Node.jsの話)
•データ設計はどうやるの?•こういう使い方するといいかも
開発しやすいってほんと?
ほんとだよ!
公式ドキュメントにも
MongoDB is an open-source, document-oriented database designed for ease of development and scaling.
MongoDBは、開発しやすさとスケールしやすさを目指したオープンソースのドキュメント指向DBです。
と書いてある
開発しやすいポイント①
インストールが簡単!
環境ごとのファイルをDLして展開するだけ
もろもろのパッケージにも対応
あとは起動するだけ
$ bin/mongod --fork \ --dbpath ./data \ --logpath ./logs/mongod.log
※ 必要最小限なオプションのみ
なにこれ簡単すぎ…?
@mongodb
>>あなたの適正もんごは?
開発しやすいポイント②
スキーマレス
どころか、DBやコレクションの
作成も不要(勝手に作られる)
$ bin/mongo
> use db1; // 操作するとDBが作られるswitched to db db1
// collectionが存在しなかったら作られる> db.user.save( { name : "matsukaz" } );
つまり簡単に使う分にはDB側の準備が全くいらない
まぢかよもんご!!!!クールすぎるぜもんご!そこにしびr(ry
開発しやすいポイント③
ドキュメント指向
RDBでやってた正規化とかめんどくない?
階層構造で持てるなら非正規化のままでもいいんでない?
プログラム上のオブジェクト構造をそのまま永続化も可能だし
試しにNode.jsで実装してみる
$ npm install mongodb # ドライバーのインストール
$ vi index.js
$ node index.js # 実行{ name: 'matsukaz', _id: 51f7efbed66a41de0f000001 }
var MongoClient = require("mongodb").MongoClient;var url = "mongodb://127.0.0.1:27017/db1";var data = {name : "matsukaz", age : 34}; // 作成したデータ
MongoClient.connect(url, function(err, db) { // 接続 var userColl = db.collection("user"); userColl.save(data, function(err) { // データを保存 userColl.findOne({name: "matsukaz"}, function(err, user) { // 名前で検索 console.log(user); process.exit(0); }); });});
こんだけ簡単だと使わない手はないよね!
おすすめライブラリ
Java•mongo-java-driver• https://github.com/mongodb/mongo-java-driver•MongoDBの公式ドライバー•ローレベルAPIなので操作はかなり面倒
Java•mongo-java-driver•コネクションプールのチューニングは必須
項目 意味 値connectionsPerHost コネクション数 100threadsAllowedToBlockForConnectionMultiplier
1コネクション辺りの接続待ち数 4
•上記の例だと、プールの上限は 100 + (100 * 4) = 500
Java•Spring Data - MongoDB• http://www.springsource.org/spring-data/mongodb•ドキュメントとPOJO間マッパー•mongo-java-driverを内部で利用
public class User { private String id; private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } // 各プロパティのgetter}
MongoTemplate mongoTemplate = new MongoTemplate(new Mongo(url), "dbname");// ドキュメント作成mongoTemplate.insert(new User("matsukaz", 34));// ドキュメント取得User matsukaz = mongoTemplate.findOne( new Query(Criteria.where("name").is("matsukaz")}, USer.class);
Javaでも言うほど相性は悪くない
Node.js•node-mongodb-native• https://github.com/mongodb/node-mongodb-native•MongoDBの公式ドライバー•スキーマ定義が不要ならこれで
require("mongodb").MongoClient.connect(url, function(err, db) { var userCollection = db.collection("user"); // ドキュメント作成 userCollection.save({name:"matsukaz", age:34}, function(err){ // ドキュメント取得 userCollection.findOne({name: "matsukaz"}, function(err, docs){ console.log(docs); }); });});
Node.js•Mongoose• http://mongoosejs.com•ドキュメントとオブジェクト間マッパー•node-mongodb-nativeを内部で利用
var db = require("mongoose").connect(url);// スキーマ定義が必要var User = db.model("user", new Schema({ name : { type : String, unique : true}, age : Number}));
var matsukaz = new User({name:"matsukaz", age:34});matsukaz.save(function(err) { // ドキュメント作成&取得 User.findOne({name:"matsukaz"}, function(err, user){ console.log(user); });});
Node.jsと本当に相性がいい!
MongoDBを使うと「開発がしやすくなる」のは伝わりました?
んでは次、
データ設計はどうやるの?
開発は簡単だけど、何も考えずに
データ構造を決めると運用で死にます・・・
データ構造を決める上で大事なポイントを整理
BSONの特徴に合わせる•BSONは複雑なデータ構造を扱える •RDBとは違い、積極的に階層化/非正規化
{ "facebookId" : xxx, "status" : { "lv" : 10, "coin" : 9999, ... }, "layerInfo" : "1|1|0|1|2|1|1|3|1|1|4|0...", "structure" : { "1|1" : { "id" : xxxx, "depth" : 3, "width" : 3, ... }, "4|8" : { "id" : xxxx, "depth" : 2, "width" : 2, ... } }, "neighbor" : [ { "id" : xxx, ... }, { "id" : xxx, ... } ]}
BSONの特徴に合わせる•スキーマレス = データ構造の変更が容易•開発を進めながら最適な構造にしていく•後方互換性も意識する
{ "dataId" : xxx, "update" : { "user" : xxx, "time" : xxx, "from" : "API" }}
{ "dataId" : xxx, "updateUser" : xxx }
{ "dataId" : xxx, "update" : { "user" : xxx }}
互換性を保ちやすい構造で
項目の追加は互換性が保ちやすい
BSONの特徴に合わせる•リレーションは、パフォーマンスも考えた上で、利用の有無とやり方を検討
•独自IDを利用
•ObjectIdを利用
•DBRefを利用
{ "userId" : 123, "manager" : 456 }
{ "userId" : 123, "manager" : ObjectId("4ba550e2b...") }
{ "userId" : 123, "manager" : DBRef("user", ObjectId("4ba550e2b...")) }
データ型の差異に注意•クライアントによっては、データ型のマッピングが異なるので注意が必要
•コンソール(mongo)•import/exportツール•言語別のドライバー/ライブラリ
苦手な部分は事前に考慮•トランザクションはない前提で•1ドキュメントにおけるデータ量/フィールド数が多すぎないように
•場合によってはデータを圧縮"layerInfo" : { "1|1" : 0, "1|2" : 1, ...}
"layerInfo" : "1|1|0|1|2|1|1|3|1|1|4|0..."
苦手な部分は事前に考慮•DBレベルのロックがかかる(v2.4時点)ので、アクセス頻度は極力減らす
•場合によっては、同じシステム内のコレクションでも、複数のDBに分割して保存する
シャードキーは慎重に•カーディナリティが低い値は使わない•利用頻度の高いデータがメモリ上に乗り、低いデータはメモリ上に乗らないように
•参照や更新が多いデータはバランスよく各Shardに分散
シャードキーは慎重に•極力Targetedオペレーションにする•Shard Keyでデータを操作•Shard Key以外の操作はIndexを利用
Operation Typedb.foo.find( { ShardKey : 1 } ) Targeteddb.foo.find( { ShardKey : 1, NonShardKey : 1 } ) Targeteddb.foo.find( { NonShardKey : 1 } ) Globaldb.foo.insert( <object> ) Targeteddb.foo.update( { ShardKey : 1 }, <object> )db.foo.remove( { ShardKey : 1 } )
Targeted
db.foo.update( { NonShardKey : 1 }, <object> )db.foo.remove( { NonShardKey : 1 } )
Global
気をつける点はそれなりに多いけど、
もんごの特性を理解していれば、
当たり前に考慮できるようになる・・はず
まずは使ってみることが大事!!
最後に、こういう使い方するといいかもという話
プロトタイプ開発•複雑なデータ構造を簡単に扱えるので、開発スピード重視で作れる
•スキーマレスなので、実装しながらデータ構造を変更できる
•ちょっとしたゲームのプロトタイプ開発(+3回の仕様変更)が2週間で出来たよ
開発中•開発中は以下のような方法を取ることが多い•各自ローカルでMongoDBを起動して開発•共用のMongoDBサーバを用意して用途別にDBを割り当てる
•各自の開発用•DEV環境用•Jenkinsによる単体テスト用
負荷テスト•コマンドラインツール(mongo)などから簡単に大量データを投入
•chunk移動しまくるので落ち着くまで注意> // 1000万件のテストデータを作成> function pad(str, length) { str = String(str); while (str.length < length) str = "0" + str; return str; }> for (var i = 1; i <= 10000000; i++) { db.user.save({name: ("hoge" + pad(i, 10)) }); }
障害テスト•障害を想定した動作検証は必ずやる。絶対。•mongodが落ちた場合•PRIMARYへ昇格するか•復帰後にデータが正しく同期されるか•mongocが落ちた場合に影響がないか•mongosが落ちた場合のシステムの挙動•バックアップから戻せるか
まとめ•開発での利用はほんとに簡単!•とりあえず作ってみよう!という場面で使えるのはもちろん
•使いどころを間違えなければサービスでもちゃんと使える
•もんごもんご!
宣伝!
•WEB+DB PRESS Vol.75 に 「MongoDB実践入門」を書きました!
ご清聴ありがとうございました!