Optimieren der Abfrageleistung mit Azure Cosmos DB
GILT FÜR: NoSQL
Azure Cosmos DB bietet eine API für NoSQL zum Abfragen von Daten, wobei Schemas oder sekundäre Indizes nicht erforderlich sind. Dieser Artikel enthält folgende Informationen für Entwickler:
- Allgemeine Informationen zur Funktionsweise der SQL-Abfrageausführung in Azure Cosmos DB
- Tipps und bewährte Methoden für die Abfrageleistung
- Beispiele für die Verwendung von Ausführungsstatistiken von SQL-Abfragen zum Debuggen der Abfrageleistung
Informationen über die Ausführung von SQL-Abfragen
In Azure Cosmos DB werden Daten in Containern, die auf eine beliebige Speichergröße oder einen beliebigen Anforderungsdurchsatz erweitert werden können, gespeichert. Azure Cosmos DB skaliert Daten nahtlos im Hintergrund über physische Partitionen hinweg, um das Datenwachstum im bereitgestellten Durchsatz zu verarbeiten. Sie können mit der REST-API oder einem der unterstützten SQL-SDKs SQL-Abfragen an einen beliebigen Container ausgeben.
Ein kurzer Überblick über die Partitionierung: Sie definieren einen Partitionsschlüssel wie „city“, der bestimmt, wie Daten auf physischen Partitionen aufgeteilt werden. Daten, die zu einem einzelnen Partitionsschlüssel gehören (z.B. „city“ == „Seattle“), werden auf einer physischen Partition gespeichert und eine einzelne physische Partition kann Daten von mehreren Partitionsschlüsseln speichern. Wenn eine Partition ihren Speichergrenzwert erreicht, teilt der Dienst die Partition nahtlos in zwei neue Partitionen auf. Daten werden gleichmäßig über die neuen Partitionen verteilt, wobei alle Daten für einen einzelnen Partitionsschlüssel zusammengehalten werden. Da Partitionen vorübergehend sind, verwenden die APIs eine Abstraktion eines Partitionsschlüsselbereichs, der die Bereiche von Partitionsschlüsselhashes bezeichnet.
Wenn Sie eine Abfrage an Azure Cosmos DB ausgeben, führt das SDK folgende logische Schritte aus:
- Es analysiert die SQL-Abfrage, um den Abfrageausführungsplan zu bestimmen.
- Wenn die Abfrage einen Filter für den Partitionsschlüssel wie
SELECT * FROM c WHERE c.city = "Seattle"
enthält, wird sie an eine einzelne Partition weitergeleitet. Wenn die Abfrage keinen Filter für den Partitionsschlüssel hat, wird sie clientseitig in allen Partitionen und Ergebnissen aus jeder Partition zusammengeführt. - Die Abfrage wird basierend auf der Clientkonfiguration seriell oder parallel in jeder Partition ausgeführt. In jeder Partition führt die Abfrage möglicherweise eine oder mehrere Roundtrips durch, je nach Komplexität der Abfrage, der konfigurierten Seitengröße und dem bereitgestelltem Durchsatz der Sammlung. Bei jeder Ausführung wird die Anzahl der Anforderungseinheiten (Request Units, RUs), die bei der Ausführung der Abfrage genutzt werden, und die Abfrageausführungsstatistik zurückgegeben.
- Das SDK führt eine partitionsübergreifende Zusammenfassung der Abfrageergebnisse durch. Wenn die Abfrage beispielsweise eine partitionsübergreifende ORDER BY-Klausel enthält, werden die Ergebnisse der einzelnen Partitionen sortiert zusammengeführt, um Ergebnisse in global sortierter Reihenfolge zurückzugeben. Wenn die Abfrage eine Aggregation wie
COUNT
enthält, wird die Anzahl der einzelnen Partitionen summiert, um die Gesamtzahl zu bestimmen.
Die SDKs stellen verschiedene Optionen für die Abfrageausführung bereit. In .NET sind diese Optionen beispielsweise in der QueryRequestOptions
-Klasse verfügbar. Die folgende Tabelle beschreibt diese Optionen und erläutert, wie sie sich auf die Abfrageausführungszeit auswirken.
Option | Beschreibung |
---|---|
EnableScanInQuery |
Diese Option ist nur anwendbar, wenn die Indizierung für den angeforderten Filterpfad deaktiviert ist. Muss auf „true“ festgelegt sein, wenn Sie die Indizierung deaktiviert haben und Abfragen mit einer vollständigen Überprüfung ausführen möchten. |
MaxItemCount |
Die maximale Anzahl von Elementen, die pro Roundtrip an den Server zurückgegeben werden soll. Sie können es auf -1 festlegen, damit der Server die Anzahl der zurückzugebenden Elemente verwalten kann. |
MaxBufferedItemCount |
Die maximale Anzahl von Elementen, die auf Clientseite während der parallelen Abfrageausführung gepuffert werden können. Ein positiver Eigenschaftswert begrenzt die Anzahl der gepufferten Elemente auf den festgelegten Wert. Sie können ihn auf weniger als 0 festlegen, damit das System automatisch die Anzahl der zu puffernden Elemente festlegen kann. |
MaxConcurrency |
Erhält oder legt die Anzahl der gleichzeitigen Vorgänge fest, die auf Clientseite während der parallelen Abfrageausführung ausgeführt werden. Ein positiver Eigenschaftswert beschränkt die Anzahl der gleichzeitigen Vorgänge auf den festgelegten Wert. Sie können sie auf weniger als 0 festlegen, damit das System automatisch die Anzahl der gleichzeitigen Vorgänge festlegen kann, die ausgeführt werden sollen. |
PopulateIndexMetrics |
Ermöglicht die Sammlung von Indexmetriken, um zu verstehen, wie das Abfragemodul vorhandene Indizes verwendet und wie potenzielle neue Indizes verwendet werden können. Diese Option verursacht mehr Aufwand, sodass sie nur beim Debuggen langsamer Abfragen aktiviert werden sollte. |
ResponseContinuationTokenLimitInKb |
Sie können die maximale Größe des vom Server zurückgegebenen Fortsetzungstokens beschränken. Möglicherweise müssen Sie dies festlegen, wenn Ihr Anwendungshost Beschränkungen für die Antwortheadergröße aufweist, sie aber die Gesamtdauer und die für die Abfrage verbrauchten RUs erhöhen können. |
Hier sehen Sie beispielsweise eine Abfrage für einen Container, der von /city
mithilfe des .NET SDK partitioniert wird:
QueryDefinition query = new QueryDefinition("SELECT * FROM c WHERE c.city = 'Seattle'");
QueryRequestOptions options = new QueryRequestOptions()
{
MaxItemCount = -1,
MaxBufferedItemCount = -1,
MaxConcurrency = -1,
PopulateIndexMetrics = true
};
FeedIterator<dynamic> feedIterator = container.GetItemQueryIterator<dynamic>(query);
FeedResponse<dynamic> feedResponse = await feedIterator.ReadNextAsync();
Jede Abfrageausführung entspricht einer REST-API POST
mit Headern, die für die Abfrageanforderungsoptionen und die SQL-Abfrage im Textkörper festgelegt sind. Weitere Informationen über die REST-API-Anforderungsheader und -Optionen finden Sie unter Abfragen von Ressourcen mithilfe der REST-API.
Bewährte Methoden für die Abfrageleistung
Die folgenden Faktoren wirken sich häufig auf die Leistung von Azure Cosmos DB-Abfragen aus. Die einzelnen Faktoren werden in diesem Artikel im Detail erläutert.
Faktor | Tipp |
---|---|
Bereitgestellter Durchsatz | Messen Sie die RUs pro Abfrage, und stellen Sie sicher, dass Sie über den erforderlichen bereitgestellten Durchsatz für Ihre Abfragen verfügen. |
Partitionierung und Partitionsschlüssel | Geben Sie Abfragen mit dem Partitionsschlüsselwert in der Filterklausel aufgrund geringer Latenz den Vorzug. |
SDK und Abfrageoptionen | Befolgen Sie bewährte Methoden für SDK wie direkte Konnektivität, und optimieren Sie Optionen für die clientseitige Abfrageausführung. |
Netzwerklatenz | Führen Sie Ihre Anwendung nach Möglichkeit in der gleichen Region aus wie Ihr Azure Cosmos DB-Konto. Hierdurch lassen sich Wartezeiten reduzieren. |
Indizierungsrichtlinie | Stellen Sie sicher, dass Sie über die erforderlichen Indizierungspfade bzw. die erforderliche Indizierungsrichtlinie für die Abfrage verfügen. |
Abfrageausführungsmetriken | Analysieren Sie die Abfrageausführungsmetriken, um potenzielle Änderungen an Abfrage- und Datenformen zu identifizieren. |
Bereitgestellter Durchsatz
In Azure Cosmos DB erstellen Sie Container für Daten, die jeweils mit einem reservierten Durchsatz – ausgedrückt in Anforderungseinheiten (Request Units, RU) pro Sekunde – ausgestattet sind. Ein RU ist definiert als das Lesen eines 1 KB großen Dokuments, und jeder Vorgang (einschließlich Abfragen) wird basierend auf der jeweiligen Komplexität als konkrete Anzahl von RUs ausgedrückt. Wenn Sie beispielsweise 1000 RU/s für Ihren Container bereitgestellt und eine Abfrage wie SELECT * FROM c WHERE c.city = 'Seattle'
haben, für die 5 RUs erforderlich sind, können Sie solche Abfragen pro Sekunde gemäß folgender Formel ausführen: (1000 RU/s) : (5 RU/Abfrage) = 200 dieser Abfragen pro Sekunde.
Wenn Sie mehr als 200 Abfragen/Sekunde übermitteln (oder andere Vorgänge, die alle bereitgestellten RUs gesättigt haben), startet der Dienst die Geschwindigkeitsbegrenzung eingehender Anforderungen. Die SDKs handlen Geschwindigkeitsbegrenzungen automatisch, indem sie einen Backoff oder eine Wiederholung ausführen. Daher werden Sie bei diesen Abfragen möglicherweise eine höhere Latenz feststellen. Durch Erhöhen des bereitgestellten Durchsatzes auf den erforderlichen Wert werden Latenz und Durchsatz Ihrer Abfragen verbessert.
Weitere Informationen zu Anforderungseinheiten finden Sie unter Anforderungseinheiten.
Partitionierung und Partitionsschlüssel
Bei Azure Cosmos DB werden die folgenden Szenarien für das Lesen von Daten von den in der Regel am schnellsten/effizientesten bis zur langsamsten/geringsten Effizienz sortiert.
- GET für einen einzelnen Partitionsschlüssel und eine Element-ID wird auch als Punktlese bezeichnet
- Abfrage mit einer Filterklausel für einen einzelnen Partitionsschlüssel
- Abfrage mit einer Gleichheits- oder Bereichsfilterklausel in einer Eigenschaft
- Abfrage ohne Filter
Abfragen, die für alle Partitionen ausgeführt werden müssen, weisen eine höhere Latenz auf und können höhere RUs nutzen. Da jede Partition über eine automatische Indizierung für alle Eigenschaften verfügt, kann die Abfrage in diesem Fall effizient über den Index verarbeitet werden. Mithilfe der Optionen für die Parallelverarbeitung können Sie partitionsübergreifende Abfragen schneller erstellen.
Weitere Informationen zur Partitionierung und zu Partitionsschlüsseln finden Sie unter Partitionieren in Azure Cosmos DB.
SDK und Abfrageoptionen
Unter Tipps zur Abfrageleistung und Leistungstests finden Sie Informationen darüber, wie mithilfe der SDKs die optimale clientseitige Leistung von Azure Cosmos DB erzielt wird.
Netzwerklatenz
Informationen zum Einrichten der globalen Verteilung und Herstellen einer Verbindung mit der nächstgelegenen Region finden Sie unter Globale Azure Cosmos DB-Verteilung. Die Netzwerklatenz hat einen entscheidenden Einfluss auf die Abfrageleistung, wenn mehrere Roundtrips durchzuführen sind oder ein großes Resultset über die Abfrage abgerufen werden muss.
Sie können Abfrageausführungsmetriken verwenden, um die Serverausführungszeit von Abfragen abzurufen, sodass Sie die für die Abfrageausführung aufgewendete Zeit von der Zeit für den Netzwerktransit unterscheiden können.
Indizierungsrichtlinie
Informationen zu Indizierungspfaden, -arten und -modi sowie ihren Auswirkungen auf die Abfrageausführung finden Sie unter Konfigurieren der Indizierungsrichtlinie. Standardmäßig wendet Azure Cosmos DB die automatische Indizierung auf alle Daten an und verwendet Bereichsindizes für Zeichenfolgen und Zahlen, die für Gleichheitsabfragen wirksam sind. Für Szenarios mit Hochleistungseinfügen sollten Sie erwägen, Pfade auszuschließen, um die RU-Kosten für jeden Einfügevorgang zu verringern.
Mithilfe der Indexmetriken können Sie ermitteln, welche Indizes für jede Abfrage verwendet werden, und ob fehlende Indizes vorhanden sind, welche die Abfrageleistung verbessern würden.
Abfrageausführungsmetriken
Detaillierte Metriken werden für jede Abfrageausführung in der Diagnose für die Anforderung zurückgegeben. Diese Metriken beschreiben, wo während der Abfrageausführung Zeit aufgewendet wird, und aktivieren die erweiterte Problembehandlung.
Erfahren Sie mehr über das Abrufen der Abfragemetriken.
Metrik | Einheit | BESCHREIBUNG |
---|---|---|
TotalTime |
Millisekunden | Abfrageausführungszeit gesamt |
DocumentLoadTime |
Millisekunden | Zeitaufwand zum Laden von Dokumenten |
DocumentWriteTime |
Millisekunden | Zeitaufwand für das Schreiben und Serialisieren der Ausgabedokumente |
IndexLookupTime |
Millisekunden | Aufgewendete Zeit in der physischen Indexebene |
QueryPreparationTime |
Millisekunden | Zeitaufwand für die Vorbereitung der Abfrage |
RuntimeExecutionTime |
Millisekunden | Gesamtdauer der Ausführung der Abfragelaufzeit |
VMExecutionTime |
Millisekunden | Zeitaufwand für die Abfragelaufzeit, welche die Abfrage ausführt |
OutputDocumentCount |
count | Anzahl der Ausgabedokumente im Resultset |
OutputDocumentSize |
count | Gesamtgröße der ausgegebenen Dokumente in Byte |
RetrievedDocumentCount |
count | Gesamtanzahl der abgerufenen Dokumente |
RetrievedDocumentSize |
Byte | Gesamtgröße der abgerufenen Dokumente in Bytes |
IndexHitRatio |
Verhältnis [0,1] | Verhältnis zwischen der Anzahl der mit dem Filter abgeglichen Dokumente und der Anzahl der geladenen Dokumente |
Die Client-SDKs können intern möglicherweise mehrere Abfrageanfragen ausführen, um die Abfrage in jeder Partition verarbeiten zu können. Der Client führt mehrere Aufrufe pro Partition durch, wenn die gesamten Ergebnisse die Anforderungsoption der maximalen Elementanzahl überschreiten, die Abfrage den für die Partition bereitgestellten Durchsatz überschreitet, die Abfragenutzlast die maximale Größe pro Seite erreicht oder die Abfrage den vom System zugeordneten Timeoutgrenzwert erreicht. Jede partielle Abfrageausführung gibt Abfragemetriken für diese Seite zurück.
Im Folgenden werden einige Beispielabfragen vorgestellt und erläutert, wie einige von der Abfrageausführung zurückgegebenen Metriken zu interpretieren sind:
Abfrage | Beispielmetrik | BESCHREIBUNG |
---|---|---|
SELECT TOP 100 * FROM c |
"RetrievedDocumentCount": 101 |
Die Anzahl der abgerufenen Dokumente beträgt 100+1 entsprechend der TOP-Klausel. Die Abfragezeit wird hauptsächlich für WriteOutputTime und DocumentLoadTime aufgewendet, da es sich um eine Überprüfung handelt. |
SELECT TOP 500 * FROM c |
"RetrievedDocumentCount": 501 |
„RetrievedDocumentCount“ ist nun höher (500+1 entsprechend der TOP-Klausel). |
SELECT * FROM c WHERE c.N = 55 |
"IndexLookupTime": "00:00:00.0009500" |
Etwa 0,9 ms wird für „IndexLookupTime“ für eine Schlüsselsuche aufgewendet, da eine Indexsuche für /N/? durchgeführt wird. |
SELECT * FROM c WHERE c.N > 55 |
"IndexLookupTime": "00:00:00.0017700" |
Etwas mehr Zeit (1,7 ms) wird für „IndexLookupTime“ über einen Bereichsscan aufgewendet, da eine Indexsuche für /N/? durchgeführt wird. |
SELECT TOP 500 c.N FROM c |
"IndexLookupTime": "00:00:00.0017700" |
Dieselbe Zeit wird wie bei den vorherigen Abfragen für DocumentLoadTime aufgewendet, jedoch weniger Zeit für DocumentWriteTime , da nur eine Eigenschaft projiziert wird. |
SELECT TOP 500 udf.toPercent(c.N) FROM c |
"RuntimeExecutionTime": "00:00:00.2136500" |
Etwa 213 ms wird für RuntimeExecutionTime zur Ausführung der benutzerdefinierten Funktion für jeden Wert von c.N aufgewendet. |
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(c.Name, 'Den') |
"IndexLookupTime": "00:00:00.0006400", "RuntimeExecutionTime": "00:00:00.0074100" |
Etwa 0,6 ms wird für IndexLookupTime bezüglich /Name/? aufgewendet. Der Großteil der Abfrageausführungszeit (~7 ms) wird für RuntimeExecutionTime aufgewendet. |
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(LOWER(c.Name), 'den') |
"IndexLookupTime": "00:00:00", "RetrievedDocumentCount": 2491, "OutputDocumentCount": 500 |
Die Abfrage wird als Überprüfung ausgeführt, da LOWER verwendet wird, und 500 von 2491 abgerufene Dokumente zurückgegeben werden. |
Nächste Schritte
- Weitere Informationen zu den unterstützten SQL-Abfrageoperatoren und Schlüsselwörtern finden Sie unter SQL-Abfrage.
- Weitere Informationen zu Anforderungseinheiten finden Sie unter Anforderungseinheiten.
- Weitere Informationen zur Indizierungsrichtlinie finden Sie unter Indizierungsrichtlinie.