Frågeprestandatips för Azure Cosmos DB-SDK:er

GÄLLER FÖR: NoSQL

Azure Cosmos DB är en snabb, flexibel distribuerad databas som skalar sömlöst med garanterad svarstid och dataflödesnivåer. Du behöver inte göra större arkitekturändringar eller skriva komplex kod för att skala databasen med Azure Cosmos DB. Det är lika enkelt att skala upp och ned som att göra ett enda API-anrop. Mer information finns i etablera containerdataflöde eller etablera databasdataflöde.

Minska frågeplansanrop

För att köra en fråga måste en frågeplan skapas. Detta representerar i allmänhet en nätverksbegäran till Azure Cosmos DB Gateway, som lägger till svarstiden för frågeåtgärden. Det finns två sätt att ta bort den här begäran och minska svarstiden för frågeåtgärden:

Optimera enskilda partitionsfrågor med optimistisk direktkörning

Azure Cosmos DB NoSQL har en optimering med namnet Optimistic Direct Execution (ODE), som kan förbättra effektiviteten för vissa NoSQL-frågor. Mer specifikt omfattar frågor som inte kräver distribution sådana som kan köras på en enda fysisk partition eller som har svar som inte kräver sidnumrering. Frågor som inte kräver distribution kan med säkerhet hoppa över vissa processer, till exempel generering av frågeplan på klientsidan och frågeomskrivning, vilket minskar frågefördröjningen och RU-kostnaden. Om du anger partitionsnyckeln i själva begäran eller frågan (eller bara har en fysisk partition) och resultatet av frågan inte kräver sidnumrering kan ODE förbättra dina frågor.

Kommentar

Optimistisk direktkörning (ODE), som ger bättre prestanda för frågor som inte kräver distribution, bör inte förväxlas med direktläge, vilket är en sökväg för att ansluta programmet till serverdelsrepliker.

ODE är nu tillgängligt och aktiverat som standard i .NET SDK version 3.38.0 och senare. När du kör en fråga och anger en partitionsnyckel i själva begäran eller frågan, eller om databasen bara har en fysisk partition, kan frågekörningen utnyttja fördelarna med ODE. Om du vill inaktivera ODE anger du EnableOptimisticDirectExecution till false i QueryRequestOptions.

Enstaka partitionsfrågor med funktionerna GROUP BY, ORDER BY, DISTINCT och aggregering (som summa, medelvärde, min och max) kan ha stor nytta av att använda ODE. Men i scenarier där frågan riktar sig mot flera partitioner eller fortfarande kräver sidnumrering kan svarstiden för frågesvaret och RU-kostnaden vara högre än utan ode. När du använder ODE rekommenderar vi därför att du:

  • Ange partitionsnyckeln i själva anropet eller frågan.
  • Se till att datastorleken inte har ökat och att partitionen har delats upp.
  • Se till att frågeresultaten inte kräver sidnumrering för att få full nytta av ODE.

Här är några exempel på enkla frågor med en enskild partition som kan dra nytta av 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 

Det kan finnas fall där enskilda partitionsfrågor fortfarande kan kräva distribution om antalet dataobjekt ökar över tid och din Azure Cosmos DB-databas delar partitionen. Exempel på frågor där detta kan inträffa är:

- 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 

Vissa komplexa frågor kan alltid kräva distribution, även om du riktar in dig på en enda partition. Exempel på sådana frågor är:

- 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 

Det är viktigt att observera att ODE kanske inte alltid hämtar frågeplanen och därför inte kan tillåta eller inaktivera för frågor som inte stöds. Till exempel efter partitionsdelning är sådana frågor inte längre berättigade till ODE och körs därför inte eftersom utvärdering av frågeplanen på klientsidan blockerar dem. För att säkerställa kompatibilitet/tjänstkontinuitet är det viktigt att se till att endast frågor som stöds fullt ut i scenarier utan ODE (dvs. de kör och ger rätt resultat i det allmänna fallet med flera partitioner) används med ODE.

Kommentar

Användning av ODE kan potentiellt leda till att en ny typ av fortsättningstoken genereras. En sådan token känns inte igen av de äldre SDK:erna avsiktligt och detta kan resultera i ett felformaterat undantag för fortsättningstoken. Om du har ett scenario där token som genereras från de nyare SDK:erna används av en äldre SDK rekommenderar vi en metod i två steg för att uppgradera:

  • Uppgradera till den nya SDK:n och inaktivera ODE, båda tillsammans som en del av en enda distribution. Vänta tills alla noder har uppgraderats.
    • För att inaktivera ODE anger du EnableOptimisticDirectExecution till false i QueryRequestOptions.
  • Aktivera ODE som en del av den andra distributionen för alla noder.

Använda generering av lokal frågeplan

SQL SDK innehåller en intern ServiceInterop.dll för att parsa och optimera frågor lokalt. ServiceInterop.dll stöds endast på Windows x64-plattformen . Följande typer av program använder 32-bitars värdbearbetning som standard. Om du vill ändra värdbearbetning till 64-bitars bearbetning följer du dessa steg baserat på typen av program:

  • För körbara program kan du ändra värdbearbetningen genom att ange plattformsmålet till x64 i fönstret Projektegenskaperfliken Skapa .

  • För VSTest-baserade testprojekt kan du ändra värdbearbetningen genom att välja Test>Test Inställningar> Default Processor Architecture as X64 på Visual Studio Test-menyn.

  • För lokalt distribuerade ASP.NET webbprogram kan du ändra värdbearbetning genom att välja Använd 64-bitarsversionen av IIS Express för webbplatser och projekt under Verktyg>alternativ>Projekt och lösningar>Webbprojekt.

  • För ASP.NET webbprogram som distribueras i Azure kan du ändra värdbearbetningen genom att välja 64-bitarsplattformen i Programinställningar i Azure-portalen.

Kommentar

Som standard är nya Visual Studio-projekt inställda på Alla processorer. Vi rekommenderar att du ställer in projektet på x64 så att det inte växlar till x86. Ett projekt som är inställt på Valfri processor kan enkelt växla till x86 om ett x86-beroende läggs till.
ServiceInterop.dll måste finnas i mappen som SDK DLL körs från. Detta bör endast vara ett problem om du kopierar DLL:er manuellt eller har anpassade bygg-/distributionssystem.

Använda frågor med en enda partition

För frågor som riktar sig mot en partitionsnyckel genom att ange egenskapen PartitionKey i QueryRequestOptions och innehåller inga sammansättningar (inklusive Distinct, DCount, Group By). I det här exemplet filtreras partitionsnyckelfältet /state i på värdet Washington.

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

Du kan också ange partitionsnyckeln som en del av objektet för alternativ för begäran.

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

Kommentar

Frågor mellan partitioner kräver att SDK:t besöker alla befintliga partitioner för att söka efter resultat. Ju fler fysiska partitioner containern har, desto långsammare kan de potentiellt vara.

Undvik att återskapa iteratorn i onödan

När alla frågeresultat förbrukas av den aktuella komponenten behöver du inte återskapa iteratorn med fortsättningen för varje sida. Föredrar alltid att tömma frågan helt om inte sidnumreringen styrs av en annan anropande komponent:

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

Justera graden av parallellitet

För frågor justerar du egenskapen MaxConcurrency i QueryRequestOptions för att identifiera de bästa konfigurationerna för ditt program, särskilt om du utför frågor mellan partitioner (utan ett filter på partitionsnyckelvärdet). MaxConcurrency styr det maximala antalet parallella uppgifter, dvs. det maximala antalet partitioner som ska besökas parallellt. Om värdet anges till -1 kan SDK:et bestämma optimal samtidighet.

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

Låt oss anta att

  • D = Standard Maximalt antal parallella uppgifter (= totalt antal processorer på klientdatorn)
  • P = Användardefingivet maximalt antal parallella uppgifter
  • N = Antal partitioner som behöver besökas för att besvara en fråga

Följande är konsekvenserna av hur parallella frågor skulle bete sig för olika värden i P.

  • (P == 0) => Serieläge
  • (P == 1) => Maximalt en aktivitet
  • (P > 1) => Min (P, N) parallella uppgifter
  • (P < 1) => Min (N, D) parallella uppgifter

Justera sidstorleken

När du utfärdar en SQL-fråga returneras resultatet på ett segmenterat sätt om resultatuppsättningen är för stor.

Kommentar

Egenskapen MaxItemCount ska inte bara användas för sidnumrering. Dess huvudsakliga användning är att förbättra prestandan för frågor genom att minska det maximala antalet objekt som returneras på en enda sida.

Du kan också ange sidstorleken med hjälp av tillgängliga Azure Cosmos DB SDK:er. Med egenskapen MaxItemCount i QueryRequestOptions kan du ange det maximala antalet objekt som ska returneras i uppräkningsåtgärden. När MaxItemCount är inställt på -1 hittar SDK automatiskt det optimala värdet, beroende på dokumentstorleken. Till exempel:

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

När en fråga körs skickas resulterande data i ett TCP-paket. Om du anger ett för lågt värde för är antalet resor som krävs för MaxItemCountatt skicka data i TCP-paketet högt, vilket påverkar prestandan. Så om du inte är säker på vilket värde som ska anges för MaxItemCount egenskapen är det bäst att ställa in den på -1 och låta SDK:t välja standardvärdet.

Justera buffertstorleken

Parallell fråga är utformad för att hämta resultat i förväg medan den aktuella batchen med resultat bearbetas av klienten. Den här förhämtningen hjälper till att förbättra den övergripande svarstiden för en fråga. Egenskapen MaxBufferedItemCount i QueryRequestOptions begränsar antalet förhämtningsresultat. Ange MaxBufferedItemCount till det förväntade antalet returnerade resultat (eller ett högre tal) så att frågan kan ta emot den maximala fördelen med förhämtning. Om du anger värdet till -1 avgör systemet automatiskt antalet objekt som ska buffras.

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

Förhämtning fungerar på samma sätt oavsett graden av parallellitet, och det finns en enda buffert för data från alla partitioner.

Nästa steg

Om du vill veta mer om prestanda med hjälp av .NET SDK:

Minska frågeplansanrop

För att köra en fråga måste en frågeplan skapas. Detta representerar i allmänhet en nätverksbegäran till Azure Cosmos DB Gateway, som lägger till svarstiden för frågeåtgärden.

Använda cachelagring av frågeplan

Frågeplanen, för en fråga som är begränsad till en enda partition, cachelagras på klienten. Detta eliminerar behovet av att göra ett anrop till gatewayen för att hämta frågeplanen efter det första anropet. Nyckeln för den cachelagrade frågeplanen är SQL-frågesträngen. Du måste kontrollera att frågan är parametriserad. Annars är cachesökningen för frågeplanen ofta en cachemiss eftersom frågesträngen sannolikt inte är identisk mellan anrop. Cachelagring av frågeplan är aktiverat som standard för Java SDK version 4.20.0 och senare och för Spring Data Azure Cosmos DB SDK version 3.13.0 och senare.

Använda parametriserade frågor med en enskild partition

För parametriserade frågor som är begränsade till en partitionsnyckel med setPartitionKey i CosmosQueryRequestOptions och som inte innehåller några aggregeringar (inklusive Distinct, DCount, Group By) kan frågeplanen undvikas:

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);

Kommentar

Frågor mellan partitioner kräver att SDK:t besöker alla befintliga partitioner för att söka efter resultat. Ju fler fysiska partitioner containern har, desto långsammare kan de potentiellt vara.

Justera graden av parallellitet

Parallella frågor fungerar genom att köra frågor mot flera partitioner parallellt. Data från en enskild partitionerad container hämtas dock seriellt med avseende på frågan. Använd därför setMaxDegreeOfParallelismCosmosQueryRequestOptions för att ange värdet till det antal partitioner du har. Om du inte känner till antalet partitioner kan du använda setMaxDegreeOfParallelism för att ange ett högt tal och systemet väljer det lägsta antalet (antal partitioner, användarindata) som maximal grad av parallellitet. Om värdet anges till -1 kan SDK:et bestämma optimal samtidighet.

Det är viktigt att observera att parallella frågor ger de bästa fördelarna om data fördelas jämnt över alla partitioner med avseende på frågan. Om den partitionerade containern partitioneras på ett sådant sätt att alla eller en majoritet av de data som returneras av en fråga koncentreras till några partitioner (en partition i värsta fall), skulle frågans prestanda försämras.

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);

Låt oss anta att

  • D = Standard Maximalt antal parallella uppgifter (= totalt antal processorer på klientdatorn)
  • P = Användardefingivet maximalt antal parallella uppgifter
  • N = Antal partitioner som behöver besökas för att besvara en fråga

Följande är konsekvenserna av hur parallella frågor skulle bete sig för olika värden i P.

  • (P == 0) => Serieläge
  • (P == 1) => Maximalt en aktivitet
  • (P > 1) => Min (P, N) parallella uppgifter
  • (P == -1) => Min (N, D) parallella uppgifter

Justera sidstorleken

När du utfärdar en SQL-fråga returneras resultatet på ett segmenterat sätt om resultatuppsättningen är för stor. Som standard returneras resultaten i segment på 100 objekt eller 4 MB, beroende på vilken gräns som uppnås först. Om du ökar sidstorleken minskar antalet tur och returresor och prestandan för frågor som returnerar fler än 100 objekt. Om du inte är säker på vilket värde som ska anges är 1 000 vanligtvis ett bra val. Minnesförbrukningen ökar när sidstorleken ökar, så om din arbetsbelastning är minneskänslig bör du överväga ett lägre värde.

Du kan använda parametern pageSize i iterableByPage() för synkroniserings-API och byPage() för asynkront API för att definiera en sidstorlek:

//  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();

Justera buffertstorleken

Parallell fråga är utformad för att hämta resultat i förväg medan den aktuella batchen med resultat bearbetas av klienten. Förhämtningen hjälper till med övergripande svarstidsförbättringar för en fråga. setMaxBufferedItemCount i CosmosQueryRequestOptions begränsar antalet förhämtningsresultat. Om du vill maximera förhämtningen maxBufferedItemCount anger du till ett högre tal än pageSize (OBS! Detta kan också leda till hög minnesförbrukning). Om du vill minimera förhämtningen maxBufferedItemCount anger du lika pageSizemed . Om du anger det här värdet till 0 avgör systemet automatiskt hur många objekt som ska buffras.

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);

Förhämtning fungerar på samma sätt oavsett graden av parallellitet, och det finns en enda buffert för data från alla partitioner.

Nästa steg

Mer information om prestanda med Hjälp av Java SDK:

Minska frågeplansanrop

För att köra en fråga måste en frågeplan skapas. Detta representerar i allmänhet en nätverksbegäran till Azure Cosmos DB Gateway, som lägger till svarstiden för frågeåtgärden. Det finns ett sätt att ta bort den här begäran och minska svarstiden för den enskilda partitionsfrågeåtgärden. För enstaka partitionsfrågor anger du partitionsnyckelvärdet för objektet och skickar det som partition_key argument:

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

Justera sidstorleken

När du utfärdar en SQL-fråga returneras resultatet på ett segmenterat sätt om resultatuppsättningen är för stor. Med max_item_count kan du ange det maximala antalet objekt som ska returneras i uppräkningsåtgärden.

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

Nästa steg

Mer information om hur du använder Python SDK för API för NoSQL:

Minska frågeplansanrop

För att köra en fråga måste en frågeplan skapas. Detta representerar i allmänhet en nätverksbegäran till Azure Cosmos DB Gateway, som lägger till svarstiden för frågeåtgärden. Det finns ett sätt att ta bort den här begäran och minska svarstiden för den enskilda partitionsfrågeåtgärden. För enstaka partitionsfrågor kan du utföra två sätt att omfångsrikta en fråga till en enskild partition.

Använda ett parameteriserat frågeuttryck och ange partitionsnyckel i frågeuttrycket. Frågan är programmatiskt sammansatt för :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}`);
}

Eller ange partitionKey i FeedOptions och skicka det som 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}`);
}

Justera sidstorleken

När du utfärdar en SQL-fråga returneras resultatet på ett segmenterat sätt om resultatuppsättningen är för stor. Med maxItemCount kan du ange det maximala antalet objekt som ska returneras i uppräkningsåtgärden.

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}`);
}

Nästa steg

Mer information om hur du använder Node.js SDK för API för NoSQL: