Tipy k výkonu dotazů pro sady SDK služby Azure Cosmos DB

PLATÍ PRO: NoSQL

Azure Cosmos DB je rychlá a flexibilní distribuovaná databáze, která se bezproblémově škáluje s garantovanou latencí a úrovněmi propustnosti. Nemusíte provádět významné změny architektury ani psát složitý kód pro škálování databáze pomocí služby Azure Cosmos DB. Vertikální navýšení a snížení kapacity je stejně snadné jako vytvoření jediného volání rozhraní API. Další informace najdete v tématu zřízení propustnosti kontejneru nebo zřízení propustnosti databáze.

Omezení volání plánu dotazů

Pokud chcete spustit dotaz, je potřeba sestavit plán dotazu. To obecně představuje síťový požadavek na bránu Azure Cosmos DB, která přidává latenci operace dotazu. Tento požadavek můžete odebrat dvěma způsoby a snížit latenci operace dotazu:

Optimalizace dotazů s jedním oddílem pomocí optimistického přímého spuštění

Azure Cosmos DB NoSQL má optimalizaci s názvem Optimistic Direct Execution (ODE), která může zlepšit efektivitu určitých dotazů NoSQL. Konkrétně dotazy, které nevyžadují distribuci, zahrnují ty, které je možné spouštět v jednom fyzickém oddílu nebo které obsahují odpovědi, které nevyžadují stránkování. Dotazy, které nevyžadují distribuci, můžou s jistotou přeskočit některé procesy, jako je generování plánu dotazů na straně klienta a přepsání dotazů, což snižuje latenci dotazů a náklady na RU. Pokud v požadavku nebo samotném dotazu zadáte klíč oddílu (nebo máte jenom jeden fyzický oddíl) a výsledky dotazu nevyžadují stránkování, může ODE vaše dotazy vylepšit.

Poznámka:

Optimistické přímé spouštění (ODE), které nabízí lepší výkon pro dotazy, které nevyžadují distribuci, by se nemělo zaměňovat s přímým režimem, což je cesta pro připojení aplikace k back-endovým replikám.

Ode je nyní k dispozici a ve výchozím nastavení je povolená v sadě .NET SDK verze 3.38.0 a novější. Při spuštění dotazu a zadání klíče oddílu v požadavku nebo samotném dotazu nebo v databázi je pouze jeden fyzický oddíl, může provádění dotazu využívat výhody ODE. Chcete-li zakázat ODE, nastavte EnableOptimisticDirectExecution na false v QueryRequestOptions.

Dotazy s jedním oddílem, které obsahují funkce GROUP BY, ORDER BY, DISTINCT a agregační funkce (například součet, střední hodnota, minimum a maximum), můžou výrazně těžit z použití ODE. V situacích, kdy dotaz cílí na více oddílů nebo stále vyžaduje stránkování, ale latence odpovědi na dotazy a náklady na RU můžou být vyšší než bez použití ODE. Proto při použití odepisu doporučujeme:

  • Zadejte klíč oddílu v samotném volání nebo dotazu.
  • Ujistěte se, že se velikost dat nezvětšila a způsobila rozdělení oddílu.
  • Ujistěte se, že výsledky dotazu nevyžadují stránkování, abyste získali plnou výhodu ODE.

Tady je několik příkladů jednoduchých dotazů na jeden oddíl, které můžou využívat ODE:

- SELECT * FROM r
- SELECT * FROM r WHERE r.pk == "value"
- SELECT * FROM r WHERE r.id > 5
- SELECT r.id FROM r JOIN id IN r.id
- SELECT TOP 5 r.id FROM r ORDER BY r.id
- SELECT * FROM r WHERE r.id > 5 OFFSET 5 LIMIT 3 

V některých případech můžou dotazy na jeden oddíl vyžadovat distribuci, pokud se počet datových položek v průběhu času zvýší a databáze Azure Cosmos DB rozdělí oddíl. Mezi příklady dotazů, ve kterých by k tomu mohlo dojít, patří:

- SELECT Count(r.id) AS count_a FROM r
- SELECT DISTINCT r.id FROM r
- SELECT Max(r.a) as min_a FROM r
- SELECT Avg(r.a) as min_a FROM r
- SELECT Sum(r.a) as sum_a FROM r WHERE r.a > 0 

Některé složité dotazy můžou vždy vyžadovat distribuci, i když cílí na jeden oddíl. Mezi příklady takových dotazů patří:

- SELECT Sum(id) as sum_id FROM r JOIN id IN r.id
- SELECT DISTINCT r.id FROM r GROUP BY r.id
- SELECT DISTINCT r.id, Sum(r.id) as sum_a FROM r GROUP BY r.id
- SELECT Count(1) FROM (SELECT DISTINCT r.id FROM root r)
- SELECT Avg(1) AS avg FROM root r 

Je důležité si uvědomit, že ODE nemusí vždy načíst plán dotazu a v důsledku toho nemůže zakázat nebo vypnout nepodporované dotazy. Například po rozdělení oddílů už tyto dotazy nemají nárok na ODE a proto se nespustí, protože vyhodnocení plánu dotazů na straně klienta je zablokuje. Aby se zajistila kompatibilita nebo kontinuita služeb, je důležité zajistit, aby se u ODE používaly pouze dotazy, které jsou plně podporované ve scénářích bez odepisu (tj. provádějí a vytvářejí správný výsledek v obecném případě s více oddíly).

Poznámka:

Použití ODE může potenciálně způsobit vygenerování nového typu tokenu pro pokračování. Tyto tokeny nejsou záměrně rozpoznány staršími sadami SDK, což by mohlo vést k výjimce tokenu pokračování s poškozeným formátem. Pokud máte scénář, ve kterém jsou tokeny vygenerované z novějších sad SDK používány starší sadou SDK, doporučujeme k upgradu provést dvoustupňový přístup:

  • Upgradujte na novou sadu SDK a zakažte ODE společně v rámci jednoho nasazení. Počkejte na upgrade všech uzlů.
    • Chcete-li zakázat ODE, nastavte EnableOptimisticDirectExecution na false v QueryRequestOptions.
  • Povolte ODE jako součást druhého nasazení pro všechny uzly.

Použití generování místního plánu dotazů

Sada SQL SDK obsahuje nativní ServiceInterop.dll k analýze a optimalizaci dotazů místně. ServiceInterop.dll se podporuje jenom na platformě Windows x64 . Následující typy aplikací ve výchozím nastavení používají 32bitové zpracování hostitele. Pokud chcete změnit zpracování hostitele na 64bitové zpracování, postupujte podle tohoto postupu na základě typu aplikace:

  • U spustitelných aplikací můžete změnit zpracování hostitele nastavením cíle platformy na x64 v okně Vlastnosti projektu na kartě Sestavení .

  • U projektů testů založených na VSTest můžete změnit zpracování hostitele výběrem >testovacího testu Nastavení> Default Processor Architecture jako X64 v nabídce Visual Studio Test.

  • U místně nasazených ASP.NET webových aplikací můžete změnit zpracování hostitele tak, že vyberete Možnost Použít 64bitovou verzi služby IIS Express pro weby a projekty v části Nástroje>Možnosti>projektů a webových projektů řešení.>

  • U ASP.NET webových aplikací nasazených v Azure můžete změnit zpracování hostitele výběrem 64bitové platformy v nastavení aplikace na webu Azure Portal.

Poznámka:

Ve výchozím nastavení jsou nové projekty sady Visual Studio nastavené na libovolný procesor. Doporučujeme nastavit projekt na x64 , aby se nepřepnul na x86. Projekt nastavený na libovolný procesor se může snadno přepnout na x86 , pokud je přidána závislost jen pro platformu x86.
ServiceInterop.dll musí být ve složce, ze které se spouští knihovna DLL sady SDK. To by mělo být důležité jenom v případě, že ručně kopírujete knihovny DLL nebo máte vlastní systémy sestavení a nasazení.

Použití dotazů s jedním oddílem

Pro dotazy, které cílí na klíč oddílu nastavením vlastnosti PartitionKey v QueryRequestOptions a neobsahují žádné agregace (včetně Distinct, DCount, Group By). V tomto příkladu je pole klíče oddílu /state filtrováno podle hodnoty Washington.

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle' AND c.state = 'Washington'"
{
    // ...
}

Volitelně můžete klíč oddílu zadat jako součást objektu možností požadavku.

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Washington")}))
{
    // ...
}

Poznámka:

Dotazy napříč oddíly vyžadují, aby sada SDK navštívila všechny existující oddíly, aby zkontrolovala výsledky. Čím více fyzických oddílů kontejner má, tím pomalejší můžou být.

Vyhněte se zbytečnému opětovnému vytvoření iterátoru.

Pokud aktuální komponenta spotřebovává všechny výsledky dotazu, nemusíte iterátor znovu vytvářet s pokračováním pro každou stránku. Vždy dáváte přednost úplnému vyprázdnění dotazu, pokud stránkování neřídí jiná volající komponenta:

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Washington")}))
{
    while (feedIterator.HasMoreResults) 
    {
        foreach(MyItem document in await feedIterator.ReadNextAsync())
        {
            // Iterate through documents
        }
    }
}

Ladění stupně paralelismu

V případě dotazů vylaďte vlastnost QueryRequestOptions MaxConcurrency tak, aby identifikovala nejlepší konfigurace pro vaši aplikaci, zejména pokud provádíte dotazy napříč oddíly (bez filtru hodnoty klíče oddílu). MaxConcurrency určuje maximální počet paralelních úloh, tj. maximální počet oddílů, které se mají paralelně navštěvovat. Nastavení hodnoty -1 umožní sadě SDK rozhodnout o optimální souběžnosti.

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { 
        PartitionKey = new PartitionKey("Washington"),
        MaxConcurrency = -1 }))
{
    // ...
}

Předpokládejme, že

  • D = Výchozí maximální počet paralelních úloh (= celkový počet procesorů v klientském počítači)
  • P = Maximální počet paralelních úloh zadaný uživatelem
  • N = počet oddílů, které je potřeba navštívit při odpovídání na dotaz

Následují důsledky chování paralelních dotazů pro různé hodnoty P.

  • (P == 0) => Sériový režim
  • (P == 1) => Maximálně jeden úkol
  • (P > 1) => minimální (P, N) paralelní úkoly
  • (P < 1) => minimální (N, D) paralelní úkoly

Vyladění velikosti stránky

Když vydáte dotaz SQL, výsledky se vrátí segmentovaným způsobem, pokud je sada výsledků příliš velká.

Poznámka:

Vlastnost MaxItemCount by se neměla používat jenom pro stránkování. Jejím hlavním využitím je zlepšení výkonu dotazů snížením maximálního počtu položek vrácených na jedné stránce.

Velikost stránky můžete také nastavit pomocí dostupných sad SDK služby Azure Cosmos DB. Vlastnost MaxItemCount umožňuje QueryRequestOptions nastavit maximální počet položek, které mají být vráceny v operaci výčtu. Když MaxItemCount je nastavená hodnota -1, sada SDK automaticky najde optimální hodnotu v závislosti na velikosti dokumentu. Příklad:

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { 
        PartitionKey = new PartitionKey("Washington"),
        MaxItemCount = 1000}))
{
    // ...
}

Při spuštění dotazu se výsledná data odesílají v paketu TCP. Pokud zadáte příliš nízkou hodnotu pro MaxItemCount, počet cest potřebných k odeslání dat v paketu TCP je vysoký, což má vliv na výkon. Pokud si tedy nejste jistí, jakou hodnotu pro vlastnost nastavit MaxItemCount , je nejlepší ji nastavit na -1 a nechat sadu SDK zvolit výchozí hodnotu.

Vyladění velikosti vyrovnávací paměti

Paralelní dotaz je navržený tak, aby předem načítá výsledky, zatímco klient zpracovává aktuální dávku výsledků. Toto předběžné načítání pomáhá zlepšit celkovou latenci dotazu. Vlastnost MaxBufferedItemCount v QueryRequestOptions omezení počtu předem načtených výsledků. Nastavte MaxBufferedItemCount očekávaný počet vrácených výsledků (nebo vyšší číslo), aby dotaz získal maximální výhodu před načtením. 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.

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { 
        PartitionKey = new PartitionKey("Washington"),
        MaxBufferedItemCount = -1}))
{
    // ...
}

Předběžné načítání funguje stejně bez ohledu na stupeň paralelismu a pro data ze všech oddílů existuje jedna vyrovnávací paměť.

Další kroky

Další informace o výkonu pomocí sady .NET SDK:

Omezení volání plánu dotazů

Pokud chcete spustit dotaz, je potřeba sestavit plán dotazu. To obecně představuje síťový požadavek na bránu Azure Cosmos DB, která přidává latenci operace dotazu.

Použití ukládání do mezipaměti plánu dotazů

Plán dotazu s oborem dotazu na jeden oddíl je uložen v mezipaměti klienta. Tím se eliminuje nutnost volat bráně, aby se po prvním volání načetl plán dotazu. Klíčem plánu dotazů v mezipaměti je řetězec dotazu SQL. Musíte se ujistit, že je dotaz parametrizovaný. Pokud ne, vyhledávání v mezipaměti plánu dotazů bude často neúspěšné, protože řetězec dotazu pravděpodobně nebude identický napříč voláními. Ukládání plánů dotazů do mezipaměti je ve výchozím nastavení povolené pro sadu Java SDK verze 4.20.0 a vyšší a pro sadu Spring Data Azure Cosmos DB SDK verze 3.13.0 a vyšší.

Použití parametrizovaných dotazů na jeden oddíl

U parametrizovaných dotazů, které jsou vymezeny na klíč oddílu s parametrem setPartitionKeyCosmosQueryRequestOptions a neobsahují žádné agregace (včetně Distinct, DCount, Group By), je možné se vyhnout plánu dotazu:

CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("Washington"));

ArrayList<SqlParameter> paramList = new ArrayList<SqlParameter>();
paramList.add(new SqlParameter("@city", "Seattle"));
SqlQuerySpec querySpec = new SqlQuerySpec(
        "SELECT * FROM c WHERE c.city = @city",
        paramList);

//  Sync API
CosmosPagedIterable<MyItem> filteredItems = 
    container.queryItems(querySpec, options, MyItem.class);

//  Async API
CosmosPagedFlux<MyItem> filteredItems = 
    asyncContainer.queryItems(querySpec, options, MyItem.class);

Poznámka:

Dotazy napříč oddíly vyžadují, aby sada SDK navštívila všechny existující oddíly, aby zkontrolovala výsledky. Čím více fyzických oddílů kontejner má, může to být pomalé.

Ladění stupně paralelismu

Paralelní dotazy fungují dotazováním více oddílů paralelně. Data z jednotlivého děleného kontejneru se ale načítají sériově s ohledem na dotaz. Proto pomocí setMaxDegreeOfParallelism nastavte CosmosQueryRequestOptions hodnotu na počet oddílů, které máte. Pokud neznáte počet oddílů, můžete použít setMaxDegreeOfParallelism k nastavení vysokého čísla a systém jako maximální stupeň paralelismu zvolí minimum (počet oddílů, zadaný vstup uživatelem). Nastavení hodnoty -1 umožní sadě SDK rozhodnout o optimální souběžnosti.

Je důležité si uvědomit, že paralelní dotazy přinášejí nejlepší výhody, pokud se data rovnoměrně distribuují napříč všemi oddíly s ohledem na dotaz. Pokud je dělený kontejner rozdělený tak, aby se všechna nebo většina dat vrácených dotazem soustředila do několika oddílů (v nejhorším případě jeden oddíl), výkon dotazu by byl snížený.

CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("Washington"));
options.setMaxDegreeOfParallelism(-1);

// Define the query

//  Sync API
CosmosPagedIterable<MyItem> filteredItems = 
    container.queryItems(querySpec, options, MyItem.class);

//  Async API
CosmosPagedFlux<MyItem> filteredItems = 
    asyncContainer.queryItems(querySpec, options, MyItem.class);

Předpokládejme, že

  • D = Výchozí maximální počet paralelních úloh (= celkový počet procesorů v klientském počítači)
  • P = Maximální počet paralelních úloh zadaný uživatelem
  • N = počet oddílů, které je potřeba navštívit při odpovídání na dotaz

Následují důsledky chování paralelních dotazů pro různé hodnoty P.

  • (P == 0) => Sériový režim
  • (P == 1) => Maximálně jeden úkol
  • (P > 1) => minimální (P, N) paralelní úkoly
  • (P == -1) => Min (N, D) paralelní úkoly

Vyladění velikosti stránky

Když vydáte dotaz SQL, výsledky se vrátí segmentovaným způsobem, pokud je sada výsledků příliš velká. Ve výchozím nastavení se výsledky vrátí v blocích 100 položek nebo 4 MB, podle toho, které omezení se dosáhne prvního. Zvětšením velikosti stránky se sníží počet požadovaných odezv a zvýší se výkon dotazů, které vracejí více než 100 položek. Pokud si nejste jistí, jakou hodnotu nastavit, je 1000 obvykle dobrou volbou. Při nárůstu velikosti stránky se zvýší spotřeba paměti, takže pokud je zatížení citlivé na paměť, zvažte nižší hodnotu.

K definování velikosti stránky můžete použít pageSize parametr pro iterableByPage() synchronizační rozhraní API a byPage() asynchronní rozhraní API:

//  Sync API
Iterable<FeedResponse<MyItem>> filteredItemsAsPages =
    container.queryItems(querySpec, options, MyItem.class).iterableByPage(continuationToken,pageSize);

for (FeedResponse<MyItem> page : filteredItemsAsPages) {
    for (MyItem item : page.getResults()) {
        //...
    }
}

//  Async API
Flux<FeedResponse<MyItem>> filteredItemsAsPages =
    asyncContainer.queryItems(querySpec, options, MyItem.class).byPage(continuationToken,pageSize);

filteredItemsAsPages.map(page -> {
    for (MyItem item : page.getResults()) {
        //...
    }
}).subscribe();

Vyladění velikosti vyrovnávací paměti

Paralelní dotaz je 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á při celkovém zlepšení latence dotazu. setMaxBufferedItemCount v CosmosQueryRequestOptions limitech počtu předem načtených výsledků. Pokud chcete maximalizovat předběžné načítání, nastavte maxBufferedItemCount na vyšší číslo, než pageSize je (POZNÁMKA: To může také vést k vysokému využití paměti). Chcete-li minimalizovat předběžné načtení, nastavte hodnotu rovna maxBufferedItemCount hodnotě pageSize. Pokud tuto hodnotu nastavíte na hodnotu 0, systém automaticky určí počet položek, které se mají ukládat do vyrovnávací paměti.

CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("Washington"));
options.setMaxBufferedItemCount(-1);

// Define the query

//  Sync API
CosmosPagedIterable<MyItem> filteredItems = 
    container.queryItems(querySpec, options, MyItem.class);

//  Async API
CosmosPagedFlux<MyItem> filteredItems = 
    asyncContainer.queryItems(querySpec, options, MyItem.class);

Předběžné načítání funguje stejně bez ohledu na stupeň paralelismu a pro data ze všech oddílů existuje jedna vyrovnávací paměť.

Další kroky

Další informace o výkonu pomocí sady Java SDK:

Omezení volání plánu dotazů

Pokud chcete spustit dotaz, je potřeba sestavit plán dotazu. To obecně představuje síťový požadavek na bránu Azure Cosmos DB, která přidává latenci operace dotazu. Existuje způsob, jak tento požadavek odebrat a snížit latenci operace dotazu s jedním oddílem. U dotazů s jedním oddílem zadejte hodnotu klíče oddílu pro položku a předejte ji jako argument partition_key :

items = container.query_items(
        query="SELECT * FROM r where r.city = 'Seattle'",
        partition_key="Washington"
    )

Vyladění velikosti stránky

Když vydáte dotaz SQL, výsledky se vrátí segmentovaným způsobem, pokud je sada výsledků příliš velká. Max_item_count umožňuje nastavit maximální počet položek, které se mají vrátit v operaci výčtu.

items = container.query_items(
        query="SELECT * FROM r where r.city = 'Seattle'",
        partition_key="Washington",
        max_item_count=1000
    )

Další kroky

Další informace o používání sady Python SDK pro rozhraní API pro NoSQL:

Omezení volání plánu dotazů

Pokud chcete spustit dotaz, je potřeba sestavit plán dotazu. To obecně představuje síťový požadavek na bránu Azure Cosmos DB, která přidává latenci operace dotazu. Existuje způsob, jak tento požadavek odebrat a snížit latenci operace dotazu s jedním oddílem. U dotazů s jedním oddílem, které dotaz rozdělují na jeden oddíl, je možné provést dvěma způsoby.

Použití parametrizovaného výrazu dotazu a zadání klíče oddílu v příkazu dotazu. Dotaz se programově skládá z SELECT * FROM todo t WHERE t.partitionKey = 'Bikes, Touring Bikes':

// find all items with same categoryId (partitionKey)
const querySpec = {
    query: "select * from products p where p.categoryId=@categoryId",
    parameters: [
        {
            name: "@categoryId",
            value: "Bikes, Touring Bikes"
        }
    ]
};

// Get items 
const { resources } = await container.items.query(querySpec).fetchAll();

for (const item of resources) {
    console.log(`${item.id}: ${item.name}, ${item.sku}`);
}

Nebo zadejte partitionKeyFeedOptions a předejte ho jako argument:

const querySpec = {
    query: "select * from products p"
};

const { resources } = await container.items.query(querySpec, { partitionKey: "Bikes, Touring Bikes" }).fetchAll();

for (const item of resources) {
    console.log(`${item.id}: ${item.name}, ${item.sku}`);
}

Vyladění velikosti stránky

Když vydáte dotaz SQL, výsledky se vrátí segmentovaným způsobem, pokud je sada výsledků příliš velká. MaxItemCount umožňuje nastavit maximální počet položek, které se mají vrátit v operaci výčtu.

const querySpec = {
    query: "select * from products p where p.categoryId=@categoryId",
    parameters: [
        {
            name: "@categoryId",
            value: items[2].categoryId
        }
    ]
};

const { resources } = await container.items.query(querySpec, { maxItemCount: 1000 }).fetchAll();

for (const item of resources) {
    console.log(`${item.id}: ${item.name}, ${item.sku}`);
}

Další kroky

Další informace o používání sady Node.js SDK pro rozhraní API pro NoSQL: