Ř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.
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 pokračování, který umožňuje 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 :
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ů 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 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ů 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 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 JOIN
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:
Vlastnosti v zásadách indexování služby Azure Cosmos DB rozlišují malá a velká písmena.
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 stále sledovat 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 jiný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ů agregační systémové funkce ve službě Azure Cosmos DB používají index. V závislosti na filtrech nebo jiný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 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, aby se agregace vypočítala.
Například vzhledem k těmto dvěma ukázkovým dotazům je 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é nebudou plně použí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
Poplatky za RU dotazů s GROUP BY
rostoucí kardinalitou vlastností v GROUP BY
klauzuli se zvyšují. Například v následujícím dotazu se s rostoucím počtem jedinečných popisů zvýší poplatky za RU dotazu.
Poplatky za RU agregační funkce s GROUP BY
klauzulí jsou vyšší než poplatky za RU agregační funkce samotné. 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, jsou 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
. Tato změna dotazu zajišťuje, že používá složený index.
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 index odpovídá 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 v každé ze tří polí 10 položek, rozšíří se na 1 x 10 x 10 x 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 má váš dotaz filtr rovnosti, který odpovídá klíči oddílu vašeho kontejneru, musíte zkontrolovat pouze 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, musí 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, kontrolují pouze jeden nebo více relevantních fyzických oddílů 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 s filtry na více vlastnostech obvykle používají index rozsahu, jsou 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 rozsahu. 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 spouštěné z jiné oblasti než účet služby Azure Cosmos DB mají 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ů, zadaný vstup 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ší: