モンゴディビ複合インデックスの最適化

35550 ワード

この文章はALEX BEVILACQUAの文章"Optimizing MongoDB Compound Indexes - The "Equality - Sort - Range" (ESR) Rule"を翻訳した.
MongoDBテクノロジーサービス部門で働いていたとき、MongoDeviユーザーがインデックスがどのような状況で有効に機能しないかを簡単に理解するのに役立つ必要があることに気づきました.
通常、インデックス最適化に関するドキュメントを推奨する際に最初に思いつくのはA.JesseJiryuDavisの「モンゴディビ複合インデックスの最適化」ですが、残念ながらこのトピックに関する正式なドキュメントはまだありません.
私はモンゴディヴィト2019またはモンゴディビワールド2019で何度かこのテーマについて議論しました.同僚のChris Harrisもモンゴディビワールド2019モンゴデビヒューストン2019で同じテーマについて話しました.
この機会に、Jesseの文章と、以前発表したコンテンツに基づいてインデックスを最適化するルールについて議論します.

ESR「ルール」


複合インデックスでは,インデックスを構成する各キー(index key)の順序が非常に重要であり,ESR規則により,すべての場合に適した最適なキー順序がほとんど見出される.
「ルール」を強調するのは、このルールにはいくつかの例外があるためです.例外の詳細については、"Tips and Tricks for Effective Indexing"リリースを参照してください.

ルール1)等しい条件語(等しい述語)を一番前に置く


同等条件者は、他の値と同じ値を比較します.
find({ x: 123 })
find({ x: { $eq: 123 } })
aggregate([ { $match:{ "x.y": 123 } } ])
上記のクエリでは、同じ条件の実行プラン(explain plan)のindexBoundsには緊密な境界があります.
"indexBounds" : {
    "x" : [
        "[123.0, 123.0]"
    ]
}
複合インデックスに複数の同等条件者を配置する場合、選択性の高いキーを最初に配置する必要がないことが重要です.
B−Treeインデックスの性質を考慮すると,この点がより顕著になる.
B−Treeは、各ノード(注:page)に可能なすべての値を格納するので、キーの順序にかかわらず、B−Treeは常に同じ数の組合せが存在する.

ルール2)ソート条件子(sort述語)は、同等条件子の後にある必要があります。


ソート条件は、クエリー結果のソート順を決定します.
find().sort({ a: 1 })
find().sort({ b: -1, a: 1 })
aggregate([ { $sort: { b: 1 } } ])
これらのクエリは、結果をソートするために鍵の範囲全体をスキャンする必要があるため、境界がありません.
"indexBounds" : {
    "b" : [
        "[MaxKey, MinKey]"
    ],
    "a" : [
        "[MinKey, MaxKey]"
    ]
}

ルール3)範囲条件記号(range述語)は、同等条件記号と並べ替え条件記号の後にある必要があります。


範囲条件子は、値と完全に一致するキーを検索するのではなく、複数のキーをスキャンできます.
find({ z: { $gte: 5} })
find({ z: { $lt: 10 } })
find({ z: { $ne: null } })
上記のクエリでは、範囲条件子は条件を満たすすべてのキーをスキャンする必要があるため、緩やかな境界(緩やかな境界)があります.
"indexBounds" : {
    "z" : [
        "[5.0, inf.0]"
    ]
}
"indexBounds" : {
    "z" : [
        "[-inf.0, 10.0)"
    ]
}
"indexBounds" : {
    "z" : [
        "[MinKey, undefined)",
        "(null, MaxKey]"
    ]
}
3つのルールは、クエリがインデックスをどのように使用し、条件を満たす結果を返すかに関連します.

テストデータ


今回は、上記のルールの実際の応用方法について、以下のデータを用いて理解します.
{ name: "Shakir", location: "Ottawa",    region: "AMER", joined: 2015 }
{ name: "Chris",  location: "Austin",    region: "AMER", joined: 2016 }
{ name: "III",    location: "Sydney",    region: "APAC", joined: 2016 }
{ name: "Miguel", location: "Barcelona", region: "EMEA", joined: 2017 }
{ name: "Alex",   location: "Toronto",   region: "AMER", joined: 2018 }
クエリーがどのように実行されているかを確認するために、実行計画のexecutionStatsフィールドも表示されます.
find({ ... }).sort({ ... }).explain("executionStats").executionStats
(E)同等条件
選択性とは、インデックスを使用してクエリー・オブジェクトを削減することを意味します.
有効なインデックスの選択性が向上し、より大きなデータでも効率的に動作するようになります.
同じ条件の者は、選択性を保証するためにインデックスの先端に位置する必要があります.
(E→S)対等条件語はソート条件語の前にある.
並べ替え条件を同じ条件にすると、
  • 水田ブロック配列
  • を行うことができる.
  • スキャンの範囲を減らす
  • この点を理解するために、次のクエリを見てみましょう.
    // operation
    createIndex({ name: 1, region: 1 })
    find({ region: "AMER" }).sort({ name: 1 })
    ソート条件語はより前にあり、範囲全体をスキャンしてから、より選択的な同等条件語を使用することができます.
    // execution stats
    "nReturned" : 3.0,
    "totalKeysExamined" : 5.0,
    "totalDocsExamined" : 5.0,
    "executionStages" : {
        ...
        "inputStage" : {
            "stage" : "IXSCAN",
            ...
            "indexBounds" : {
                "name" : [
                    "[MinKey, MaxKey]"
                ],
                "region" : [
                    "[MinKey, MaxKey]"
                ]
            },

    したがって、上記のインデックスを使用する場合は、3つのエンティティを返すには、5つのキーをすべてスキャンする必要があります.
    // operation
    createIndex({ region: 1, name: 1 })
    find({ region: "AMER" }).sort({ name: 1 })
    逆に、同じ条件の文字を先に配置すると、フィルタ条件を満たすと同時に、より緊密な境界でより少ないキーをスキャンすることができます.
    // execution stats
    "nReturned" : 3.0,
    "totalKeysExamined" : 3.0,
    "totalDocsExamined" : 3.0,
    "executionStages" : {
        ...
        "inputStage" : {
            "stage" : "IXSCAN",
            ...
            "indexBounds" : {
                "region" : [
                    "[\"AMER\", \"AMER\"]"
                ],
                "name" : [
                    "[MinKey, MaxKey]"
                ]
            },

    (E→R)範囲条件子の前に等しい条件子を置く.
    範囲条件ワードスキャンのキーはソート条件ワードより少ないが,より高い選択性を保証するには,同等条件ワードの後ろに置いたほうがよい.
    // operation
    createIndex({ joined: 1, region: 1 })
    find({ region: "AMER", joined: { $gt: 2015 } })
    範囲条件を指定する前に同じ条件を指定した場合は、条件を満たすドキュメントに戻るために、より多くの鍵をスキャンする必要があります.
    // execution stats
    "nReturned" : 2.0,
    "totalKeysExamined" : 4.0,
    "totalDocsExamined" : 2.0,
    "executionStages" : {
        ...
        "inputStage" : {
            "stage" : "IXSCAN",
            ...
            "indexBounds" : {
                "joined" : [
                    "(2015.0, inf.0]"
                ],
                "region" : [
                    "[\"AMER\", \"AMER\"]"
                ]
            },

    上記の例では、4つのキーをスキャンして2つのエンティティを返します.
    逆に、同じ条件の者が先に到着するように手配すれば、スキャンを必要とするキーを減らすことができる.
    // operation
    createIndex({ region: 1, joined: 1 })
    find({ region: "AMER", joined: { $gt: 2015 } })
    // execution stats
    "nReturned" : 2.0,
    "totalKeysExamined" : 2.0,
    "totalDocsExamined" : 2.0,
    "executionStages" : {
        ...
        "inputStage" : {
            "stage" : "IXSCAN",
            ...
            "indexBounds" : {
                "region" : [
                    "[\"AMER\", \"AMER\"]"
                ],
                "joined" : [
                    "(2015.0, inf.0]"
                ]
            },

    同等条件語を範囲条件の前に置くと、必要な内容しか正確にスキャンできません.
    (S→R)範囲条件子の前に並べ替え条件子を置きます.
    範囲条件語をソート条件語の前に置くと、ソートではインデックスが使用できないため、ソートがブロックされます.
    // operation
    createIndex({ joined: 1, region: 1 })
    find({ joined: { $gt: 2015 } }).sort({ region: 1 })
    // execution stats
    "nReturned" : 4.0,
    "totalKeysExamined" : 4.0,
    "totalDocsExamined" : 4.0,
    "executionStages" : {
        ...
        "inputStage" : {
            "stage" : "SORT",
            ...
            "sortPattern" : {
                "region" : 1.0
            },
            "memUsage" : 136.0,
            "memLimit" : 33554432.0,
            "inputStage" : {
                "stage" : "SORT_KEY_GENERATOR",
                ...
                "inputStage" : {
                    "stage" : "IXSCAN",
                    ...
                    "indexBounds" : {
                        "joined" : [
                            "(2015.0, inf.0]"
                        ],
                        "region" : [
                            "[MinKey, MaxKey]"
                        ]
                    },

    範囲条件記号を使用すると、条件を満たす4つのキーが見つかりますが、クエリーの結果はソートされません.そのため、結果を返す前にメモリソートを行う必要があります.
    ソート条件を範囲条件の前に移動する場合は、より多くのキーをスキャンする必要がありますが、ソートする必要はありません.
    // operation
    createIndex({ region: 1, joined: 1 })
    find({ joined: { $gt: 2015 } }).sort({ region: 1 })
    // execution stats
    "nReturned" : 4.0,
    "totalKeysExamined" : 5.0,
    "totalDocsExamined" : 5.0,
    "executionStages" : {
        ...
        "inputStage" : {
            "stage" : "IXSCAN",
            ...
            "indexBounds" : {
                "region" : [
                    "[MinKey, MaxKey]"
                ],
                "joined" : [
                    "[MinKey, MaxKey]"
                ]
            },

    この方法は、より多くの鍵をスキャンするが、ブロックソートを行わないため、通常、より良いパフォーマンスを示す.

    の最後の部分


    ESRルールでモンゴディビインデックスを最適化し、クエリーのパフォーマンスを向上させるには、問題がある場合は、モンゴルデジタル開発者コミュニティフォーラムを使用するか、メッセージを残してください.
    詳細なヘルプが必要な場合は、Atlas開発者サービスまたはエンタープライズサービスにアクセスしてください.
    幸せの最適化になることを望みます!