MongoDB紹介
2012/5/18 matsumura
MongoDBってなんぞ - 多機能 but 発展途上 • ドキュメント指向データベース
o 最新2.0.5 • 自動シャーディング
o Read / Writeがスケールアウト
• 自動フェイルオーバー o Master deadでも自動でフェイルオーバー
• 柔軟なクエリ o SQLで可能なことはJOIN句以外 一通りできる
• スキーマレス o データによって自由に持つものを決められる
他にも多機能
構成例
Web mongos
Web mongos
Mongod (config)
Mongod (config)
Mongod (config)
meta
Replica set
mongod
mongod
mongod
Replica set
mongod
mongod
mongod
data data
3processで最適化 最小構成台数
3process
基本的なデータの持ち方
mongod
データベース etcr
データベース other
コレクション 行動履歴 コレクション XXマスタ
コレクション 各種ログ
doc doc doc
doc doc doc
doc doc doc
doc
doc doc doc
mysqld
MySQLで例えると
データベース
テーブル
レコード
レプリカセット - MySQL同様
Mongod (Primary)
データベース etcr
コレクション 行動履歴
docA docB docC
docD docE docF
データベース local
コレクション oplog
操作 操作 操作
Mongod(Secondary)
データベース etcr
コレクション 行動履歴
docA docB docC
docD docE docF
データベース local
コレクション oplog
操作 操作 操作
Mongod(Secondary)
データベース etcr
コレクション 行動履歴
docA docB docC
docD docE docF
データベース local
コレクション oplog
操作 操作 操作 同期
再現 再現
自動フェイルオーバー • Primaryが死ぬ
• Primaryが死んだことがreplica set内で共有 • 残ったノードで投票を行う • ノードごとの優先度設定、最終同期時刻をもとに投票を行う
• 過半数より多くの票を集めたノードがPrimaryとなる
• この間 約20s ~ 60s
Mongod (Shard A)
自動シャーディング - phase1
データベース etcr
コレクション 行動履歴
Mongod (Shard B) データベース etcr
コレクション 行動履歴
Chunk ( –無限大 〜 無限大]
docA docB docC
デフォルト 200MB
Mongod (Shard A)
自動シャーディング - phase2
データベース etcr
コレクション 行動履歴
Mongod (Shard B) データベース etcr
コレクション 行動履歴
Chunk ( –無限大 〜 D]
docA docB docC
Chunk ( D 〜 無限大]
docD docE docF
Mongod (Shard A)
自動シャーディング - phase3
データベース etcr
コレクション 行動履歴
Mongod (Shard B) データベース etcr
コレクション 行動履歴
Chunk ( –無限大 〜 D] docA docB docC
Chunk ( D 〜 無限大]
docD docE docF
docC’ docC’’ docC’’’
Mongod (Shard A)
Chunk (C’ 〜 D]
自動シャーディング - phase4
データベース etcr
コレクション 行動履歴
Mongod (Shard B) データベース etcr
コレクション 行動履歴
Chunk ( –無限大 〜 C’]
docA docB docC
Chunk ( D 〜 無限大]
docD docE docF
docC’ docC’’ docC’’’
Mongod (Shard A)
Chunk (C’ 〜 D]
自動シャーディング - phase5
データベース etcr
コレクション 行動履歴
Mongod (Shard B) データベース etcr
コレクション 行動履歴
Chunk ( –無限大 〜 C’]
docA docB docC
Chunk ( D 〜 無限大]
docD docE docF
docC’ docC’’ docC’’’ 水平方向に スケールアウト
自動シャーディング - phase6
• Sharding Demo
スキーマレス
• Create table, Create column family 不要 o Insertした時点で作られる o アプリ要件に合わせて柔軟に入れられる Item1 = {
_id: ObjectId('4b0552b0f0da7d1eb6f12xxx'), name: 秘薬, price: 100,
} Item2 = {
_id: ObjectId('4b0552b0f0da7d1eb6f12yyy'), name: 自分用秘薬,
}
柔軟なクエリ
• SQL文を持たない • Demo • ハッシュでO/Rマッパーのように指定する
o フロントjavascriptからクエリオブジェクトを送って、サーバーサイドでは検証後、即実行できる
クエリ 周辺の話 (1)
• Index(B-Tree) o 配列やオブジェクトに対してもはれる
§ ただし、配列は1つ / indexに制限 o メモリに乗るようにintを使うと吉
// indexをつける db.test.ensureIndex({x:1, y:1, z:1}) ○ db.test.find({x:'a'}) ○ db.test.find({x:'a', y:'b'}) ○ db.test.find({x:'a', y:'b'}).sort({z:1}) // 順序が重要 × db.test.find({y:'b', x:'a'})
クエリ 周辺の話(2)
• クエリオプティマイザ o MySQLのようなコストベースではない o 初回のクエリで複数クエリプランを同時実行 o 最も早かったクエリを利用 o データ量に応じて定期的に見直し o explain()
クエリ 周辺の話(3)
• Capped コレクション o あらかじめサイズを決めたコレクション o 古いものから順次消えていく o 挿入順での検索で高速 o Shardingできない o 削除不能 o Create文を明示的に発行して作成
§ db.createCollection("mycoll", {capped:true, size:100000})
クエリ findの話 (1) • 検索条件は、bsonオブジェクトの先頭に寄せる
{ owner_id: 123, request: { type: '合成', params: {}}, process: {category: 'composit'}, memo: ['lv.0 -> lv.15'], concerned: [ {io: 'i', type: 'card', id: 100, object: '111', base: true}, {io: 'i', type: 'card', id: 201, object: '222'}, {io: 'o', type: 'card', id: 100, object: '111'}, ] })
1. db.activityHistoryDemo.find({owner_id: 123})
2. db.activityHistoryDemo.find({‘concerned.id’: ‘201’})
クエリ findの話 (2) • bsonオブジェクト階層を細分化したほうが早い
{ owner_id: 123, request: { type: '合成', params: {}}, process: {category: 'composit'}, memo: ['lv.0 -> lv.15'], concerned: [ {io: 'i', type: 'card', id: 100, object: '111', base: true}, {io: 'i', type: 'card', id: 201, object: '222'}, {io: 'o', type: 'card', id: 100, object: '111'}, ] })
db.activityHistoryDemo.find({‘concerned.id’: ‘201’})
クエリ findの話 (3) • 条件の指定順序
o And条件は結果の小さな条件から順次 § 補集合を無視するので。
o Or条件は結果の大きな条件から順次 § 後続条件は補集合から検索するので。
○ db.sample.find({owner_id: 123, ‘concerned.type’: ‘card’})
× db.sample.find({‘concerned.type’: ‘card’, owner_id: 123})
○ db.sample.find({$or: [{‘concerned.type’: ‘card’}, {owner_id: 123}])
× db.sample.find({$or: [{owner_id: 123}, {‘concerned.type’: ‘card’}])
クエリ findの話 (4) • DBRef
doc = { name: 'ryooo', card:[ {'$ref': 'card', '$id' : ObjectId('4b0552b0f0da7d1eb6f12xxx')}, ] } doc.card[0].fetch() // ←カードオブジェクトがとれる
@ruby db = Connection.new.db(”etcr ") user_card = db["user_card"].save({:name => ”ryooo”, :card_id => 123}) ref = DBRef.new(”card", user_card.card_id) db.dereference(ref) #=> カードオブジェクト
クエリ findの話 (5) • 検索条件に関数も使える(javascript)
• mongoサーバーサイドに関数を登録できる
// 極端な話、こんなクエリも書けちゃいます db.cards.find(function(){ row = db.user_summary.findOne({owner_id: this.owner_id}) return this._id == row.leader_card_id; })
// 関数を登録 db.system.js.save({_id:’name', value: function (){ //implementation }}); f = db.system.js.findOne({_id:’name'}) // 検索で利用(fはサーバー側で実行される) Db.cards.find(f)
Mongod (Shard A)
クエリ findの話 (6)
データベース etcr
コレクション 行動履歴
Mongod (Shard B)
データベース etcr
コレクション 行動履歴
Chunk
docA docB docC
Chunk
docD docE docF
Shard keyを利用したクエリ Shard keyを利用しないクエリ
targeted global
クエリ insert/updateの話 (1) • fire and forget
o 発火即忘却 § 結果を確認せずreturnする § 結果を知りたければgetlasterrorオプションを指定
• 確実にcommitさせる o データファイルにフラッシュさせる
§ fsync: true o 2台のメンバーに書き込みが完了するまで待機(timeout:5000)
§ db.getlasterror(2, 5000) § db.getlasterror('majority')
クエリ insert/updateの話 (2) • ID値
o デフォルトでは、IDは自動で振られる o ObjectId = BSON(
[4byte timestamp] + [3byte hash(hostname)] + [2byte pid] + [3byte inc])
// parseすれば時間やサーバーなどもわかる object_id = '4b0552b0f0da7d1eb6f12yyy’ createdDt = new Date(parseInt(object_id.substr(0, 8), 16) * 1000) #=> Thu Nov 19 2009 23:14:08 GMT+0900 (JST)
クエリ insert/updateの話 (3) • Padding
o insert時に、パディング領域を確保している § 配列に追加されるなど、ドキュメントサイズが拡大しても高速にupdateするため(In-place update)
o Padding領域を越える更新 § ドキュメントの再配置が発生(遅い) § 増加性を持ったコレクションはPaddingサイズ調整が必要
• Atomicな操作 o 1つのドキュメントの更新に対して別のクエリをブロック o トランザクションのACIDのA(atomic : all or nothing)ではない。 o sharding環境でサポートされない
クエリ removeの話 • 断片化
o 削除時はドキュメントの再配置を行わないので断片化する
o repairコマンド § 同容量の空き領域が必要 § サーバー単位(sharding環境なら各shardで)
o compactコマンド § より少ない空き領域で可能 § コレクション単位 § paddingも削除するので、増加性を持ったコレクションはデフラグ後にupdateパフォーマンス悪化
困った話 - 1 • flush前のデータロスト
o デフォルトでは60sに1回flushされるまではメモリで保持 § 最大で60秒間のデータロストの可能性
o 対策1 (~ver1.8) § 60秒の設定を短縮する § getlasterror()でflushさせる
• 重くなる o 対策2 ジャーナルモード (ver1.8~)
§ ジャーナルログ(disc)に100msに1回書き込む § flushと違い、データの更新先などを意識しないため早い § 起動時にJournalディレクトリがあれば復元し、正常終了時はディレクトリを削除する。
困った話 - 2 • 書き込み時(flush時)はDBロックする
o あまり問題にならないほど、書き込みは高速とのこと § ほんまかいな
• 非効率なCPUリソース利用 o 書き込み処理とMapReduceではシングルコアしか使えない o 読み込みは複数コアを使う
みんなが苦労しているのは • Shard key の決め方
o Chunkの移動があまり起こらないこと o 長期の運用でも綺麗に分散すること o Shard keyを使って効率よく検索できること
• Shard keyは一度決めると変えられない
• Migration中の残念なパフォーマンスと不整合
Shard keyの考察 (うけうり) • [bad] 離散データ
o chunk分割できない § 例:都道府県コード
• [bad] 単純インクリメントデータ o 最後のchunkのみが分割移動される
§ 例:連番
• [bad] ランダム値 o 十分に分散するまでは偏りがあるため、大きなchunkができる
§ 例:ハッシュ
• [good] 緩やかに増加するキーと検索に利用するキーの組み合わせ o 検索利用キーでひと月かけて偏りが発生してくるが、ひと月たてば偏りがリセットされる § 例:yyyymm-owner_id
解析用サーバー
シンプルな解析PFの例
Web Web
日次増分をMap/Reduceで集計
認証機能が必要なため。 I/Oをjsonで一致させて
開発スピードアップ
使ったことないものを 仕事で使ってみたいという想い