Sdílet prostřednictvím


Ř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 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 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 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.

Běžné problémy se sadou SDK

Než si přečtete tuto příručku, je vhodné zvážit běžné problémy se sadou SDK, které nesouvisejí s dotazovacím modulem.

  • 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 tyto účely můžou být:
    • Sada SDK může provádět více síťových volání.
    • Načtení dokumentů může trvat delší 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 webu Azure Portal. Po spuštění dotazu v Průzkumníku dat se metriky dotazu zobrazí vedle karty Výsledky :

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řeboval k načtení. Počet výstupních dokumentů je počet dokumentů potřebných pro výsledky dotazu. Pokud je počet načtených dokumentů výrazně vyšší než počet výstupních dokumentů, doš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, kde načtený počet dokumentů překračuje počet výstupních dokumentů

Počet načtených dokumentů je počet dokumentů, které dotazovací modul potřeboval 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ů, doš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:

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 měly zahrnovat všechny vlastnosti zahrnuté v WHERE klauzulích, ORDER BY klauzulích JOINa 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:

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

Pokud spustíte následující jednoduchý dotaz na datovou sadu výživy , při indexování vlastnosti v WHERE klauzuli uvidíte mnohem nižší poplatek 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řidávat 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
  • Šipka vlevo
  • Podřetětěžka – 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í WHERE klauzule musí každý dokument načíst:

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

Tyto systémové funkce můžou používat indexy s výjimkou případů, kdy se používají v dotazech s agregacemi:

Systémová funkce Návrhy pro optimalizaci
Funkce prostorového systému Uložení výsledku dotazu do materializovaného 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.

Konkrétnější je, že jakákoli systémová funkce, jejíž poplatky ZA RU se zvyšují, protože se zvyšuje kardinalita vlastnosti, může být přínosem ORDER BY v dotazu. Tyto dotazy prověřují index, takže výsledky dotazu seřadí, aby byl dotaz efektivnější.

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

  • StartsWith (where case-insensitive = true)
  • StringEquals (where case-insensitive = true)
  • Contains
  • RegexMatch
  • EndsWith

Představte si například následující dotaz s výrazem CONTAINS. CONTAINS bude používat 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 sledovat velmi vysoké poplatky za RU.

Původní dotaz:

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

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

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

Stejná optimalizace může pomoct s dotazy 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 vylepšit přidáním ORDER BY a složeným indexem 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ů budou agregační systémové funkce ve službě Azure Cosmos DB používat index. V závislosti na filtrech nebo dalších klauzulích v agregačním dotazu ale může být dotazovací modul nutný k načtení velkého počtu dokumentů. Dotazovací modul obvykle použije nejprve 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, aby se agregace vypočítala.

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

Dotaz s pouze CONTAINS filtrem – vyšší poplatky 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é plně nepoužívají 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

Poplatky za RU dotazů s GROUP BY rostoucí kardinalitou vlastností v GROUP BY klauzuli se zvýší. V následujícím dotazu se například s rostoucím počtem jedinečných popisů zvýší poplatky za RU dotazu.

Poplatky za RU agregační funkce s GROUP BY klauzulí budou vyšší než poplatky 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 Azure Cosmos DB než spouštění jednotlivých dotazů.

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

I když dotazy, které mají filtr a ORDER BY klauzuli, obvykle používají index rozsahu, budou efektivnější, pokud je lze obsluhovat ze složeného indexu. Kromě úprav zásad indexování byste měli do klauzule 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 tím, že za každý výraz select-many nasdílí predikáty místo po všech křížových spojeních v WHERE klauzuli.

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

Pro tento dotaz bude index odpovídat jakémukoli dokumentu, který má značku s názvem infant formula, nutritionValue větší než 0 a amount větší než 1. Výraz JOIN zde provede křížový součin všech položek značek, živin a obsluhuje pole 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> řazenou kolekci členů.

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

Tento dotaz je ekvivalentní předchozímu dotazu, ale používá poddotazy:

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 živiny i pole obsluhy. Výrazy JOIN se rozšíří na 1 x 1 x 5 x 5 = 25 položek, na rozdíl od 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ů, jako jsou dotazy používající 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í 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.

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ů.

Pokud například vytvoříte kontejner s klíčem oddílu foodGroup, budou muset následující dotazy 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, zkontrolují pouze relevantní 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 pro klíč oddílu, budou muset "ventilátorovat" a zkontrolovat výsledky v každém indexu 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 vlastnostech, 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 bez rovnosti 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 relevantní 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í 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 jste se pokusili spustit dotaz, pokud není k dispozici dostatek propustnosti, služba Azure Cosmos DB vrátí chybu HTTP 429. Některé z aktuálních sad API pro sady SDK NoSQL tento dotaz po krátké době automaticky zopakují. 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 na webu Azure Portal.

Zvýšení maxconcurrency

Paralelní dotazy fungují dotazováním více oddílů paralelně. 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čtení pomáhá zlepšit celkovou latenci dotazu. Nastavení MaxBufferedItemCount omezuje 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ýhodu při předběžném načtení. Pokud tuto hodnotu nastavíte na hodnotu -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 měření RU na dotaz, získání statistik provádění pro ladění dotazů a další: