Mongoクエリー・ベース・レプリケーション
レプリカセット間レプリケーション
MongoDBではレプリカセットを跨いでデータを同期する手段が無い。
そもそもレプリカセット自体が冗長構成を目的としているので設計に組み込まれていないのだろう。
しかし現実は Staging環境や、PV系/集計系の分離など、用途はある。
今は、レプリカセットのslaveを1台切り離してそこで何かするしかない。
フレッシュデータじゃないし運用も面倒。
monmo-repl
https://github.com/monmo/monmo-repl
なければ作れば良いので作った。
oplogベースの同期を行う。-s で指定したレプリカセットのPRIMARYのoplogを読み、-d で指定したレプリカセットに反映していく。
PRIMARYダウン時の挙動など、まだ未テストの部分は多いが
『とりあえず動く』
程度の完成度。
例
ReplicaSet1 =sync=> ReplicaSet2 [ 127.0.0.1 ] [ 127.0.0.1 ] - 27017 - 28017 - 27018 - 28018 - 27019 - 28019 ./bin/monmo-repl.sh -s 127.0.0.1:27017,127.0.0.1:27018,127.0.0.1:27019 -d 127.0.0.1:28017,127.0.0.1:28018,127.0.0.1:28019 -i
仕様
- 例によって bash , mongo シェルしか要らない
- 同期元のレプリカセットのmasterのoplogを読む(将来、選択可能になる可能性あり)
- インデックスの同期はオプショナル(解析用には専用のindex持ちたいだろうし)
- 起動時に完全同期オプション(-f) は遅い。。。
- 同期対象DB を指定できる(デフォルト全て)
Index intersection を試してみた。(失敗談)
MongoDB 2.6 からIndex intersectionという機能が追加された。
1つのクエリーで2つのインデックスを使う(かもしれない)機能で、より効率的にクエリーを処理できる。
(どう効率的なのか?はこのへんが詳しい)
さて、じゃあ実際に見てみようというのが今回の趣旨。
元データ
国土地理院が公開している住所情報が手元にあったのでこれを使うことにした。
大体1000万件/4GB弱のデータ。
> db.block_master.stats().count 11700898 > db.block_master.stats().size 3713899792 > db.block_master.findOne() { "_id" : ObjectId("52b8378dab3de2fd4abb193f"), "full" : "北海道 札幌市中央区 南七条西十一丁目 1281", "pref" : ObjectId("52b8378dab3de2fd4abb193c"), "pref_name" : "北海道", "city" : ObjectId("52b8378dab3de2fd4abb193d"), "city_name" : "札幌市中央区", "town" : ObjectId("52b8378dab3de2fd4abb193e"), "town_name" : "南七条西十一丁目", "name" : "1281", "loc" : { "type" : "Point", "coordinates" : [ 141.342094, 43.050264 ] } }
Indexを張る
db.block_master.ensureIndex({pref_name:1}); db.block_master.ensureIndex({city_name:1}); db.block_master.ensureIndex({town_name:1});
今までは、複合インデックスを張っておかないと大変だったのだがさてどうなるか。。。
ちょっとデータを確認
Index intersection はそれぞれのインデックスで絞り込んだ結果の共通部分を抜き出すので、投げるクエリーを検討する。
- 幾つかの県にある同名の市区町村
- 幾つかの市区町村にある同名の町丁
が狙い目だ。
> db.block_master.aggregate([ { $group: {_id: {city: "$city_name", pref: "$pref_name"}}}, { $group: {_id: "$_id.city", num: {$sum: 1}, prefs: {$push: "$_id.pref"}}}, { $match: {num: {$gt:1}}}, { $sort: {num: -1}} ]) { "_id" : "伊達市", "num" : 2, "prefs" : [ "福島県", "北海道" ] } { "_id" : "府中市", "num" : 2, "prefs" : [ "東京都", "広島県" ] }
へー意外と少ない!
> db.block_master.aggregate([ { $group: {_id: {town: "$town_name", city: "$city_name"}}}, { $group: {_id: "$_id.town", num: {$sum: 1}}}, { $match: {num: {$gt:1}}}, { $sort: {num: -1}} ]) { "_id" : "本町", "num" : 144 } { "_id" : "本町二丁目", "num" : 141 } { "_id" : "本町一丁目", "num" : 140 } { "_id" : "栄町", "num" : 133 } { "_id" : "本町三丁目", "num" : 114 } { "_id" : "新町", "num" : 106 } { "_id" : "幸町", "num" : 97 } { "_id" : "中央二丁目", "num" : 93 } { "_id" : "中央一丁目", "num" : 92 } { "_id" : "本町四丁目", "num" : 84 } { "_id" : "東町", "num" : 84 } { "_id" : "末広町", "num" : 79 } { "_id" : "旭町", "num" : 77 } { "_id" : "栄町二丁目", "num" : 75 } { "_id" : "中央三丁目", "num" : 74 } { "_id" : "栄町一丁目", "num" : 73 } { "_id" : "緑町", "num" : 72 } { "_id" : "南町", "num" : 69 } { "_id" : "泉町", "num" : 65 } { "_id" : "中町", "num" : 63 } Type "it" for more >
おお!流石の『本町』!
意外な『栄町』!?
『新町』だと思ったのになぁ。。。
じゃなかった。。まあ兎に角この辺が狙い目。。
あと余談だけどaggregationがcursorで帰って来るようになったのも2.4から。
それまでは単に配列で帰ってくるわ、64MB超えるとコケるわ。。ダメダメだった。
このaggregationも13970程帰って来たから、市区町村名までArrayに含めたらコケたでしょうね。
さてintersectionに戻ろう。。
チャレンジ1
クエリー
> db.block_master.find({city_name:'府中市', pref_name:'東京都'}).count() 5219
explain(true)
> var explain = db.block_master.find({city_name:'府中市', pref_name:'東京都'}).explain(true) > explain.indexBounds { "city_name" : [ [ "府中市", "府中市" ] ] } > explain.allPlans [ { "cursor" : "BtreeCursor city_name_1", "isMultiKey" : false, "n" : 5219, "nscannedObjects" : 13272, "nscanned" : 13272, "scanAndOrder" : false, "indexOnly" : false, "nChunkSkips" : 0, "indexBounds" : { "city_name" : [ [ "府中市", "府中市" ] ] } }, { "cursor" : "BtreeCursor pref_name_1", "isMultiKey" : false, "n" : 0, "nscannedObjects" : 101, "nscanned" : 102, "scanAndOrder" : false, "indexOnly" : false, "nChunkSkips" : 0, "indexBounds" : { "pref_name" : [ [ "東京都", "東京都" ] ] } }, { "cursor" : "Complex Plan", "n" : 0, "nscannedObjects" : 0, "nscanned" : 103, "nChunkSkips" : 0 } ] > explain.stats { "type" : "KEEP_MUTATIONS", "works" : 13273, "yields" : 104, "unyields" : 104, "invalidates" : 0, "advanced" : 5219, "needTime" : 8053, "needFetch" : 0, "isEOF" : 1, "children" : [ { "type" : "FETCH", "works" : 13273, "yields" : 104, "unyields" : 104, "invalidates" : 0, "advanced" : 5219, "needTime" : 8053, "needFetch" : 0, "isEOF" : 1, "alreadyHasObj" : 0, "forcedFetches" : 0, "matchTested" : 5219, "children" : [ { "type" : "IXSCAN", "works" : 13272, "yields" : 104, "unyields" : 104, "invalidates" : 0, "advanced" : 13272, "needTime" : 0, "needFetch" : 0, "isEOF" : 1, "keyPattern" : "{ city_name: 1.0 }", "boundsVerbose" : "field #0['city_name']: [\"府中市\", \"府中市\"]", "isMultiKey" : 0, "yieldMovedCursor" : 0, "dupsTested" : 0, "dupsDropped" : 0, "seenInvalidated" : 0, "matchTested" : 0, "keysExamined" : 13272, "children" : [ ] } ] } ] }
あれ!?
効かない。。
13272件舐めて5219件抽出した。
IXSCANで13272件舐めてFETCHで13273件って事は普通にcity_nameだけ使ったな。
city_name: '東京都'が22万件ほどあって全然絞れないから、
普通に処理することを選択したっぽい。
チャレンジ2
クエリー
期待の『本町』だ。
クエリー
> db.block_master.find({city_name: '松戸市', town_name: '本町'}).count() 25
データ数
> db.block_master.find({city_name: '松戸市'}).count() 30661 > db.block_master.find({town_name: '本町'}).count() 6563
良い感じのバランスである。
explain(true)
> db.block_master.find({city_name: '松戸市', town_name: '本町'}).explain(true).indexBounds { "town_name" : [ [ "本町", "本町" ] ] } > db.block_master.find({city_name: '松戸市', town_name: '本町'}).explain(true).allPlans [ { "cursor" : "BtreeCursor town_name_1", "isMultiKey" : false, "n" : 25, "nscannedObjects" : 6563, "nscanned" : 6563, "scanAndOrder" : false, "indexOnly" : false, "nChunkSkips" : 0, "indexBounds" : { "town_name" : [ [ "本町", "本町" ] ] } }, { "cursor" : "BtreeCursor city_name_1", "isMultiKey" : false, "n" : 15, "nscannedObjects" : 6564, "nscanned" : 6565, "scanAndOrder" : false, "indexOnly" : false, "nChunkSkips" : 0, "indexBounds" : { "city_name" : [ [ "松戸市", "松戸市" ] ] } }, { "cursor" : "Complex Plan", "n" : 15, "nscannedObjects" : 0, "nscanned" : 6566, "nChunkSkips" : 0 } ] > db.block_master.find({city_name: '松戸市', town_name: '本町'}).explain(true).stats { "type" : "KEEP_MUTATIONS", "works" : 6565, "yields" : 153, "unyields" : 153, "invalidates" : 0, "advanced" : 25, "needTime" : 6538, "needFetch" : 0, "isEOF" : 1, "children" : [ { "type" : "FETCH", "works" : 6564, "yields" : 153, "unyields" : 153, "invalidates" : 0, "advanced" : 25, "needTime" : 6538, "needFetch" : 0, "isEOF" : 1, "alreadyHasObj" : 0, "forcedFetches" : 0, "matchTested" : 25, "children" : [ { "type" : "IXSCAN", "works" : 6563, "yields" : 153, "unyields" : 153, "invalidates" : 0, "advanced" : 6563, "needTime" : 0, "needFetch" : 0, "isEOF" : 1, "keyPattern" : "{ town_name: 1.0 }", "boundsVerbose" : "field #0['town_name']: [\"本町\", \"本町\"]", "isMultiKey" : 0, "yieldMovedCursor" : 0, "dupsTested" : 0, "dupsDropped" : 0, "seenInvalidated" : 0, "matchTested" : 0, "keysExamined" : 6563, "children" : [ ] } ] } ] }
まとめ
Index intersectionの気持ちは解りませんでした。。
、、かといって今は精進(source code reading)してる暇は無いんだよなぁ。。。
MongoDB2.6.1 でやっとメジャーバージョンアップ
チェンジログ
http://docs.mongodb.org/master/release-notes/2.6-changelog/
ひたすらヤバイ者揃いですが、やっとメジャーバージョンアップ程度の品質になったかと。
やっと頑張れば使えるかな?
暇が出来次第
http://mongodb.jp/
を早々に生贄にする所存。。
MongoDB 2.4 => 2.6 アップデートした
2.6.1(人柱バージョン)にチャレンジ
2.4.4 => 2.6.1 バージョンアップ手順
今回データファイルには互換性があるので超簡単
ディレクトリ構成
/usr/local/mongo |- bin -> mongodb-linux-x86_64-2.4.4/bin |- mongodb-linux-x86_64-2.4.4 |- data |- logs |- conf |- mongod.conf
手順
- Download & extract
$ cd /tmp $ wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.6.1.tgz $ cd /usr/local/mongo $ tar xzvf /tmp/mongodb-linux-x86_64-2.6.1.tgz
- 既存のmongodを落とす
$ kill `cat /usr/local/mongo/logs/mongod.pid`
- Symlink切り替え
$ cd /usr/local/mongo $ ln -sfT mongodb-linux-x86_64-2.6.1/bin bin
- 新しいディレクトリ構成
/usr/local/mongo |- bin -> mongodb-linux-x86_64-2.6.1/bin |- mongodb-linux-x86_64-2.4.4 |- mongodb-linux-x86_64-2.6.1 |- data |- logs |- conf |- mongod.conf
- mongod起動
$ /usr/local/mongo/bin/mongod -f /usr/local/mongo/conf/mongod.conf
終わり
まとめ
も、何も一直線。
迷うところ無し。
相変わらずmongoの運用設計は秀逸!
ちょっと使ってみた所、geoJSON系が体感的に早くなってる気がする。
他の処理の互換性はいまチェック中。
今のところ大丈夫っぽいが、、
MongoDB2.6.0は時期尚早
MongoDBのメジャーバージョンアップはいつもの通り大混乱だ。
最早様式美ですらある。。
いつも思うがmongodb.incの連中はmongodb使って欲しい訳だ。
Eat yourown dog food !!
jira眺めてて、今問題が多そうな部分
- インデクシング(全体的におかしい)
- AggregateFW(今回の目玉だからね)
- mongodump
個人的にはバックアップが取れなくなるmongodumpがクラッシュする問題が一番困る。。
そんな訳でアップグレードは2.6.2 - 2.6.4 辺りまで様子見のつもり。。
=> お前が率先して人柱やれよ!!
ってツッコミを受けそうですが、、最近ちょっとそこまで余裕無いので。。。
MongoDB2.6リリースノート斜め読み!
ご存知の通りMongoDB2.6がリリースされました!
相変わらず乱文で解説!!
Aggregation Enhancements
Aggregationが強化された。
- db.collection.aggregate() がカーソルを返却するようになった
- 今まで最終結果には64MBの制約があったが、解消されたようだ。
というかそれが普通。。。
- パイプラインがexplainをサポート
- 今までは感覚で是非を判断していたので嬉しい改善!
- ディスクソートが効率的になった
- $out オペレータで指定のコレクションに結果出力が可能
- 今までは結果をforで回して入れなおしてたのでこれも便利。
- $redact でパイプライン中にデータの微修正ができる
- あんまり使う機会が思い当たらない。。
多分この様な用途でMongoDBを使うこと自体が詰んでる。
新しいoperator
- $let, $map
- $literal, $size
- $cond
この辺りはまたいずれ。。
本当に大規模のデータを扱ったときにAggregationは非力なので、ちょっと便利に使いたい人向けの機能と思ってよい。
Text Search Integration
大丈夫。使い物にならない。
Insert and Update Improvements
consistency に関わる改善がある。
MongoDBはドキュメントの"フィールド"の順番を保つルールがある
- _id は常に先頭
- フィールド値の更新やリネームは順番が変わる場合がある
UPDATE系の新オペレータ
- $bit xor
- $min/$max update if max or min
- $push capped-array系(昔ここで解説した)に$position が追加
- $currentDate (説明不要)
- $mul multiplicative increments マニアック。。。
また少し便利になった!
New Write Operation Protocol
write concernの辺りのアップデート。結構重要。
- Ordered Operations
- 処理順を保障し、失敗があった時点で以降の処理を中止する。
Write operation: A => B => C => D Bが失敗した場合、C , Dはキャンセル 結果、Aだけが反映。
- Unordered Operations
- 順序性は無い。失敗があったオペレーションだけが影響を受ける
Write operation: A => B => C => D Bが失敗した場合、Bだけが影響を受ける 結果、A,C,Dが反映。
mecabでのユーザ辞書でハマった話
コストは単純に足し込むと思ってたのだけど、遷移コストなんてものがあるのね。。
日本テレビ東京で学ぶMeCabのコスト計算
しかし困ったぞ、、cost 0 でユーザ辞書に登録しても採用されない問題!
どんな事が起きるかというと、、
(形態素解析の例としては良くないが。。)
山形、山形県、山形産、山形県産、切り落とし
みたいなユーザ辞書を作ったとして、
『山形県産牛モモ切り落とし』
みたいな単語を形態素解析した場合
当然、
山形県産 牛 モモ 切り落とし
と期待したい所だが
山形 県 産 牛 モモ 切り 落とし
みたいな結果になり得る。。
これを防ぐには、それぞれの単語に適切なコストを振らなきゃならないが、辛すぎる。。という話。。。
なんか良い方法がないだろうか。。。