Posted at

Mongoのインデックスの改善についてまとめてみた

More than 3 years have passed since last update.

インデックスは闇雲に貼ればいいという物ではなく、既存のシステムにインデックスを作成するのであれば調査から行うのが定石ということで、まずはボトルネックの調査方法から紹介します。


撲滅スロークエリ


スロークエリ


MongoShell

# まずは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という条件というのがわかる


MongoShell

> 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で解析してみます。


MongoShell

> 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をかけることも可能なので、それでも実行計画を見てもいいと思います。


RailsConsole

Person.where(age: {"$gt": 0}).explain


チェックする項目はcursorとnとnscannedObjectsとnscannedとmillis


  1. cursor

    cursorにBasicCursorと書いてあったらインデックスが使われていない

    BTreeCursor


  2. n

    実際にヒットしたドキュメントの件数


  3. nscannedObjects

    ドキュメント(RDBでいうところのレコード)を検索した件数


  4. nscanned

    インデックスを使用して検索した件数


  5. millis

    実行時間 短い方がうれしいね


インデックスを効率的に使用して検索できているか調査するので n / nscanned が1に近い方がいい。

次に nscannedObjects と nscanned に乖離がある場合はwhereに複数条件を指定している場合や、

ソートの指定を行っていて、インデックスでヒットしたドキュメントからBasicCursorを使ってmongoが

ドキュメントをチェックしていっていることがあるので、複合インデックスを貼ることを考えた方が良いかもしれない。

複合インデックスの張り方についてはMySQLと同じ考えなので、以前ボクが書いたMySQLの記事が役に立つと思います。

BTreeIndexのことにも触れているので参考にしてください。

http://qiita.com/kkyouhei/items/e3502ef632c48d94541d


インデックス

mongoで一般的に使用されるインデックスはMySQLでもお馴染みB-TreeIndexです。

インデックスを貼る作業はマシンにとってとてもコストのかかる処理なので、運用中のシステムに実行する場合は

バックグラウンドで実行するオプションをつけたり、アクセスが少ない時間帯にやるなどしたほうが懸命です。

バックグラウンドで実行と言っても更新などのロックがかからないというだけで、マシンにかかる負担は変わらないので、やっぱり気をつけましょう。


インデックス参照


MongoShell

db.people.getIndexes()



インデックス作成


MongoShell

db.people.ensureIndex({"field_name": 1})

# バックグラウンドで実行
db.people.ensureIndex({"field_name": 1}, { background: true})



インデックス削除


MongoShell

db.people.dropIndex({"field_name": 1})

# 全部消す
db.people.dropIndexes()