web-dev-qa-db-ja.com

カバードカウントクエリでmongodbのドキュメントをフェッチして調べる必要があるのはなぜですか?

クエリをカバーするインデックスが存在するにもかかわらず、カウントクエリがドキュメントをメモリにフェッチする理由がわかりません。問題は、そのようなクエリは本番環境で1時間以上かかる可能性があることです。大量のデータをディスクから読み取る必要があるため、データベース全体の速度が低下します(クエリの実行中は75〜100 MB /秒)。本番環境で使用されるインデックスは、各mongodbノードで約5 GBです。本番環境のすべてのインデックスの合計サイズは32 GBであり、各ノードには128 GBのRAMがあるため、RAMに完全に適合します。

私は問題を最小限の、分割されていないセットアップに分類しました。次の種類のドキュメントを挿入しました。

  1. shopIdmissingSinceの両方のフィールドがない3つのドキュメント
  2. フィールド_shopId:1_を含む5つのドキュメントフィールドなしmissingSince
  3. フィールド_shopId:1_および_missingSince:null_の7つのドキュメント
  4. フィールド_shopId:1_およびmissingSince:ISODate("2017-05-22T07:52:40.831Z")を含む13のドキュメント

non-sparse index _{shopId:1, missingSince:1}_を作成しました。クエリの実行プランcount({"shopId":1, "missingSince":null})は_"totalDocsExamined" : 12_を示しました。これは、12のドキュメントをフェッチする必要があることを意味します。これらは、ポイント2の5つのドキュメントとポイント3の7つのドキュメントである必要があります。これらの12のドキュメントはすべて、_shopId:1, missingSince:null_を使用してインデックス内にある必要があり、クエリを満たします。

しかし、mongodbがこれらの12のドキュメントをフェッチして調べる必要があるのはなぜですか?

これが私のテストコレクションです:

_rs1:PRIMARY> db.offer.find()
{ "_id" : 1, "v" : 1 }
{ "_id" : 2, "v" : 1 }
{ "_id" : 3, "v" : 1 }
{ "_id" : 4, "shopId" : 1, "v" : 1 }
{ "_id" : 5, "shopId" : 1, "v" : 1 }
{ "_id" : 6, "shopId" : 1, "v" : 1 }
{ "_id" : 7, "shopId" : 1, "v" : 1 }
{ "_id" : 8, "shopId" : 1, "v" : 1 }
{ "_id" : 9, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 10, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 11, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 12, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 13, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 14, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 15, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 16, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 17, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 18, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 19, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 20, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 21, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 22, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 23, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 24, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 25, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 26, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 27, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 28, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
_

以下は、explain()の出力です。

_rs1:PRIMARY> db.offer.explain(true).count({"shopId":1, "missingSince":null})
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.offer",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "$and" : [
                {
                    "missingSince" : {
                        "$eq" : null
                    }
                },
                {
                    "shopId" : {
                        "$eq" : 1
                    }
                }
            ]
        },
        "winningPlan" : {
            "stage" : "COUNT",
            "inputStage" : {
                "stage" : "FETCH",
                "filter" : {
                    "missingSince" : {
                        "$eq" : null
                    }
                },
                "inputStage" : {
                    "stage" : "IXSCAN",
                    "keyPattern" : {
                        "shopId" : 1,
                        "missingSince" : 1
                    },
                    "indexName" : "shopId_1_missingSince_1",
                    "isMultiKey" : false,
                    "isUnique" : false,
                    "isSparse" : false,
                    "isPartial" : false,
                    "indexVersion" : 1,
                    "direction" : "forward",
                    "indexBounds" : {
                        "shopId" : [
                            "[1.0, 1.0]"
                        ],
                        "missingSince" : [
                            "[null, null]"
                        ]
                    }
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 0,
        "executionTimeMillis" : 0,
        "totalKeysExamined" : 12,
        "totalDocsExamined" : 12,
        "executionStages" : {
            "stage" : "COUNT",
            "nReturned" : 0,
            "executionTimeMillisEstimate" : 0,
            "works" : 13,
            "advanced" : 0,
            "needTime" : 12,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "invalidates" : 0,
            "nCounted" : 12,
            "nSkipped" : 0,
            "inputStage" : {
                "stage" : "FETCH",
                "filter" : {
                    "missingSince" : {
                        "$eq" : null
                    }
                },
                "nReturned" : 12,
                "executionTimeMillisEstimate" : 0,
                "works" : 13,
                "advanced" : 12,
                "needTime" : 0,
                "needYield" : 0,
                "saveState" : 0,
                "restoreState" : 0,
                "isEOF" : 1,
                "invalidates" : 0,
                "docsExamined" : 12,
                "alreadyHasObj" : 0,
                "inputStage" : {
                    "stage" : "IXSCAN",
                    "nReturned" : 12,
                    "executionTimeMillisEstimate" : 0,
                    "works" : 13,
                    "advanced" : 12,
                    "needTime" : 0,
                    "needYield" : 0,
                    "saveState" : 0,
                    "restoreState" : 0,
                    "isEOF" : 1,
                    "invalidates" : 0,
                    "keyPattern" : {
                        "shopId" : 1,
                        "missingSince" : 1
                    },
                    "indexName" : "shopId_1_missingSince_1",
                    "isMultiKey" : false,
                    "isUnique" : false,
                    "isSparse" : false,
                    "isPartial" : false,
                    "indexVersion" : 1,
                    "direction" : "forward",
                    "indexBounds" : {
                        "shopId" : [
                            "[1.0, 1.0]"
                        ],
                        "missingSince" : [
                            "[null, null]"
                        ]
                    },
                    "keysExamined" : 12,
                    "dupsTested" : 0,
                    "dupsDropped" : 0,
                    "seenInvalidated" : 0
                }
            }
        },
        "allPlansExecution" : [ ]
    },
    "serverInfo" : {
        "Host" : "Kays MacBook Pro",
        "port" : 27017,
        "version" : "3.2.6",
        "gitVersion" : "05552b562c7a0b3143a729aaa0838e558dc49b25"
    },
    "ok" : 1
}
_
2
Kay

誰もこの問題の正当な理由を見つけることができなかったので、私は昨日mongodbバグレポートを開きました: https://jira.mongodb.org/browse/SERVER-29326

Mongodbのエンジニアは、これがバグであることを確認しました。残念ながら、mongodbのドキュメントには記載されていないため、問題を追跡し、最初から別のスキーマ設計を展開するのに何時間もかかることになります。

2
Kay

この特定のクエリの問題は、NULL値を検索することです。クエリのそのnullをISODate( "2017-05-22T07:52:40.831Z")に変更すると、必要な結果が得られます(すべてがインデックスから取得されます)。

_idを無効にするプロジェクションを使用してクエリを作成します。

db.offer.find({"shopId":1, "missingSince":ISODate("2017-05-22T07:52:40.831Z")},{_id:0,shopId:1,missingSince:1}).explain(true)

_idは常に結果に含まれるため、無効にする場合はexceptになります。 _id(インデックスに含まれていないもの)を取得するには、mongoはディスクに移動してそれを読み取る必要があります。

0
JJussi