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)してる暇は無いんだよなぁ。。。