Sdílet prostřednictvím


Řešení potíží s dotazy při používání služby Azure Cosmos DB for MongoDB

PLATÍ PRO: MongoDB

Tento článek vás provede obecným doporučeným přístupem k řešení potíží s dotazy ve službě Azure Cosmos DB. I když byste neměli zvážit kroky popsané v tomto článku jako úplnou ochranu před potenciálními problémy s dotazy, zahrnuli jsme zde nejčastější tipy k výkonu. Tento článek byste měli použít jako výchozí místo pro řešení potíží s pomalými nebo nákladovými dotazy v rozhraní API služby Azure Cosmos DB pro MongoDB. Pokud používáte službu Azure Cosmos DB for NoSQL, přečtěte si článek průvodce odstraňováním potíží s dotazy rozhraní API for NoSQL.

Optimalizace dotazů ve službě Azure Cosmos DB jsou obecně rozdělené do kategorií následujícím způsobem:

  • Optimalizace, které snižují poplatky za jednotku žádosti (RU) dotazu
  • Optimalizace, které pouze snižují latenci

Pokud snížíte poplatky za RU dotazu, obvykle také snížíte latenci.

Tento článek obsahuje příklady, které můžete znovu vytvořit pomocí datové sady výživy.

Poznámka:

Tento článek předpokládá, že používáte rozhraní API služby Azure Cosmos DB pro účty MongoDB verze 3.6 a vyšší. Některé dotazy, které fungují špatně ve verzi 3.2, mají významná vylepšení ve verzích 3.6 nebo novější. Upgradujte na verzi 3.6 vytvořením žádosti o podporu.

Získání metrik pomocí příkazu $explain

Při optimalizaci dotazu ve službě Azure Cosmos DB je prvním krokem vždy získání poplatku za RU pro váš dotaz. Obecně platí, že byste měli prozkoumat možnosti snížení poplatků za RU pro dotazy, u kterých jsou poplatky vyšší než 50 RU.

Kromě získání informací o poplatcích za RU byste pomocí příkazu $explain měli získat i metriky dotazu a využití indexu. Tady je příklad, který spustí dotaz a pomocí $explain příkazu zobrazí metriky využití dotazů a indexů:

$explain příkaz:

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

Výstup:

{
    "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
}

Výstup $explain příkazu je zdlouhavý a obsahuje podrobné informace o provádění dotazu. Při optimalizaci výkonu dotazů byste se však obecně měli zaměřit na několik částí:

Metrický Popis
timeInclusiveMS Latence back-endových dotazů
pathsIndexed Zobrazuje indexy použitého dotazu.
pathsNotIndexed Zobrazuje indexy, které dotaz mohl použít, pokud je k dispozici.
shardInformation Souhrn výkonu dotazů pro konkrétní fyzický oddíl
retrievedDocumentCount Počet dokumentů načtených dotazovacím strojem
outputDocumentCount Počet dokumentů vrácených ve výsledcích dotazu
estimatedDelayFromRateLimitingInMilliseconds Odhadovaná další latence dotazů kvůli omezování rychlosti

Jakmile získáte metriky dotazů, porovnejte retrievedDocumentCount je s dotazem outputDocumentCount . Na základě tohoto porovnání určete příslušné části tohoto článku, které byste si měli projít. Jedná se retrievedDocumentCount o počet dokumentů, které dotazovací modul potřebuje načíst. Jedná se outputDocumentCount o počet dokumentů potřebných pro výsledky dotazu. retrievedDocumentCount Pokud je hodnota výrazně vyšší, outputDocumentCountdošlo alespoň k jedné části dotazu, která nemohla použít index a potřebovala provést kontrolu.

Informace o relevantních optimalizacích dotazů pro váš scénář najdete v následujících částech.

Poplatky za RU dotazu jsou příliš vysoké

Počet načtených dokumentů je výrazně vyšší než počet výstupních dokumentů.

Počet načtených dokumentů je přibližně roven výstupnímu počtu dokumentů.

Poplatky za RU dotazu jsou přijatelné, ale latence je stále příliš vysoká.

Dotazy, u kterých počet načtených dokumentů překračuje počet výstupních dokumentů

Jedná se retrievedDocumentCount o počet dokumentů, které dotazovací modul potřeboval k načtení. Jedná se outputDocumentCount o počet dokumentů vrácených dotazem. retrievedDocumentCount Pokud je hodnota výrazně vyšší, outputDocumentCountdošlo alespoň k jedné části dotazu, která nemohla použít index a potřebovala provést kontrolu.

Tady je příklad vyhledávacího dotazu, který nebyl plně obsluhován indexem:

$explain příkaz:

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

Výstup:

{
    "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
}

Hodnota retrievedDocumentCount (8618) je výrazně vyšší než outputDocumentCount (1), což znamená, že tento dotaz vyžadoval kontrolu dokumentu.

Zahrnout nezbytné indexy

Měli byste zkontrolovat pathsNotIndexed pole a přidat tyto indexy. V tomto příkladu by měly být cesty foodGroup a description měly by být indexovány.

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

Osvědčené postupy indexování v rozhraní API služby Azure Cosmos DB pro MongoDB se liší od MongoDB. V rozhraní API služby Azure Cosmos DB pro MongoDB se složené indexy používají jenom v dotazech, které potřebují efektivně řadit podle více vlastností. Pokud máte dotazy s filtry pro více vlastností, měli byste pro každou z těchto vlastností vytvořit indexy s jedním polem. Predikáty dotazů můžou používat více indexů s jedním polem.

Indexy se zástupnými znaky můžou zjednodušit indexování. Na rozdíl od MongoDB můžou indexy se zástupnými cardy podporovat více polí v predikátech dotazů. Pokud místo vytvoření samostatného indexu pro každou vlastnost použijete jeden index se zástupnými znaky, nebude výkon dotazů nijak jiný. Přidání indexu zástupných znaků pro všechny vlastnosti je nejjednodušší způsob, jak optimalizovat všechny dotazy.

Nové indexy můžete kdykoli přidávat bez vlivu na dostupnost zápisu nebo čtení. Průběh transformace indexu můžete sledovat.

Vysvětlení, které agregační operace používají index

Ve většině případů budou agregační operace v rozhraní API služby Azure Cosmos DB pro MongoDB částečně používat indexy. Dotazovací modul obvykle použije nejprve filtry rovnosti a rozsahu a použije indexy. Po použití těchto filtrů může dotazovací modul vyhodnotit další filtry a v případě potřeby načíst zbývající dokumenty, aby se agregace vypočítala.

Tady je příklad:

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

V tomto případě můžou indexy fázi optimalizovat $match . Přidání indexu pro foodGroup výrazně zvýší výkon dotazů. Stejně jako v MongoDB byste měli co nejdříve umístit $match kanál agregace, abyste maximalizovali využití indexů.

V rozhraní API služby Azure Cosmos DB pro MongoDB se indexy nepoužívají pro skutečnou agregaci, což je $maxv tomto případě . Přidáním indexu version se nezlepší výkon dotazů.

Dotazy, ve kterých je načtený počet dokumentů roven výstupnímu počtu dokumentů

Pokud je dotazovací retrievedDocumentCount modul přibližně stejný outputDocumentCount, nemusel prohledávat mnoho nepotřebných dokumentů.

Minimalizace dotazů mezi oddíly

Azure Cosmos DB používá dělení k škálování jednotlivých kontejnerů, protože je potřeba zvýšit kapacitu jednotek žádostí a úložiště dat. Každý fyzický oddíl má samostatný, nezávislý index. Pokud dotaz obsahuje filtr rovnosti, který odpovídá kontejnerovému klíči oddílu, bude potřeba zkontrolovat jenom index příslušného oddílu. Tato optimalizace snižuje celkový počet jednotek RU, které dotaz vyžaduje. Přečtěte si další informace o rozdílech mezi dotazy v oddílu a dotazy napříč oddíly.

Pokud máte velký počet zřízených RU (více než 30 000) nebo velké množství uložených dat (více než 100 GB), pravděpodobně máte dostatečně velký kontejner, abyste viděli významné snížení poplatků za RU dotazů.

Pole můžete zkontrolovat shardInformation , abyste porozuměli metrikám dotazů pro každý jednotlivý fyzický oddíl. Početjedinečných shardKeyRangeId V tomto příkladu se dotaz spustil na čtyřech fyzických oddílech. Je důležité si uvědomit, že provádění je zcela nezávislé na využití indexu. Jinými slovy, dotazy napříč oddíly můžou stále používat indexy.

  "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
                    }
                ]

Optimalizace, které snižují latenci dotazů

V řadě případů můžou být poplatky za RU přijatelné, zatímco latence dotazů je stále příliš vysoká. V následujících částech najdete přehled tipů ke snížení latence dotazů. Pokud několikrát spustíte stejný dotaz na stejnou datovou sadu, obvykle budou poplatky za RU pokaždé stejné. Latence dotazu se však mezi jednotlivými spuštěními dotazu může lišit.

Zlepšení blízkosti

Dotazy, které se spouští z jiné oblasti než účet služby Azure Cosmos DB, budou mít vyšší latenci, než kdyby byly spuštěny ve stejné oblasti. Pokud například spouštíte kód na stolním počítači, měli byste očekávat latenci o desítky nebo stovky milisekund vyšší (nebo více), než kdyby dotaz pocházel z virtuálního počítače ve stejné oblasti Azure jako služba Azure Cosmos DB. Globální distribuce dat ve službě Azure Cosmos DB je jednoduchá, abyste měli jistotu, že data můžete přiblížit k aplikaci.

Zvýšení zřízené propustnosti

Ve službě Azure Cosmos DB se zřízená propustnost měří v jednotkách žádostí (RU). Představte si, že máte dotaz, který spotřebovává 5 RU propustnosti. Pokud například zřídíte 1000 RU, budete moct tento dotaz spustit 200krát za sekundu. Pokud se pokusíte dotaz spustit, když není k dispozici dostatečná propustnost, Azure Cosmos DB omezí rychlost požadavků. Rozhraní API služby Azure Cosmos DB pro MongoDB tento dotaz po krátké době automaticky zopakuje. Omezené požadavky trvají delší dobu, takže zvýšením zřízené propustnosti můžete snížit latenci dotazů.

Pokud zvýšíte propustnost, hodnota estimatedDelayFromRateLimitingInMilliseconds dává smysl pro potenciální výhody latence.

Další kroky