インデックスは闇雲に貼ればいいという物ではなく、既存のシステムにインデックスを作成するのであれば調査から行うのが定石ということで、まずはボトルネックの調査方法から紹介します。
撲滅スロークエリ
スロークエリ
# まずはMongoShellにログインします
$ mongo
# DBを選択します
use sample
# db.setProfilingLevel(level, slowms)
# level:1 スロークエリのみ出力
# level:2 すべて
# slowms 以下の例だと20ms以上かかっているクエリを出力する
db.setProfilingLevel(1,20)
# いくつかクエリを実行したあとに実行してみます
# forEach(printjson) は見やすく整形するメソッド
db.system.profile.find().forEach(printjson)
system.profile.find()だと全て出力されるので新しいスロークエリ3件だけ
commandの要素を見るとqueryとあるので、この中がwhereに設定した条件
今回の例だとage: {"$gt": 0}なので、年齢 > 0という条件というのがわかる
> db.system.profile.find().sort({ts: -1}).limit(3).forEach(printjson)
{
"op" : "command",
"ns" : "user_model_development.$cmd",
"command" : {
"count" : "people",
"query" : {
"age" : {
"$gt" : 0
}
}
},
"keyUpdates" : 0,
"numYield" : 1,
"lockStats" : {
"timeLockedMicros" : {
"r" : NumberLong(15597646),
"w" : NumberLong(0)
},
"timeAcquiringMicros" : {
"r" : NumberLong(82294),
"w" : NumberLong(3)
}
},
"responseLength" : 48,
"millis" : 8104,
"execStats" : {
},
"ts" : ISODate("2015-11-04T10:29:00.841Z"),
"client" : "127.0.0.1",
"allUsers" : [ ],
"user" : ""
}
クエリを調査
クエリを調査するにはMySQLでもおなじみのexplainを使用して調査します。
先ほどのスロークエリをexplainで解析してみます。
> db.people.find({age: {"$gt": 0}}).explain()
{
"cursor" : "BasicCursor",
"isMultiKey" : false,
"n" : 2624196,
"nscannedObjects" : 2624196,
"nscanned" : 2624196,
"nscannedObjectsAllPlans" : 2624196,
"nscannedAllPlans" : 2624196,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 20509,
"nChunkSkips" : 0,
"millis" : 6404,
"server" : "vagrant-centos64.vagrantup.com:27017",
"filterSet" : false
}
ただRailsでMongoidを使用している人はこのクエリが一体どこから来たものなのかわからない場合があると思うので、
そういう時は直接Modelのfindやwhereに対してexplainをかけることも可能なので、それでも実行計画を見てもいいと思います。
Person.where(age: {"$gt": 0}).explain
チェックする項目はcursorとnとnscannedObjectsとnscannedとmillis
-
cursor
cursorにBasicCursorと書いてあったらインデックスが使われていない
BTreeCursor -
n
実際にヒットしたドキュメントの件数 -
nscannedObjects
ドキュメント(RDBでいうところのレコード)を検索した件数 -
nscanned
インデックスを使用して検索した件数 -
millis
実行時間 短い方がうれしいね
インデックスを効率的に使用して検索できているか調査するので n / nscanned が1に近い方がいい。
次に nscannedObjects と nscanned に乖離がある場合はwhereに複数条件を指定している場合や、
ソートの指定を行っていて、インデックスでヒットしたドキュメントからBasicCursorを使ってmongoが
ドキュメントをチェックしていっていることがあるので、複合インデックスを貼ることを考えた方が良いかもしれない。
複合インデックスの張り方についてはMySQLと同じ考えなので、以前ボクが書いたMySQLの記事が役に立つと思います。
BTreeIndexのことにも触れているので参考にしてください。
http://qiita.com/kkyouhei/items/e3502ef632c48d94541d
インデックス
mongoで一般的に使用されるインデックスはMySQLでもお馴染みB-TreeIndexです。
インデックスを貼る作業はマシンにとってとてもコストのかかる処理なので、運用中のシステムに実行する場合は
バックグラウンドで実行するオプションをつけたり、アクセスが少ない時間帯にやるなどしたほうが懸命です。
バックグラウンドで実行と言っても更新などのロックがかからないというだけで、マシンにかかる負担は変わらないので、やっぱり気をつけましょう。
インデックス参照
db.people.getIndexes()
インデックス作成
db.people.ensureIndex({"field_name": 1})
# バックグラウンドで実行
db.people.ensureIndex({"field_name": 1}, { background: true})
インデックス削除
db.people.dropIndex({"field_name": 1})
# 全部消す
db.people.dropIndexes()