Share via


Query-problemen bij het gebruik van Azure Cosmos DB oplossen

VAN TOEPASSING OP: NoSQL

In dit artikel wordt een algemene aanbevolen aanpak beschreven voor het oplossen van problemen met query's in Azure Cosmos DB. Hoewel u niet rekening moet houden met de stappen die in dit artikel worden beschreven, moet u een volledige verdediging tegen potentiële queryproblemen overwegen, maar we hebben hier de meest voorkomende tips voor prestaties opgenomen. Gebruik dit artikel als uitgangspunt voor het oplossen van problemen met trage of dure query's in Azure Cosmos DB voor NoSQL. U kunt ook diagnostische logboeken gebruiken om te bepalen welke query's langzaam zijn of welke query's aanzienlijke hoeveelheden doorvoer gebruiken. Als u de API van Azure Cosmos DB voor MongoDB gebruikt, moet u de API van Azure Cosmos DB gebruiken voor het oplossen van problemen met MongoDB-query's

Queryoptimalisaties in Azure Cosmos DB worden globaal als volgt gecategoriseerd:

  • Optimalisaties die de ru-kosten (Request Unit) van de query verminderen
  • Optimalisaties die alleen latentie verminderen

Als u de RU-kosten van een query verlaagt, vermindert u doorgaans ook de latentie.

Dit artikel bevat voorbeelden die u opnieuw kunt maken met behulp van de voedingsgegevensset.

Veelvoorkomende SDK-problemen

Voordat u deze handleiding leest, is het handig om veelvoorkomende SDK-problemen te overwegen die niet zijn gerelateerd aan de query-engine.

  • Volg deze tips voor sdk-prestaties voor query's.
  • Soms bevatten query's lege pagina's, zelfs wanneer er resultaten zijn op een toekomstige pagina. Redenen hiervoor kunnen zijn:
    • De SDK kan meerdere netwerkoproepen uitvoeren.
    • Het kan lang duren voordat de query de documenten heeft opgehaald.
  • Alle query's hebben een vervolgtoken waarmee de query kan worden voortgezet. Zorg ervoor dat u de query volledig afvoert. Meer informatie over het verwerken van meerdere pagina's met resultaten

Metrische gegevens van de query ophalen

Wanneer u een query in Azure Cosmos DB optimaliseert, is de eerste stap altijd het ophalen van de metrische querygegevens voor uw query. Deze metrische gegevens zijn ook beschikbaar via Azure Portal. Nadat u de query in Data Explorer hebt uitgevoerd, zijn de metrische querygegevens zichtbaar naast het tabblad Resultaten :

Metrische querygegevens ophalen

Nadat u de metrische gegevens van de query van de klant hebt opgehaald, vergelijkt u het aantal opgehaalde documenten met het aantal uitvoerdocumenten voor uw query. Gebruik deze vergelijking om de relevante secties in dit artikel die u wilt bekijken te bepalen.

Het opgehaalde aantal documenten is het aantal documenten dat de query-engine nodig heeft om te laden. Het aantal uitvoerdocumenten is het aantal documenten dat nodig was voor de resultaten van de query. Als het opgehaalde aantal documenten aanzienlijk hoger is dan het aantal uitvoerdocument, was er ten minste één deel van de query die geen index kon gebruiken en die een scan moest uitvoeren.

Raadpleeg de volgende secties voor meer informatie over de relevante queryoptimalisaties voor uw scenario.

Ru-kosten van query's zijn te hoog

Het aantal opgehaalde documenten is aanzienlijk hoger dan het aantal uitgevoerde documenten


Het opgehaalde aantal documenten is ongeveer gelijk aan het aantal uitvoerdocument


Ru-kosten van query's zijn acceptabel, maar latentie is nog steeds te hoog

Query's waarbij het opgehaalde aantal documenten groter is dan het aantal uitvoerdocument

Het opgehaalde aantal documenten is het aantal documenten dat de query-engine nodig heeft om te laden. Het aantal uitvoerdocumenten is het aantal documenten dat door de query wordt geretourneerd. Als het opgehaalde aantal documenten aanzienlijk hoger is dan het aantal uitvoerdocument, was er ten minste één deel van de query die geen index kon gebruiken en die een scan moest uitvoeren.

Hier volgt een voorbeeld van een scanquery die niet volledig wordt geleverd door de index:

Query:

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

Metrische querygegevens:

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

Het opgehaalde aantal documenten (60.951) is aanzienlijk hoger dan het aantal uitvoerdocument (7), wat impliceert dat deze query heeft geresulteerd in een documentscan. In dit geval gebruikt de systeemfunctie UPPER() geen index.

Vereiste paden opnemen in het indexeringsbeleid

Uw indexeringsbeleid moet betrekking hebben op alle eigenschappen die zijn opgenomen in WHERE componenten, ORDER BY componenten JOINen de meeste systeemfuncties. De gewenste paden die in het indexbeleid zijn opgegeven, moeten overeenkomen met de eigenschappen in de JSON-documenten.

Notitie

Eigenschappen in het indexeringsbeleid van Azure Cosmos DB zijn hoofdlettergevoelig

Als u de volgende eenvoudige query uitvoert op de voedingsgegevensset , ziet u een veel lagere RU-kosten wanneer de eigenschap in de WHERE component wordt geïndexeerd:

Oorspronkelijk

Query:

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

Indexeringsbeleid:

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

RU-kosten: 409,51 RU's

Geoptimaliseerd

Bijgewerkt indexeringsbeleid:

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

RU-kosten: 2,98 RU's

U kunt op elk gewenst moment eigenschappen toevoegen aan het indexeringsbeleid, zonder dat dit van invloed is op de beschikbaarheid van schrijf- of leesbewerkingen. U kunt de voortgang van de indextransformatie bijhouden.

Begrijpen welke systeemfuncties de index gebruiken

De meeste systeemfuncties maken gebruik van indexen. Hier volgt een lijst met enkele algemene tekenreeksfuncties die gebruikmaken van indexen:

  • StartsWith
  • Contains
  • RegexMatch
  • Left
  • Subtekenreeks- maar alleen als de eerste num_expr 0 is

Hier volgen enkele algemene systeemfuncties die niet gebruikmaken van de index en die elk document moeten laden wanneer ze worden gebruikt in een WHERE component:

Systeemfunctie Ideeën voor optimalisatie
Boven/onder In plaats van de systeemfunctie te gebruiken om gegevens te normaliseren voor vergelijkingen, normaliseert u de behuizing bij invoeging. Een query zoals SELECT * FROM c WHERE UPPER(c.name) = 'BOB' wordt SELECT * FROM c WHERE c.name = 'BOB'.
GetCurrentDateTime/GetCurrentTimestamp/GetCurrentTicks Bereken de huidige tijd voordat de query wordt uitgevoerd en gebruik die tekenreekswaarde in de WHERE component.
Wiskundige functies (niet-aggregaties) Als u een waarde vaak in uw query wilt berekenen, kunt u overwegen om de waarde op te slaan als een eigenschap in uw JSON-document.

Deze systeemfuncties kunnen indexen gebruiken, behalve wanneer ze worden gebruikt in query's met aggregaties:

Systeemfunctie Ideeën voor optimalisatie
Ruimtelijke systeemfuncties Het queryresultaat opslaan in een realtime gerealiseerde weergave

Bij gebruik in de SELECT component hebben inefficiënte systeemfuncties geen invloed op de wijze waarop query's indexen kunnen gebruiken.

De uitvoering van tekenreekssysteemfuncties verbeteren

Voor sommige systeemfuncties die gebruikmaken van indexen, kunt u de uitvoering van query's verbeteren door een ORDER BY component toe te voegen aan de query.

Meer in het bijzonder kan elke systeemfunctie waarvan de RU-kosten toenemen naarmate de kardinaliteit van de eigenschap toeneemt, baat hebben ORDER BY bij de query. Deze query's voeren een indexscan uit, dus als de queryresultaten zijn gesorteerd, kan de query efficiënter worden.

Deze optimalisatie kan de uitvoering voor de volgende systeemfuncties verbeteren:

  • StartsWith (waarbij hoofdlettergevoelig = true)
  • StringEquals (waarbij hoofdlettergevoelig = true)
  • Contains
  • RegexMatch
  • EndsWith

Denk bijvoorbeeld aan de onderstaande query met CONTAINS. CONTAINS gebruikt indexen, maar soms, zelfs nadat u de relevante index hebt toegevoegd, kunt u nog steeds een zeer hoge RU-kosten observeren bij het uitvoeren van de onderstaande query.

Oorspronkelijke query:

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

U kunt de uitvoering van query's verbeteren door het volgende toe te voegen ORDER BY:

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

Dezelfde optimalisatie kan helpen bij query's met extra filters. In dit geval kunt u het beste eigenschappen met gelijkheidsfilters toevoegen aan de ORDER BY component.

Oorspronkelijke query:

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

U kunt de uitvoering van query's verbeteren door een samengestelde index toe te voegen ORDER BY voor (c.name, c.town):

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

Begrijpen welke aggregatiequery's de index gebruiken

In de meeste gevallen gebruiken statistische systeemfuncties in Azure Cosmos DB de index. Afhankelijk van de filters of aanvullende componenten in een statistische query, kan het echter nodig zijn dat de query-engine een groot aantal documenten laadt. Normaal gesproken past de query-engine eerst gelijkheids- en bereikfilters toe. Nadat u deze filters hebt toegepast, kan de query-engine aanvullende filters evalueren en eventueel andere documenten laden om de statistische functie te berekenen.

Op basis van deze twee voorbeeldquery's is de query met zowel een gelijkheids- als CONTAINS systeemfunctiefilter over het algemeen efficiënter dan een query met alleen een CONTAINS systeemfunctiefilter. Dit komt doordat het gelijkheidsfilter eerst wordt toegepast en de index gebruikt voordat documenten moeten worden geladen voor het duurdere CONTAINS filter.

Query's met alleen CONTAINS filter: hogere RU-kosten:

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

Query met zowel gelijkheidsfilter als CONTAINS filter: lagere RU-kosten:

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

Hier volgen aanvullende voorbeelden van statistische query's die niet volledig gebruikmaken van de index:

Query's met systeemfuncties die de index niet gebruiken

Raadpleeg de pagina van de relevante systeemfunctie om te zien of de index wordt gebruikt.

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

Statistische query's met door de gebruiker gedefinieerde functies (UDF's)

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

Query's met GROUP BY

De RU-kosten van query's met GROUP BY toenemen naarmate de kardinaliteit van de eigenschappen in de GROUP BY component toeneemt. In de onderstaande query worden bijvoorbeeld de RU-kosten van de query verhoogd naarmate het aantal unieke beschrijvingen toeneemt.

De RU-kosten van een statistische functie met een GROUP BY component zijn hoger dan de RU-kosten van een statistische functie alleen. In dit voorbeeld moet de query-engine elk document laden dat overeenkomt met het c.foodGroup = "Sausages and Luncheon Meats" filter, zodat de RU-kosten naar verwachting hoog zijn.

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

Als u van plan bent om regelmatig dezelfde statistische query's uit te voeren, is het mogelijk efficiënter om een realtime gerealiseerde weergave te bouwen met de Azure Cosmos DB-wijzigingenfeed dan het uitvoeren van afzonderlijke query's.

Query's optimaliseren met zowel een filter als een ORDER BY-component

Hoewel query's met een filter en een ORDER BY component normaal gesproken een bereikindex gebruiken, zijn ze efficiënter als ze kunnen worden geleverd vanuit een samengestelde index. Naast het wijzigen van het indexeringsbeleid moet u alle eigenschappen in de samengestelde index toevoegen aan de ORDER BY component. Deze wijziging in de query zorgt ervoor dat de samengestelde index wordt gebruikt. U kunt de impact bekijken door een query uit te voeren op de voedingsgegevensset :

Oorspronkelijk

Query:

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

Indexeringsbeleid:

{

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

RU-kosten: 44.28 RU's

Geoptimaliseerd

Bijgewerkte query (bevat beide eigenschappen in de ORDER BY component):

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

Bijgewerkt indexeringsbeleid:

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

RU-kosten: 8.86 RU's

JOIN-expressies optimaliseren met behulp van een subquery

Subquery's met meerdere waarden kunnen expressies optimaliseren JOIN door predicaten na elke select-many-expressie te pushen in plaats van na alle kruiskoppelingen in de WHERE component.

Houd rekening met deze query:

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

RU-kosten: 167.62 RU's

Voor deze query komt de index overeen met elk document met een tag met de naam infant formula, nutritionValue groter dan 0 en amount groter dan 1. De JOIN expressie hier voert het kruisproduct uit van alle items van tags, voedingsstoffen en het leveren van matrices voor elk overeenkomend document voordat een filter wordt toegepast. De WHERE component past vervolgens het filterpredicaat toe op elke <c, t, n, s> tuple.

Als een overeenkomend document bijvoorbeeld 10 items in elk van de drie matrices bevat, wordt het uitgebreid tot 1 x 10 x 10 x 10 (dat wil gezegd 1000) tuples. Het gebruik van subquery's hier kan helpen bij het filteren van samengevoegde matrixitems voordat u met de volgende expressie gaat samenvoegen.

Deze query is gelijk aan de voorgaande, maar gebruikt subquery's:

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)

RU-kosten: 22.17 RU's

Stel dat slechts één item in de tagsmatrix overeenkomt met het filter en dat er vijf items zijn voor zowel de voedingsstoffen als het serveren van matrices. De JOIN expressies worden uitgebreid tot 1 x 1 x 5 x 5 = 25 items, in plaats van 1000 items in de eerste query.

Query's waarbij het aantal opgehaalde documenten groter is dan het aantal uitgevoerde documenten

Als het aantal opgehaalde documenten ongeveer gelijk is aan het aantal uitvoerdocumenten, hoefde de query-engine niet veel onnodige documenten te scannen. Voor veel query's, zoals query's die gebruikmaken van het TOP trefwoord, kan het aantal opgehaalde documenten groter zijn dan het aantal uitvoerdocument met 1. Je hoeft je hier geen zorgen over te maken.

Minimaliseer partitieoverkoepelende query's

Azure Cosmos DB maakt gebruik van partitionering om afzonderlijke containers te schalen naarmate de aanvraageenheid en gegevensopslag toeneemt. Elke fysieke partitie heeft een afzonderlijke en onafhankelijke index. Als je query een gelijkheidsfilter heeft dat overeenkomt met de partitiesleutel van je container, hoef je alleen de index van de relevante partitie te controleren. Deze optimalisatie vermindert het totale aantal RU's dat nodig is voor de query.

Als u een groot aantal ingerichte RU's (meer dan 30.000) of een grote hoeveelheid gegevens hebt opgeslagen (meer dan ongeveer 100 GB), hebt u waarschijnlijk een grote container om een aanzienlijke vermindering van de query-RU-kosten te zien.

Als u bijvoorbeeld een container maakt met de partitiesleutel foodGroup, moeten de volgende query's slechts één fysieke partitie controleren:

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

Query's met een IN filter met de partitiesleutel controleren alleen de relevante fysieke partitie(s) en zullen niet 'uitwaaieren':

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

Query's met bereikfilters op de partitiesleutel of die geen filters op de partitiesleutel hebben, moeten 'uitwaaieren' en de index van elke fysieke partitie controleren op resultaten:

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"

Query's met filters op meerdere eigenschappen optimaliseren

Hoewel query's met filters op meerdere eigenschappen normaal gesproken een bereikindex gebruiken, zijn ze efficiënter als ze kunnen worden geleverd vanuit een samengestelde index. Voor kleine hoeveelheden gegevens heeft deze optimalisatie geen grote impact. Het kan echter handig zijn voor grote hoeveelheden gegevens. Je kunt maximaal één niet-gelijkheidsfilter per samengestelde index optimaliseren. Als je query meerdere niet-gelijkheidsfilters heeft, kies je er een die de samengestelde index gebruikt. De rest blijft bereikindexen gebruiken. Het filter voor niet-gelijkheid moet als laatste in de samengestelde index worden gedefinieerd. Meer informatie over samengestelde indexen.

Hier volgen enkele voorbeelden van query's die kunnen worden geoptimaliseerd met een samengestelde index:

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

Dit is de relevante samengestelde index:

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

Optimalisaties die de querylatentie verminderen

In veel gevallen kunnen de RU-kosten acceptabel zijn wanneer de querylatentie nog steeds te hoog is. De volgende secties geven een overzicht van tips voor het verminderen van querylatentie. Als u dezelfde query meerdere keren uitvoert op dezelfde gegevensset, worden er doorgaans steeds dezelfde RU-kosten in rekening gebracht. Maar querylatentie kan variëren tussen queryuitvoeringen.

Nabijheid verbeteren

Query's die worden uitgevoerd vanuit een andere regio dan het Azure Cosmos DB-account, hebben een hogere latentie dan wanneer ze in dezelfde regio zijn uitgevoerd. Als u bijvoorbeeld code uitvoert op uw computer, moet u verwachten dat de latentie tientallen of honderden milliseconden hoger (of meer) is dan als de query afkomstig is van een virtuele machine in dezelfde Azure-regio als Azure Cosmos DB. Het is eenvoudig om gegevens wereldwijd te distribueren in Azure Cosmos DB om ervoor te zorgen dat u uw gegevens dichter bij uw app kunt brengen.

Ingerichte doorvoer verhogen

In Azure Cosmos DB wordt uw ingerichte doorvoer gemeten in aanvraageenheden (RU's). Stel dat u een query heeft die 5 RU's aan doorvoer verbruikt. Als u bijvoorbeeld 1000 RU's inricht, kunt u die query 200 keer per seconde uitvoeren. Als u de query probeerde uit te voeren wanneer er onvoldoende doorvoer beschikbaar was, zou Azure Cosmos DB een HTTP 429-fout retourneren. Een van de huidige API's voor NoSQL SDK's voert deze query automatisch opnieuw uit nadat u een korte tijd hebt gewacht. Beperkte aanvragen duren langer, zodat het verhogen van de ingerichte doorvoer de querylatentie kan verbeteren. U kunt het totale aantal vertraagde aanvragen bekijken op de blade Metrische gegevens van Azure Portal.

MaxConcurrency verhogen

Parallelle query's werken door gelijktijdig query's uit te voeren op meerdere partities. Maar gegevens van een afzonderlijke gepartitioneerde verzameling worden serieel opgehaald met betrekking tot de query. Dus als u MaxConcurrency instelt op het aantal partities, hebt u de beste kans om de meest presterende query te bereiken, mits alle andere systeemvoorwaarden hetzelfde blijven. Als u het aantal partities niet weet, kunt u MaxConcurrency (of MaxDegreesOfParallelism in oudere SDK-versies) instellen op een hoog getal. Het systeem kiest het minimum (aantal partities, door de gebruiker verstrekte invoer) als de maximale mate van parallelle uitvoering.

MaxBufferedItemCount verhogen

Query's zijn ontworpen om resultaten vooraf op te halen terwijl de huidige batch met resultaten door de client wordt verwerkt. Vooraf ophalen helpt bij het verbeteren van de algehele latentie van een query. Als u MaxBufferedItemCount instelt, wordt het aantal vooraf opgehaalde resultaten beperkt. Als u deze waarde instelt op het verwachte aantal geretourneerde resultaten (of een hoger getal), kan de query het meeste voordeel halen uit vooraf ophalen. Als u deze waarde instelt op -1, bepaalt het systeem automatisch het aantal items dat moet worden gebufferd.

Volgende stappen

Zie de volgende artikelen voor informatie over het meten van RU's per query, het ophalen van uitvoeringsstatistieken om uw query's af te stemmen en meer: