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

PLATÍ PRO: NoSQL

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 kroky popsané v tomto článku neměli považovat za úplnou ochranu před potenciálními problémy s dotazy, zahrnuli jsme sem nejběžně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 drahými dotazy ve službě Azure Cosmos DB for NoSQL. K identifikaci dotazů, které jsou pomalé nebo které spotřebovávají významnou část propustnosti, můžete použít také diagnostické protokoly. Pokud používáte rozhraní API služby Azure Cosmos DB pro MongoDB, měli byste použít průvodce odstraňováním potíží s dotazy rozhraní API služby Azure Cosmos DB pro MongoDB.

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

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

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

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

Běžné problémy se sadou SDK

Před přečtením této příručky je vhodné zvážit běžné problémy se sadou SDK, které nesouvisejí s dotazovacím strojem.

  • Pro dotazy postupujte podle těchto tipů k výkonu sady SDK.
  • Někdy můžou mít dotazy prázdné stránky, i když se na budoucí stránce zobrazí výsledky. Důvody pro to můžou být:
    • Sada SDK může provádět více síťových volání.
    • Načtení dokumentů dotazem může trvat dlouhou dobu.
  • Všechny dotazy mají token pro pokračování, který umožní pokračovat v dotazu. Nezapomeňte dotaz zcela vyčerpat. Další informace o zpracování více stránek výsledků

Získání metrik dotazu

Při optimalizaci dotazu ve službě Azure Cosmos DB je prvním krokem vždy získání metrik dotazů pro váš dotaz. Tyto metriky jsou k dispozici také prostřednictvím Azure Portal. Po spuštění dotazu v Data Explorer se vedle karty Výsledky zobrazí metriky dotazu:

Získání metrik dotazů

Jakmile získáte metriky dotazu, porovnejte počet načtených dokumentů s počtem výstupních dokumentů dotazu. Na základě tohoto porovnání určete příslušné části tohoto článku, které byste si měli projít.

Počet načtených dokumentů je počet dokumentů, které dotazovací modul potřebuje k načtení. Počet výstupních dokumentů je počet dokumentů, které byly potřeba pro výsledky dotazu. Pokud je počet načtených dokumentů výrazně vyšší než počet výstupních dokumentů, byla alespoň jedna část 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ů se přibližně rovná počtu výstupních 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ů

Počet načtených dokumentů je počet dokumentů, které dotazovací modul potřebuje k načtení. Počet výstupních dokumentů je počet dokumentů vrácených dotazem. Pokud je počet načtených dokumentů výrazně vyšší než počet výstupních dokumentů, byla alespoň jedna část dotazu, která nemohla použít index a potřebovala provést kontrolu.

Tady je příklad dotazu kontroly, který nebyl zcela obsloužen indexem:

Dotaz:

SELECT VALUE c.description
FROM c
WHERE UPPER(c.description) = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"

Metriky dotazů:

Retrieved Document Count                 :          60,951
Retrieved Document Size                  :     399,998,938 bytes
Output Document Count                    :               7
Output Document Size                     :             510 bytes
Index Utilization                        :            0.00 %
Total Query Execution Time               :        4,500.34 milliseconds
  Query Preparation Times
    Query Compilation Time               :            0.09 milliseconds
    Logical Plan Build Time              :            0.05 milliseconds
    Physical Plan Build Time             :            0.04 milliseconds
    Query Optimization Time              :            0.01 milliseconds
  Index Lookup Time                      :            0.01 milliseconds
  Document Load Time                     :        4,177.66 milliseconds
  Runtime Execution Times
    Query Engine Times                   :          322.16 milliseconds
    System Function Execution Time       :           85.74 milliseconds
    User-defined Function Execution Time :            0.00 milliseconds
  Document Write Time                    :            0.01 milliseconds
Client Side Metrics
  Retry Count                            :               0
  Request Charge                         :        4,059.95 RUs

Počet načtených dokumentů (60 951) je výrazně vyšší než počet výstupních dokumentů (7), což znamená, že výsledkem tohoto dotazu byla kontrola dokumentu. V tomto případě systémová funkce UPPER() nepoužívá index.

Zahrnutí nezbytných cest do zásad indexování

Zásady indexování by se měly vztahovat na všechny vlastnosti obsažené v WHERE klauzulích, JOINORDER BY klauzulích a většině systémových funkcí. Požadované cesty zadané v zásadách indexu by měly odpovídat vlastnostem v dokumentech JSON.

Poznámka

U vlastností v zásadách indexování služby Azure Cosmos DB se rozlišují velká a malá písmena.

Pokud na datovou sadu výživy spustíte následující jednoduchý dotaz, uvidíte při indexování vlastnosti v WHERE klauzuli mnohem nižší poplatky za RU:

Původní

Dotaz:

SELECT *
FROM c
WHERE c.description = "Malabar spinach, cooked"

Zásady indexování:

{
    "indexingMode": "consistent",
    "automatic": true,
    "includedPaths": [
        {
            "path": "/*"
        }
    ],
    "excludedPaths": [
        {
            "path": "/description/*"
        }
    ]
}

Poplatek za RU: 409,51 RU

Optimalizované

Aktualizované zásady indexování:

{
    "indexingMode": "consistent",
    "automatic": true,
    "includedPaths": [
        {
            "path": "/*"
        }
    ],
    "excludedPaths": []
}

Poplatek za RU: 2,98 RU

Do zásad indexování můžete kdykoli přidat vlastnosti bez vlivu na dostupnost zápisu nebo čtení. Průběh transformace indexu můžete sledovat.

Zjištění, které systémové funkce používají index

Většina systémových funkcí používá indexy. Tady je seznam některých běžných řetězcových funkcí, které používají indexy:

  • StartsWith
  • Contains
  • RegexMatch
  • Left
  • Podřetěžce – ale pouze v případě, že první num_expr je 0

Následuje několik běžných systémových funkcí, které nepoužívají index a při použití v WHERE klauzuli musí načíst každý dokument:

Systémová funkce Nápady pro optimalizaci
Horní/dolní Místo použití systémové funkce k normalizaci dat pro porovnání normalizujte při vložení velikost písmen. Dotaz jako SELECT * FROM c WHERE UPPER(c.name) = 'BOB' se změní na SELECT * FROM c WHERE c.name = 'BOB'.
GetCurrentDateTime/GetCurrentTimestamp/GetCurrentTicks Vypočítejte aktuální čas před spuštěním dotazu a použijte tuto řetězcovou hodnotu v klauzuli WHERE .
Matematické funkce (neagregované) Pokud potřebujete v dotazu počítat hodnotu často, zvažte uložení hodnoty jako vlastnosti v dokumentu JSON.

Tyto systémové funkce můžou používat indexy s výjimkou použití v dotazech s agregacemi:

Systémová funkce Nápady pro optimalizaci
Funkce prostorového systému Uložení výsledku dotazu v materializovaném zobrazení v reálném čase

Při použití v klauzuli nebudou SELECT neefektivní systémové funkce mít vliv na to, jak můžou dotazy používat indexy.

Zlepšení provádění řetězcových systémových funkcí

U některých systémových funkcí, které používají indexy, můžete zlepšit provádění dotazů přidáním ORDER BY klauzule do dotazu.

Přesněji řečeno, každá systémová funkce, jejíž poplatek za RU se zvyšuje se zvyšováním kardinality vlastnosti, může být v dotazu přínosná ORDER BY . Tyto dotazy prověřují index, takže seřazení výsledků dotazu může být efektivnější.

Tato optimalizace může zlepšit provádění následujících systémových funkcí:

  • StartsWith (kde se nerozlišují malá a velká písmena = true)
  • StringEquals (kde nerozlišují malá a velká písmena = true)
  • Contains
  • RegexMatch
  • EndsWith

Zvažte například následující dotaz pomocí CONTAINSpříkazu . CONTAINS použije indexy, ale někdy, i po přidání příslušného indexu, můžete při spuštění následujícího dotazu stále pozorovat velmi vysoké poplatky za RU.

Původní dotaz:

SELECT *
FROM c
WHERE CONTAINS(c.town, "Sea")

Provádění dotazů můžete zlepšit přidáním ORDER BY:

SELECT *
FROM c
WHERE CONTAINS(c.town, "Sea")
ORDER BY c.town

Stejná optimalizace může pomoct v dotazech s dalšími filtry. V tomto případě je nejlepší do klauzule přidat také vlastnosti s filtry ORDER BY rovnosti.

Původní dotaz:

SELECT *
FROM c
WHERE c.name = "Samer" AND CONTAINS(c.town, "Sea")

Provádění dotazů můžete zlepšit přidáním ORDER BYsloženého indexu pro (c.name, c.town):

SELECT *
FROM c
WHERE c.name = "Samer" AND CONTAINS(c.town, "Sea")
ORDER BY c.name, c.town

Zjištění, které agregační dotazy používají index

Ve většině případů použije index agregace systémových funkcí ve službě Azure Cosmos DB. V závislosti na filtrech nebo dalších klauzulích v agregačním dotazu však může být dotazovací modul vyžadován k načtení velkého počtu dokumentů. Dotazovací modul obvykle nejprve použije filtry rovnosti a rozsahu. 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 pro výpočet agregace.

Například vzhledem k těmto dvěma ukázkovým dotazům bude dotaz s filtrem rovnosti i CONTAINS systémových funkcí obecně efektivnější než dotaz s pouhým filtrem systémových CONTAINS funkcí. Důvodem je to, že filtr rovnosti se použije jako první a použije index před načtením dokumentů pro dražší CONTAINS filtr.

Dotaz pouze CONTAINS s filtrem – vyšší poplatek za RU:

SELECT COUNT(1)
FROM c
WHERE CONTAINS(c.description, "spinach")

Dotaz s filtrem rovnosti i CONTAINS filtrem – nižší poplatek za RU:

SELECT AVG(c._ts)
FROM c
WHERE c.foodGroup = "Sausages and Luncheon Meats" AND CONTAINS(c.description, "spinach")

Tady jsou další příklady agregačních dotazů, které nebudou plně využívat index:

Dotazy se systémovými funkcemi, které nepoužívají index

Měli byste se podívat na stránku příslušné systémové funkce a zjistit, jestli používá index.

SELECT MAX(c._ts)
FROM c
WHERE CONTAINS(c.description, "spinach")

Agregace dotazů pomocí uživatelem definovaných funkcí (UDF)

SELECT AVG(c._ts)
FROM c
WHERE udf.MyUDF("Sausages and Luncheon Meats")

Dotazy se GROUP BY

S rostoucí kardinalitou vlastností v klauzuli GROUP BY se zvýší poplatky za RU u dotazů s GROUP BY použitím. Například v následujícím dotazu se zvýší poplatek za RU dotazu se zvýšením počtu jedinečných popisů.

Poplatek za RU agregační funkce s klauzulí GROUP BY bude vyšší než poplatek za RU samotné agregační funkce. V tomto příkladu musí dotazovací modul načíst každý dokument, který odpovídá c.foodGroup = "Sausages and Luncheon Meats" filtru, aby se očekávalo vysoké poplatky za RU.

SELECT COUNT(1)
FROM c
WHERE c.foodGroup = "Sausages and Luncheon Meats"
GROUP BY c.description

Pokud plánujete často spouštět stejné agregační dotazy, může být efektivnější vytvořit materializované zobrazení v reálném čase pomocí kanálu změn služby Azure Cosmos DB než spouštění jednotlivých dotazů.

Optimalizace dotazů, které mají filtr i klauzuli ORDER BY

I když dotazy s filtrem a klauzulí ORDER BY obvykle používají index rozsahu, budou efektivnější, pokud je lze obsluhovat ze složeného indexu. Kromě úprav zásad indexování byste do klauzule měli přidat všechny vlastnosti ve složeného indexu ORDER BY . Touto změnou dotazu zajistíte, že se použije složený index. Dopad můžete sledovat spuštěním dotazu na datovou sadu výživy :

Původní

Dotaz:

SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies"
ORDER BY c._ts ASC

Zásady indexování:

{

        "automatic":true,
        "indexingMode":"Consistent",
        "includedPaths":[  
            {  
                "path":"/*"
            }
        ],
        "excludedPaths":[]
}

Poplatek za RU: 44,28 RU

Optimalizované

Aktualizovaný dotaz (zahrnuje obě vlastnosti v klauzuli ORDER BY ):

SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies"
ORDER BY c.foodGroup, c._ts ASC

Aktualizované zásady indexování:

{  
        "automatic":true,
        "indexingMode":"Consistent",
        "includedPaths":[  
            {  
                "path":"/*"
            }
        ],
        "excludedPaths":[],
        "compositeIndexes":[  
            [  
                {  
                    "path":"/foodGroup",
                    "order":"ascending"
        },
                {  
                    "path":"/_ts",
                    "order":"ascending"
                }
            ]
        ]
    }

Poplatek za RU: 8,86 RU

Optimalizace výrazů JOIN s využitím poddotazu

Poddotazy s více hodnotami můžou optimalizovat JOIN výrazy tak, že za každý výraz select-many nasdílí predikáty, a ne po všech křížových spojeních v klauzuli WHERE .

Zvažte tento dotaz:

SELECT Count(1) AS Count
FROM c
JOIN t IN c.tags
JOIN n IN c.nutrients
JOIN s IN c.servings
WHERE t.name = 'infant formula' AND (n.nutritionValue > 0
AND n.nutritionValue < 10) AND s.amount > 1

Poplatek za RU: 167,62 RU

U tohoto dotazu bude index odpovídat jakémukoli dokumentu, který má značku s názvem infant formula, nutritionValue větším než 0 a amount větším než 1. Výraz JOIN zde provede křížový součin všech položek značek, živin a polí obsluhy pro každý odpovídající dokument předtím, než se použije jakýkoli filtr. Klauzule WHERE pak použije predikát filtru na každou <c, t, n, s> kolekci členů.

Pokud má například odpovídající dokument v každé ze tří polí 10 položek, rozbalí se na 1 x 10 x 10 x 10 (tj. 1 000) řazených kolekcí členů. Použití poddotazů může pomoct odfiltrovat spojené položky pole před spojením s dalším výrazem.

Tento dotaz odpovídá předchozímu dotazu, ale používá poddotaz:

SELECT Count(1) AS Count
FROM c
JOIN (SELECT VALUE t FROM t IN c.tags WHERE t.name = 'infant formula')
JOIN (SELECT VALUE n FROM n IN c.nutrients WHERE n.nutritionValue > 0 AND n.nutritionValue < 10)
JOIN (SELECT VALUE s FROM s IN c.servings WHERE s.amount > 1)

Poplatek za RU: 22,17 RU

Předpokládejme, že filtr odpovídá pouze jedné položce v poli značek a že existuje pět položek pro pole živin a servírování. Výrazy JOIN se rozšíří na 1 x 1 x 5 x 5 = 25 položek namísto 1 000 položek v prvním dotazu.

Dotazy, u kterých počet načtených dokumentů odpovídá počtu výstupních dokumentů

Pokud počet načtených dokumentů přibližně odpovídá počtu výstupních dokumentů, nemusí dotazovací modul zbytečně kontrolovat mnoho nepotřebných dokumentů. U mnoha dotazů, například těch, které používají TOP klíčové slovo, může počet načtených dokumentů překročit počet výstupních dokumentů o 1. Není potřeba se tím zabývat.

Minimalizace dotazů mezi oddíly

Azure Cosmos DB používá dělení ke škálování jednotlivých kontejnerů s rostoucími potřebami jednotky žá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.

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ž přibližně 100 GB), pravděpodobně máte dostatečně velký kontejner, aby se výrazně snížily poplatky za RU dotazů.

Pokud například vytvoříte kontejner s klíčem oddílu foodGroup, následující dotazy budou muset zkontrolovat pouze jeden fyzický oddíl:

SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies" and c.description = "Mushroom, oyster, raw"

Dotazy, které mají IN filtr s klíčem oddílu, budou kontrolovat pouze příslušné fyzické oddíly a nebudou "fan-out":

SELECT *
FROM c
WHERE c.foodGroup IN("Soups, Sauces, and Gravies", "Vegetables and Vegetable Products") and c.description = "Mushroom, oyster, raw"

Dotazy, které mají filtry rozsahu pro klíč oddílu nebo které nemají žádné filtry na klíč oddílu, budou muset "fan-out" a zkontrolovat výsledky v indexu každého fyzického oddílu:

SELECT *
FROM c
WHERE c.description = "Mushroom, oyster, raw"
SELECT *
FROM c
WHERE c.foodGroup > "Soups, Sauces, and Gravies" and c.description = "Mushroom, oyster, raw"

Optimalizace dotazů obsahujících filtry několika vlastností

I když dotazy, které mají filtry na více vlastností, obvykle používají index rozsahu, budou efektivnější, pokud je lze obsluhovat ze složeného indexu. U malých objemů dat nebude mít tato optimalizace velký význam. Při velkých objemech dat ale může být užitečná. Optimalizovat můžete nejvýše jeden filtr bez rovnosti za složený index. Pokud je v dotazu několik filtrů bez rovnosti, vyberte ten, který použije složený index. Zbytek bude dál používat indexy rozsahů. Filtr nerovnosti musí být definován jako poslední ve složeného indexu. Přečtěte si další informace o složených indexech.

Tady je několik příkladů dotazů, které je možné optimalizovat pomocí složeného indexu:

SELECT *
FROM c
WHERE c.foodGroup = "Vegetables and Vegetable Products" AND c._ts = 1575503264
SELECT *
FROM c
WHERE c.foodGroup = "Vegetables and Vegetable Products" AND c._ts > 1575503264

Tady je příslušný složený index:

{  
        "automatic":true,
        "indexingMode":"Consistent",
        "includedPaths":[  
            {  
                "path":"/*"
            }
        ],
        "excludedPaths":[],
        "compositeIndexes":[  
            [  
                {  
                    "path":"/foodGroup",
                    "order":"ascending"
                },
                {  
                    "path":"/_ts",
                    "order":"ascending"
                }
            ]
        ]
}

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í vzdálenosti

Dotazy spouštěné z jiné oblasti, než je úč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 je 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 byste se pokusili spustit dotaz v případě, že není k dispozici dostatečná propustnost, vrátila by služba Azure Cosmos DB chybu HTTP 429. Jakékoli aktuální rozhraní API pro sady NoSQL SDK tento dotaz po krátkém čekání automaticky zopakuje. Omezené požadavky trvají delší dobu, takže zvýšením zřízené propustnosti můžete snížit latenci dotazů. Celkový počet omezených požadavků můžete sledovat v okně Metriky Azure Portal.

Zvýšení hodnoty MaxConcurrency

Paralelní dotazy fungují tak, že se paralelně dotazují na více oddílů. Data z jednotlivé dělené kolekce se ale načítají sériově s ohledem na dotaz. Pokud tedy nastavíte MaxConcurrency na počet oddílů, máte nejlepší šanci dosáhnout nejvýkonnějšího dotazu za předpokladu, že všechny ostatní systémové podmínky zůstanou stejné. Pokud neznáte počet oddílů, můžete nastavit maxConcurrency (nebo MaxDegreesOfParallelism ve starších verzích sady SDK) na vysoké číslo. Systém zvolí minimální (počet oddílů, vstup zadaný uživatelem) jako maximální stupeň paralelismu.

Zvýšení hodnoty MaxBufferedItemCount

Dotazy jsou navržené tak, aby předem načítá výsledky, zatímco klient zpracovává aktuální dávku výsledků. Předběžné načítání pomáhá zlepšit celkovou latenci dotazu. Nastavení maxBufferedItemCount omezí počet předem načtených výsledků. Pokud tuto hodnotu nastavíte na očekávaný počet vrácených výsledků (nebo vyšší číslo), může dotaz získat největší výhody z předběžného načtení. Pokud tuto hodnotu nastavíte na -1, systém automaticky určí počet položek, které se mají ukládat do vyrovnávací paměti.

Další kroky

V následujících článcích najdete informace o tom, jak měřit RU v jednotlivých dotazech, získat statistiky provádění pro ladění dotazů a další informace: