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 :
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
Begrijpen welke statistische query's gebruikmaken van de index.
Optimaliseer query's met zowel een filter als een ORDER BY-component.
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 JOIN
en 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: