読者です 読者をやめる 読者になる 読者になる

256bitの殺人メニュー

インフラエンジニアだったソリューションアーキテクトなくわののブログ。こちらのBlogは個人の意見となっていて会社とは全く関係ありません。お約束です。[twitter:@kuwa_tw]めんどくさがりが重い腰を上げて何かをアウトプットすることにどれほどの意味があるのかを試してみたいブログでもある。

DEXでもうMongoDB職人は要らなくなるの巻

※このエントリは個人の見解であり、所属する組織の公式見解ではありません

このエントリは、MongoDB Advent Calendar 2015 18日目のエントリです。

どうもどうも乙カレー様です。桑野です。
MongoDB on AWS的ななにかを書こうとしたのですが、その前にこれ紹介したことなかったなーと思いDEXの紹介しようと思います。

DEXとは

あのMongoDBならこの人達のMongolabさんの作った、MongoDBのSlowlogなどから適切なINDEX設定をRecommendしてくれるプロダクトになります。
神様仏様Mongolab様。

インストール

pipで簡単。

$ pip install dex

コマンドライン手順

基本的には、MongoDBのURLと、Logのパスを指定していきましょう。

$ dex -f /var/log/mongodb/mongodb.log  mongodb://localhost
{
    'runStats': {
        'linesRecommended': 0,
        'linesProcessed': 1,
        'linesPassed': 149
    },
    'results': []
}

ほいできました。

データベース例

データベースはこの前 作ったデータベースにデータを足して作ってみましょう。
適当に1000万レコードほど追加しました。

$ mongo d1
(snip)
> db.t1.find().count()
10110005


そこでもう一回DEXを実行してみますが、まだクエリを実行していないので当然何も出ません。

$ dex -f /var/log/mongodb/mongodb.log  mongodb://localhost
{
    'runStats': {
        'linesRecommended': 0,
        'linesProcessed': 1,
        'linesPassed': 149
    },
    'results': []
}

では、Indexのきいていないクエリを打ってみましょう
どれも10秒以上かかります。重い。

$ mongo d1
# クエリ1つ目
> db.t1.find( { "nosql" : "mongodb"} )
{ "_id" : ObjectId("567365cf760961552b000001"), "created_at" : ISODate("2015-12-16T13:04:10Z"), "name" : "kuwa_tw", "nosql" : "mongodb", "price" : 10 }
# クエリ2つ目
> db.t1.find( { "name" : "saeoshi"} )
{ "_id" : ObjectId("567365cf760961552b000005"), "created_at" : ISODate("2015-12-16T13:04:38Z"), "name" : "saeoshi", "nosql" : "voldemote", "price" : 2000 }
# クエリ3つ目
> db.t1.find( { "price" : { $gt: 9000 } } )
(いっぱい出たのでsnip)
>
# クエリ4つ目:複合条件
> db.t1.find( { "name" : "kakerukaeru" , "price" : { $gt: 10 } } )
>


そうしたらそこでもう一回DEXを実行してみましょう。
そうすると、重かったクエリをピックアップして、どのようなインデックスをはればいいのかRecommendしてくれます。これは素晴らし。

$ dex -f /var/log/mongodb/mongodb.log  mongodb://localhost
{
    'runStats': {
        'linesRecommended': 7,
        'linesProcessed': 7,
        'linesPassed': 201
    },
    'results': [
        {
            'queryMask': '{"$query":{"name":"<val>","price":{"$gt":"<val>"}}}',
            'namespace': 'd1.t1',
            'recommendation': {
                'index': '{"name": 1, "price": 1}',
                'namespace': 'd1.t1',
                'shellCommand': 'db["t1"].ensureIndex({"name": 1, "price": 1}, {"background": true})'
            },
            'details': {
                'count': 3,
                'totalTimeMillis': 52289,
                'avgTimeMillis': 17429
            }
        },
        {
            'queryMask': '{"$query":{"name":"<val>"}}',
            'namespace': 'd1.t1',
            'recommendation': {
                'index': '{"name": 1}',
                'namespace': 'd1.t1',
                'shellCommand': 'db["t1"].ensureIndex({"name": 1}, {"background": true})'
            },
            'details': {
                'count': 2,
                'totalTimeMillis': 35758,
                'avgTimeMillis': 17879
            }
        },
        {
            'queryMask': '{"$query":{"nosql":"<val>"}}',
            'namespace': 'd1.t1',
            'recommendation': {
                'index': '{"nosql": 1}',
                'namespace': 'd1.t1',
                'shellCommand': 'db["t1"].ensureIndex({"nosql": 1}, {"background": true})'
            },
            'details': {
                'count': 1,
                'totalTimeMillis': 17983,
                'avgTimeMillis': 17983
            }
        },
        {
            'queryMask': '{"$query":{"price":{"$gt":"<val>"}}}',
            'namespace': 'd1.t1',
            'recommendation': {
                'index': '{"price": 1}',
                'namespace': 'd1.t1',
                'shellCommand': 'db["t1"].ensureIndex({"price": 1}, {"background": true})'
            },
            'details': {
                'count': 1,
                'totalTimeMillis': 17488,
                'avgTimeMillis': 17488
            }
        }
    ]
}


ついでにexplainもしてみましょう。

> db.t1.find( { "name" : "kakerukaeru" , "price" : { $gt: 10 } } ).explain()
{
        "cursor" : "BasicCursor",
        "isMultiKey" : false,
        "n" : 0,
        "nscannedObjects" : 10110005,
        "nscanned" : 10110005,
        "nscannedObjectsAllPlans" : 10110005,
        "nscannedAllPlans" : 10110005,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 18,
        "nChunkSkips" : 0,
        "millis" : 17893,
        "indexBounds" : {

        },
        "server" : "ip-172-31-20-34:27017"
}

当たり前ながらIndexが無いので当然BasicCursorです。


ではRecommendにしたがってIndexを貼ってみましょう

> db["t1"].ensureIndex({"name": 1, "price": 1}, {"background": true})
> db["t1"].getIndexes()
[
        {
                "v" : 1,
                "key" : {
                        "_id" : 1
                },
                "ns" : "d1.t1",
                "name" : "_id_"
        },
        {
                "v" : 1,
                "key" : {
                        "name" : 1,
                        "price" : 1
                },
                "ns" : "d1.t1",
                "name" : "name_1_price_1",
                "background" : true
        }
]

はいIndexれましたね。
ではもう一回explainしてみましょ。
爆速!!!

> db.t1.find( { "name" : "kakerukaeru" , "price" : { $gt: 10 } } ).explain()
{
        "cursor" : "BtreeCursor name_1_price_1",
        "isMultiKey" : false,
        "n" : 0,
        "nscannedObjects" : 0,
        "nscanned" : 0,
        "nscannedObjectsAllPlans" : 0,
        "nscannedAllPlans" : 0,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 0,
        "indexBounds" : {
                "name" : [
                        [
                                "kakerukaeru",
                                "kakerukaeru"
                        ]
                ],
                "price" : [
                        [
                                10,
                                1.7976931348623157e+308
                        ]
                ]
        },
        "server" : "ip-172-31-20-34:27017"
}


BtreeCursorになってIndex[name_1_price_1]が使われているのがわかると思います。
というわけで、Indexはメモリを食うので一概に貼りまくったらええというわけではないんですが、定期的にDEXを流すことでIndexが使われていないクエリが流れていないかを確認することができます。
簡単に使えるので使っていくと幸せになりますよ。


MongoDB使ってる時点で幸せなのかよくわかりませんがね。。。(怪談的な終わり方)


MongoDB in Action

MongoDB in Action

  • 作者: Kyle Banker,Peter Bakkum,Shaun Verch,Douglas Garrett,Tim Hawkins
  • 出版社/メーカー: Manning Pubns Co
  • 発売日: 2016/04/15
  • メディア: ペーパーバック
  • この商品を含むブログを見る