Dela via


Felsök förfrågningsprobelm när du använder Azure Cosmos DB för MongoDB

GÄLLER FÖR: MongoDB

Den här artikeln går igenom en allmän metod som rekommenderas för felsökning av frågor i Azure Cosmos DB. Även om du inte bör betrakta stegen som beskrivs i den här artikeln som ett fullständigt skydd mot potentiella frågeproblem har vi tagit med de vanligaste prestandatipsen här. Du bör använda den här artikeln som utgångspunkt för felsökning av långsamma eller dyra frågor i Azure Cosmos DB:s API för MongoDB. Om du använder Azure Cosmos DB för NoSQL kan du läsa felsökningsguiden för API för NoSQL-frågor.

Frågeoptimeringar i Azure Cosmos DB kategoriseras i stort sett enligt följande:

  • Optimeringar som minskar RU-avgiften (Request Unit) för frågan
  • Optimeringar som bara minskar svarstiden

Om du minskar RU-avgiften för en fråga minskar du vanligtvis även svarstiden.

Kommentar

Den här artikeln förutsätter att du använder Azure Cosmos DB:s API för MongoDB-konton med version 3.6 och senare. Vissa frågor som presterar dåligt i version 3.2 har betydande förbättringar i version 3.6+. Uppgradera till version 3.6 genom att lämna in en supportbegäran.

Använd kommandot $explain för att hämta mått

När du optimerar en fråga i Azure Cosmos DB är det första steget alltid att hämta RU-avgiften för din fråga. En riktlinje är att du bör utforska sätt att sänka RU-avgiften för frågor med högre avgift än 50 RU.

Förutom att hämta RU-avgiften bör du använda kommandot $explain för att hämta användningsstatistiken för frågor och index. Här är ett exempel som kör en fråga och använder $explain kommandot för att visa användningsstatistik för frågor och index:

$explain kommando:

db.coll.find({foodGroup: "Baby Foods"}).explain({"executionStatistics": true })

Utdata:

{
    "stages" : [ 
        {
            "stage" : "$query",
            "timeInclusiveMS" : 905.2888,
            "timeExclusiveMS" : 905.2888,
            "in" : 362,
            "out" : 362,
            "details" : {
                "database" : "db-test",
                "collection" : "collection-test",
                "query" : {
                    "foodGroup" : {
                        "$eq" : "Baby Foods"
                    }
                },
                "pathsIndexed" : [],
                "pathsNotIndexed" : [ 
                    "foodGroup"
                ],
                "shardInformation" : [ 
                    {
                        "activityId" : "e68e6bdd-5e89-4ec5-b053-3dbbc2428140",
                        "shardKeyRangeId" : "0",
                        "durationMS" : 788.5867,
                        "preemptions" : 1,
                        "outputDocumentCount" : 362,
                        "retrievedDocumentCount" : 8618
                    }
                ],
                "queryMetrics" : {
                    "retrievedDocumentCount" : 8618,
                    "retrievedDocumentSizeBytes" : 104963042,
                    "outputDocumentCount" : 362,
                    "outputDocumentSizeBytes" : 2553535,
                    "indexHitRatio" : 0.0016802042237178,
                    "totalQueryExecutionTimeMS" : 777.72,
                    "queryPreparationTimes" : {
                        "queryCompilationTimeMS" : 0.19,
                        "logicalPlanBuildTimeMS" : 0.14,
                        "physicalPlanBuildTimeMS" : 0.09,
                        "queryOptimizationTimeMS" : 0.03
                    },
                    "indexLookupTimeMS" : 0,
                    "documentLoadTimeMS" : 687.22,
                    "vmExecutionTimeMS" : 774.09,
                    "runtimeExecutionTimes" : {
                        "queryEngineExecutionTimeMS" : 37.45,
                        "systemFunctionExecutionTimeMS" : 10.82,
                        "userDefinedFunctionExecutionTimeMS" : 0
                    },
                    "documentWriteTimeMS" : 49.42
                }
            }
        }
    ],
    "estimatedDelayFromRateLimitingInMilliseconds" : 0.0,
    "continuation" : {
        "hasMore" : false
    },
    "ok" : 1.0
}

Kommandoutdata $explain är långa och innehåller detaljerad information om frågekörning. I allmänhet finns det dock några avsnitt som du bör fokusera på när du optimerar frågeprestanda:

Mätvärde Beskrivning
timeInclusiveMS Svarstid för serverdelsfråga
pathsIndexed Visar index som frågan använde
pathsNotIndexed Visar index som frågan kan ha använt, om det är tillgängligt
shardInformation Sammanfattning av frågeprestanda för en viss fysisk partition
retrievedDocumentCount Antal dokument som läses in av frågemotorn
outputDocumentCount Antal dokument som returneras i frågeresultatet
estimatedDelayFromRateLimitingInMilliseconds Beräknad ytterligare frågesvarstid på grund av hastighetsbegränsning

När du har hämtat frågemåtten retrievedDocumentCount jämför du med outputDocumentCount för din fråga. Använd den här jämförelsen för att identifiera relevanta avsnitt som ska granskas i den här artikeln. retrievedDocumentCount är antalet dokument som frågemotorn behöver läsa in. outputDocumentCount är det antal dokument som behövdes för resultatet av frågan. retrievedDocumentCount Om är betydligt högre än outputDocumentCountfanns det minst en del av frågan som inte kunde använda ett index och som behövde utföra en genomsökning.

Se följande avsnitt för att förstå relevanta frågeoptimeringar för ditt scenario.

Frågans RU-avgift är för hög

Antalet hämtade dokument är betydligt högre än antalet utdatadokument

Antal hämtade dokument är ungefär lika med antal utdatadokument

Frågans RU-avgift är acceptabel men svarstiden är fortfarande för hög

Frågor där antalet hämtade dokument överskrider utdatadokument

retrievedDocumentCount är antalet dokument som frågemotorn behövde läsa in. outputDocumentCount är antalet dokument som returneras av frågan. retrievedDocumentCount Om är betydligt högre än outputDocumentCountfanns det minst en del av frågan som inte kunde använda ett index och som behövde utföra en genomsökning.

Här är ett exempel på en genomsökningsfråga som inte helt hanteras av indexet:

$explain kommando:

db.coll.find(
  {
    $and : [
            { "foodGroup" : "Cereal Grains and Pasta"}, 
            { "description" : "Oat bran, cooked"}
        ]
  }
).explain({"executionStatistics": true })

Utdata:

{
    "stages" : [ 
        {
            "stage" : "$query",
            "timeInclusiveMS" : 436.5716,
            "timeExclusiveMS" : 436.5716,
            "in" : 1,
            "out" : 1,
            "details" : {
                "database" : "db-test",
                "collection" : "indexing-test",
                "query" : {
                    "$and" : [ 
                        {
                            "foodGroup" : {
                                "$eq" : "Cereal Grains and Pasta"
                            }
                        }, 
                        {
                            "description" : {
                                "$eq" : "Oat bran, cooked"
                            }
                        }
                    ]
                },
                "pathsIndexed" : [],
                "pathsNotIndexed" : [ 
                    "foodGroup", 
                    "description"
                ],
                "shardInformation" : [ 
                    {
                        "activityId" : "13a5977e-a10a-4329-b68e-87e4f0081cac",
                        "shardKeyRangeId" : "0",
                        "durationMS" : 435.4867,
                        "preemptions" : 1,
                        "outputDocumentCount" : 1,
                        "retrievedDocumentCount" : 8618
                    }
                ],
                "queryMetrics" : {
                    "retrievedDocumentCount" : 8618,
                    "retrievedDocumentSizeBytes" : 104963042,
                    "outputDocumentCount" : 1,
                    "outputDocumentSizeBytes" : 6064,
                    "indexHitRatio" : 0.0,
                    "totalQueryExecutionTimeMS" : 433.64,
                    "queryPreparationTimes" : {
                        "queryCompilationTimeMS" : 0.12,
                        "logicalPlanBuildTimeMS" : 0.09,
                        "physicalPlanBuildTimeMS" : 0.1,
                        "queryOptimizationTimeMS" : 0.02
                    },
                    "indexLookupTimeMS" : 0,
                    "documentLoadTimeMS" : 387.44,
                    "vmExecutionTimeMS" : 432.93,
                    "runtimeExecutionTimes" : {
                        "queryEngineExecutionTimeMS" : 45.36,
                        "systemFunctionExecutionTimeMS" : 16.86,
                        "userDefinedFunctionExecutionTimeMS" : 0
                    },
                    "documentWriteTimeMS" : 0.13
                }
            }
        }
    ],
    "estimatedDelayFromRateLimitingInMilliseconds" : 0.0,
    "continuation" : {
        "hasMore" : false
    },
    "ok" : 1.0
}

( retrievedDocumentCount 8618) är betydligt högre än outputDocumentCount (1), vilket innebär att den här frågan krävde en dokumentgenomsökning.

Inkludera nödvändiga index

Du bör kontrollera matrisen pathsNotIndexed och lägga till dessa index. I det här exemplet ska sökvägarna foodGroup och description indexeras.

"pathsNotIndexed" : [ 
                    "foodGroup", 
                    "description"
                ]

Metodtips för indexering i Azure Cosmos DB:s API för MongoDB skiljer sig från MongoDB. I Azure Cosmos DB:s API för MongoDB används sammansatta index endast i frågor som effektivt behöver sorteras efter flera egenskaper. Om du har frågor med filter för flera egenskaper bör du skapa index för enskilda fält för var och en av dessa egenskaper. Frågepredikat kan använda flera enkla fältindex.

Jokerteckenindex kan förenkla indexeringen. Till skillnad från i MongoDB kan jokerteckenindex stödja flera fält i frågepredikat. Det blir ingen skillnad i frågeprestanda om du använder ett enda jokerteckenindex i stället för att skapa ett separat index för varje egenskap. Att lägga till ett jokerteckenindex för alla egenskaper är det enklaste sättet att optimera alla dina frågor.

Du kan lägga till nya index när som helst, utan att det påverkar skriv- eller lästillgängligheten. Du kan spåra indextransformeringens förlopp.

Förstå vilka aggregeringsåtgärder som använder indexet

I de flesta fall använder aggregeringsåtgärder i Azure Cosmos DB:s API för MongoDB delvis index. Vanligtvis tillämpar frågemotorn likhets- och intervallfilter först och använder index. När du har tillämpat dessa filter kan frågemotorn utvärdera ytterligare filter och använda för att läsa in återstående dokument för att beräkna aggregeringen om det behövs.

Här är ett exempel:

db.coll.aggregate( [
   { $match: { foodGroup: 'Fruits and Fruit Juices' } },
   {
     $group: {
        _id: "$foodGroup",
        total: { $max: "$version" }
     }
   }
] )

I det här fallet kan index optimera $match fasen. Om du lägger till ett index för foodGroup förbättras frågeprestanda avsevärt. Precis som i MongoDB bör du placera $match så tidigt i aggregeringspipelinen som möjligt för att maximera användningen av index.

I Azure Cosmos DB:s API för MongoDB används inte index för den faktiska aggregeringen, som i det här fallet är $max. Om du lägger till ett index på version förbättras inte frågeprestandan.

Frågor där det hämtade antalet dokument är lika med antal utdatadokument

retrievedDocumentCount Om är ungefär lika med outputDocumentCountbehövde frågemotorn inte skanna många onödiga dokument.

Minimera frågor mellan partitioner

Azure Cosmos DB använder partitionering för att skala enskilda containrar när begärandeenheten och datalagringsbehoven ökar. Varje fysisk partition har ett separat och oberoende index. Om frågan har ett likhetsfilter som matchar containerns partitionsnyckel behöver du bara kontrollera den relevanta partitionens index. Den här optimeringen minskar det totala antalet RU:er som frågan kräver. Läs mer om skillnaderna mellan partitionsfrågor och frågor mellan partitioner.

Om du har ett stort antal etablerade RU:er (mer än 30 000) eller en stor mängd data lagrade (mer än cirka 100 GB), har du förmodligen en tillräckligt stor container för att se en betydande minskning av ru-avgifter för frågor.

Du kan kontrollera matrisen shardInformation för att förstå frågemåtten för varje enskild fysisk partition. Antalet unika shardKeyRangeId värden är antalet fysiska partitioner där frågan behövde köras. I det här exemplet kördes frågan på fyra fysiska partitioner. Det är viktigt att förstå att körningen är helt oberoende av indexanvändning. Med andra ord kan frågor mellan partitioner fortfarande använda index.

  "shardInformation" : [ 
                    {
                        "activityId" : "42f670a8-a201-4c58-8023-363ac18d9e18",
                        "shardKeyRangeId" : "5",
                        "durationMS" : 24.3859,
                        "preemptions" : 1,
                        "outputDocumentCount" : 463,
                        "retrievedDocumentCount" : 463
                    }, 
                    {
                        "activityId" : "a8bf762a-37b9-4c07-8ed4-ae49961373c0",
                        "shardKeyRangeId" : "2",
                        "durationMS" : 35.8328,
                        "preemptions" : 1,
                        "outputDocumentCount" : 905,
                        "retrievedDocumentCount" : 905
                    }, 
                    {
                        "activityId" : "3754e36b-4258-49a6-8d4d-010555628395",
                        "shardKeyRangeId" : "1",
                        "durationMS" : 67.3969,
                        "preemptions" : 1,
                        "outputDocumentCount" : 1479,
                        "retrievedDocumentCount" : 1479
                    }, 
                    {
                        "activityId" : "a69a44ee-db97-4fe9-b489-3791f3d52878",
                        "shardKeyRangeId" : "0",
                        "durationMS" : 185.1523,
                        "preemptions" : 1,
                        "outputDocumentCount" : 867,
                        "retrievedDocumentCount" : 867
                    }
                ]

Optimeringar som minskar frågefördröjningen

I många fall kan RU-avgiften vara acceptabel även om svarstiden för frågor är för lång. I följande avsnitt finns en översikt med tips om hur svarstiden för frågor kan minskas. Om du kör samma fråga flera gånger på samma datamängd får den vanligtvis samma RU-avgift varje gång. Frågesvarstiden kan dock variera mellan frågekörningar.

Förbättra närhet

Frågor som körs från en annan region än Azure Cosmos DB-kontot har högre svarstid än om de kördes i samma region. Om du till exempel kör kod på din stationära dator bör du förvänta dig att svarstiden blir tiotals eller hundratals millisekunder längre (eller mer) än om frågan kom från en virtuell dator i samma Azure-region som Azure Cosmos DB. Det är enkelt att distribuera data globalt i Azure Cosmos DB för att säkerställa att du kan föra dina data närmare din app.

Öka etablerat dataflöde

I Azure Cosmos DB mäts ditt etablerade dataflöde i enheter för programbegäran (RU:er). Anta att du har en fråga som förbrukar 5 RU av dataflödet. Om du till exempel etablerar 1 000 RU skulle du kunna köra frågan 200 gånger per sekund. Om du försökte köra frågan när det inte fanns tillräckligt med dataflöde tillgängligt begränsar Azure Cosmos DB antalet begäranden. Azure Cosmos DB:s API för MongoDB gör automatiskt ett nytt försök efter en kort stund. Begränsade begäranden tar längre tid, så att öka det etablerade dataflödet kan ge kortare svarstid för frågor.

Värdet estimatedDelayFromRateLimitingInMilliseconds ger en uppfattning om de potentiella svarstidsfördelarna om du ökar dataflödet.

Nästa steg