Risolvere i problemi di query relativi all'uso di Azure Cosmos DB
SI APPLICA A: NoSQL
Questo articolo illustra un approccio generale consigliato per la risoluzione dei problemi relativi alle query in Azure Cosmos DB. I passaggi descritti in questo articolo non rappresentano una soluzione completa ai potenziali problemi di query, ma offrono suggerimenti per la soluzione degli errori più comuni relativi alle prestazioni. È consigliabile usare questo articolo come punto di partenza per la soluzione dei problemi relativi a query lente o con costo elevato in Azure Cosmos DB for NoSQL. È inoltre possibile usare i log di diagnostica per identificare le query lente o che usano quantità significative di velocità effettiva. Se si usa l'API di Azure Cosmos DB for MongoDB, è consigliabile usare la guida alla risoluzione dei problemi delle query dell'API di Azure Cosmos DB for MongoDB
In generale, le ottimizzazioni delle query in Azure Cosmos DB sono classificate nel modo seguente:
- Ottimizzazioni che consentono di ridurre l'addebito dell'unità richiesta (UR) della query
- Ottimizzazioni che consentono di ridurre solo la latenza
Se si riduce l'addebito UR di una query, generalmente è possibile ridurre anche la latenza.
Problemi comuni dell'SDK
Prima di leggere questa guida, è opportuno prendere in considerazione i più problemi comuni dell'SDK che non sono correlati al motore di query.
- Seguire questi suggerimenti sulle prestazioni dell'SDK per la query.
- A volte le query possono includere pagine vuote anche quando sono presenti risultati in una pagina successiva. Alcuni possibili motivi sono:
- L'SDK sta eseguendo più chiamate di rete.
- Il recupero dei documenti da parte della query potrebbe richiedere molto tempo.
- Tutte le query hanno un token di continuazione che consente la continuazione della query. Assicurarsi di svuotare completamente la query. Ottenere altre informazioni sulla gestione di più pagine di risultati
Recuperare le metriche della query
Quando si ottimizza una query in Azure Cosmos DB, il primo passaggio consiste sempre nell'recuperare le metriche per la query. Le metriche sono disponibili anche tramite il portale di Azure. Una volta eseguita la query in Esplora dati, le metriche della query sono visibili accanto alla scheda dei risultati:
Dopo aver recuperato le metriche della query, confrontare il conteggio dei documenti recuperati con il conteggio dei documenti di output per la query. Usare questo confronto per identificare le sezioni rilevanti da esaminare in questo articolo.
Il conteggio dei documenti recuperati corrisponde al numero di documenti che il motore di query doveva caricare. Il conteggio dei documenti di output corrisponde al numero di documenti che erano necessari per i risultati della query. Se il conteggio dei documenti recuperati è superiore al conteggio dei documenti di output, è presente almeno una parte della query che non è stato in grado di usare un indice ed è necessario eseguire un'analisi.
Per informazioni sull'ottimizzazione delle query relative a ogni scenario, vedere le sezioni che seguono.
L'addebito UR della query è troppo elevato
Il conteggio dei documenti recuperati è superiore al conteggio dei documenti di output
Includere i percorsi necessari nei criteri di indicizzazione.
Ottimizzare le query che includono un filtro e una clausola ORDER BY.
Il conteggio documenti recuperati è approssimativamente uguale al conteggio documenti di output
L'addebito UR della query è accettabile, ma la latenza è ancora troppo elevata
Query in cui il conteggio dei documenti recuperati supera il conteggio documenti di output
Il conteggio dei documenti recuperati corrisponde al numero di documenti che il motore di query doveva caricare. Il conteggio dei documenti di output è il numero di documenti restituiti dalla query. Se il conteggio dei documenti recuperati è superiore al conteggio dei documenti di output, è presente almeno una parte della query che non è stato in grado di usare un indice ed è necessario eseguire un'analisi.
Di seguito è riportato un esempio di una query di analisi che non è stata interamente gestita dall'indice:
Query:
SELECT VALUE c.description
FROM c
WHERE UPPER(c.description) = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"
Metriche della query:
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
Il conteggio dei documenti recuperati (60.951) è superiore al conteggio dei documenti di output (7), implicando che la query ha generato un'analisi del documento. In questo caso, la funzione di sistema UPPER () non usa un indice.
Includere i percorsi necessari nei criteri di indicizzazione
I criteri di indicizzazione devono interessare tutte le proprietà incluse nelle clausole WHERE
, ORDER BY
, JOIN
e nella maggior parte delle funzioni di sistema. I percorsi desiderati specificati nei criteri di indicizzazione devono corrispondere alle proprietà nei documenti JSON.
Nota
Le proprietà nei criteri di indicizzazione di Azure Cosmos DB fanno distinzione tra maiuscole e minuscole.
Originale
Query:
SELECT *
FROM c
WHERE c.description = "Malabar spinach, cooked"
Criteri di indicizzazione:
{
"indexingMode": "consistent",
"automatic": true,
"includedPaths": [
{
"path": "/*"
}
],
"excludedPaths": [
{
"path": "/description/*"
}
]
}
Addebito UR: 409,51 UR
Con ottimizzazione
Criteri di indicizzazione aggiornati:
{
"indexingMode": "consistent",
"automatic": true,
"includedPaths": [
{
"path": "/*"
}
],
"excludedPaths": []
}
Addebito UR: 2,98 UR
È possibile aggiungere proprietà ai criteri di indicizzazione in qualsiasi momento, senza alcun effetto sulla disponibilità di scrittura o lettura. È possibile tenere traccia dell'avanzamento della trasformazione dell'indice.
Individuare le funzioni di sistema che usano l'indice
La maggior parte delle funzioni di sistema utilizza indici. Di seguito è riportato l'elenco di alcune funzioni di stringa comuni che usano indici:
- StartsWith
- Contiene
- RegexMatch
- Sinistra
- Sottostringa : ma solo se il primo num_expr è 0
Di seguito sono riportate alcune funzioni di sistema comuni che non usano l'indice e che devono caricare ogni documento quando sono utilizzate in una clausola WHERE
:
Funzione di sistema | Suggerimenti per l'ottimizzazione |
---|---|
Upper/Lower | Anziché usare la funzione di sistema per normalizzare i dati per i confronti, normalizzare le maiuscole e minuscole al momento dell'inserimento. Una query come SELECT * FROM c WHERE UPPER(c.name) = 'BOB' diventa SELECT * FROM c WHERE c.name = 'BOB' . |
GetCurrentDateTime/GetCurrentTimestamp/GetCurrentTicks | Calcolare l'ora corrente prima dell'esecuzione della query e usare tale valore stringa nella clausola WHERE . |
Funzioni matematiche (non aggregate) | Se nella query è necessario calcolare spesso un valore specifico, è consigliabile memorizzare il valore come proprietà nel documento JSON. |
Queste funzioni di sistema possono usare indici, tranne quando sono usate nelle query con aggregazioni:
Funzione di sistema | Suggerimenti per l'ottimizzazione |
---|---|
Funzioni di sistema spaziale | Archiviare il risultato della query in una vista materializzata in tempo reale |
Se usate nella clausola SELECT
, le funzioni di sistema inefficienti non influiscono sul modo in cui le query possono usare gli indici.
Migliorare l'esecuzione della funzione di sistema stringa
Per alcune funzioni di sistema che usano indici, è possibile migliorare l'esecuzione delle query aggiungendo una clausola ORDER BY
alla query.
In particolare, qualsiasi funzione di sistema il cui addebito UR aumenta man mano che la cardinalità della proprietà aumenta può trarre vantaggio dalla presenza di ORDER BY
nella query. Queste query eseguono un'analisi dell'indice, in modo che i risultati della query ordinati possano rendere la query più efficiente.
Questa ottimizzazione può migliorare l'esecuzione per le funzioni di sistema seguenti:
- StartsWith (dove senza distinzione tra maiuscole e minuscole = true)
- StringEquals (dove senza distinzione tra maiuscole e minuscole = true)
- Contiene
- RegexMatch
- EndsWith
Si consideri ad esempio la query seguente con CONTAINS
. CONTAINS
userà indici, ma a volte, anche dopo aver aggiunto l'indice pertinente, è comunque possibile osservare un addebito elevato di UR durante l'esecuzione della query seguente.
Query originale:
SELECT *
FROM c
WHERE CONTAINS(c.town, "Sea")
È possibile migliorare l'esecuzione delle query aggiungendo ORDER BY
:
SELECT *
FROM c
WHERE CONTAINS(c.town, "Sea")
ORDER BY c.town
La stessa ottimizzazione può essere utile nelle query con altri filtri. In questo caso, è consigliabile aggiungere anche proprietà con filtri di uguaglianza alla clausola ORDER BY
.
Query originale:
SELECT *
FROM c
WHERE c.name = "Samer" AND CONTAINS(c.town, "Sea")
È possibile migliorare l'esecuzione delle query aggiungendo ORDER BY
e un indice composito per (c.name, c.town):
SELECT *
FROM c
WHERE c.name = "Samer" AND CONTAINS(c.town, "Sea")
ORDER BY c.name, c.town
Individuare le query di aggregazione che usano l'indice
Nella maggior parte dei casi, le funzioni di sistema di aggregazione in Azure Cosmos DB usano l'indice. Tuttavia, a seconda dei filtri o di un'altra clausola in una query di aggregazione, il motore di query potrebbe essere necessario per caricare un numero elevato di documenti. In genere, il motore di query applica prima i filtri di uguaglianza e di intervallo. Dopo aver applicato questi filtri, il motore di query può valutare altri filtri e ricorrere al caricamento di documenti rimanenti per calcolare l'aggregazione, se necessario.
Ad esempio, date queste due query di esempio, la query con un filtro di uguaglianza e CONTAINS
funzione di sistema è in genere più efficiente rispetto a una query con solo un CONTAINS
filtro di funzione di sistema. Questo è dovuto al fatto che il filtro di uguaglianza viene applicato per primo e usa l'indice prima che i documenti debbano essere caricati per il filtro CONTAINS
più costoso.
Query solo con il filtro CONTAINS
, addebito UR superiore:
SELECT COUNT(1)
FROM c
WHERE CONTAINS(c.description, "spinach")
Query con filtro di uguaglianza e filtro CONTAINS
, addebito UR inferiore:
SELECT AVG(c._ts)
FROM c
WHERE c.foodGroup = "Sausages and Luncheon Meats" AND CONTAINS(c.description, "spinach")
Di seguito sono riportati altri esempi di query di aggregazione che non useranno completamente l'indice:
Query con funzioni di sistema che non usano l'indice
Fare riferimento alla pagina della funzione di sistema pertinente per verificare se usa l'indice.
SELECT MAX(c._ts)
FROM c
WHERE CONTAINS(c.description, "spinach")
Query di aggregazione con funzioni definite dall'utente
SELECT AVG(c._ts)
FROM c
WHERE udf.MyUDF("Sausages and Luncheon Meats")
Query con clausola GROUP BY
Addebito ur delle query con GROUP BY
aumento con aumento della cardinalità delle proprietà nella GROUP BY
clausola . Nella query seguente, ad esempio, l'addebito delle UR della query aumenta man mano che aumentano le descrizioni univoca dei numeri.
L'addebito ur di una funzione di aggregazione con una GROUP BY
clausola è superiore all'addebito ur di una funzione di aggregazione da sola. In questo esempio, il motore di query deve caricare tutti i documenti che corrispondono al filtro c.foodGroup = "Sausages and Luncheon Meats"
, pertanto è prevedibile che l'addebito UR sia elevato.
SELECT COUNT(1)
FROM c
WHERE c.foodGroup = "Sausages and Luncheon Meats"
GROUP BY c.description
Se si prevede di eseguire spesso le stesse query di aggregazione, anziché eseguire le singole query, una soluzione più efficace può essere la creazione di una vista materializzata in tempo reale con il feed di modifiche di Azure Cosmos DB.
Ottimizzare le query con un filtro e una clausola ORDER BY
Sebbene le query con un filtro e una ORDER BY
clausola usino in genere un indice di intervallo, sono più efficienti se possono essere gestite da un indice composito. Oltre a modificare i criteri di indicizzazione, è necessario aggiungere alla clausola ORDER BY
tutte le proprietà dell'indice composto. Questa modifica alla query garantisce che usi l'indice composito.
Originale
Query:
SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies"
ORDER BY c._ts ASC
Criteri di indicizzazione:
{
"automatic":true,
"indexingMode":"Consistent",
"includedPaths":[
{
"path":"/*"
}
],
"excludedPaths":[]
}
Addebito UR: 44,28 UR
Ottimizzazione
Query aggiornata (include entrambe le proprietà nella clausola ORDER BY
):
SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies"
ORDER BY c.foodGroup, c._ts ASC
Criteri di indicizzazione aggiornati:
{
"automatic":true,
"indexingMode":"Consistent",
"includedPaths":[
{
"path":"/*"
}
],
"excludedPaths":[],
"compositeIndexes":[
[
{
"path":"/foodGroup",
"order":"ascending"
},
{
"path":"/_ts",
"order":"ascending"
}
]
]
}
Addebito UR: 8,86 UR
Ottimizzare le espressioni JOIN usando una sottoquery
Le sottoquery multivalore consentono di ottimizzare le espressioni JOIN
eseguendo il push dei predicati dopo ogni espressione SelectMany, anziché dopo tutti i cross join nella clausola WHERE
.
Considerare la query seguente:
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
Addebito UR: 167,62 UR
Per questa query, l'indice corrisponde a qualsiasi documento con un tag con il nome infant formula
, nutritionValue
maggiore di 0 e amount
maggiore di 1. L'espressione JOIN
qui esegue il prodotto incrociato di tutti gli elementi di tag, nutrienti e matrici di porzioni per ogni documento corrispondente prima dell'applicazione di qualsiasi filtro. La clausola WHERE
applica quindi il predicato del filtro per ogni tupla di <c, t, n, s>
.
Ad esempio, se un documento corrispondente ha 10 elementi in ognuna delle tre matrici, si espande fino a 1 x 10 x 10 x 10 (ovvero 1.000) tuple. L'uso di sottoquery in questo caso consente di filtrare gli elementi della matrice aggiunti prima dell'unione con l'espressione successiva.
Questa query è equivalente alla precedente, ma usa le sottoquery:
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)
Addebito UR: 22,17 UR
Si supponga che un solo elemento nella matrice di tag corrisponda al filtro e che siano presenti cinque elementi per le matrici nutrizionali e delle porzioni. Le JOIN
espressioni si espandono a 1 x 1 x 5 x 5 = 25 elementi, anziché a 1.000 elementi nella prima query.
Query in cui il conteggio dei documenti recuperati è uguale al conteggio dei documenti di output
Se il conteggio dei documenti recuperati è approssimativamente uguale al conteggio di documenti di output, il motore di query non ha dovuto analizzare molti documenti superflui. Per molte query, ad esempio quelle che usano la parola chiave TOP
, il conteggio dei documenti recuperati potrebbe superare il conteggio dei documenti di output di 1 unità. Non è necessario preoccuparsi di questa opzione.
Ridurre al minimo le query su più partizioni
Azure Cosmos DB usa il partizionamento per la scalabilità dei singoli contenitori quando si riscontra un aumento dell'UR e delle risorse di archiviazione dati necessarie. Ogni partizione fisica dispone di un indice distinto e indipendente. Se la query ha un filtro di uguaglianza che corrisponde alla chiave di partizione del contenitore, è necessario controllare solo l'indice della partizione pertinente. Questa ottimizzazione consente di ridurre il numero totale di UR necessarie per la query.
Se è presente un numero elevato di UR di cui è stato effettuato il provisioning (più di 30.000) o di una quantità elevata di dati archiviati (circa oltre 100 GB), è probabile che si disponga di un contenitore sufficientemente grande da consentire una riduzione significativa degli addebiti UR.
Ad esempio, se si crea un contenitore con la chiave di partizione foodGroup, le query seguenti devono controllare solo una singola partizione fisica:
SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies" and c.description = "Mushroom, oyster, raw"
Le query con un IN
filtro con la chiave di partizione controllano solo una o più partizioni fisiche pertinenti e non "fan-out":
SELECT *
FROM c
WHERE c.foodGroup IN("Soups, Sauces, and Gravies", "Vegetables and Vegetable Products") and c.description = "Mushroom, oyster, raw"
Per le query con filtri di intervallo o senza filtri per la chiave di partizione, dovrà essere effettuato il fan-out e controllato l'indice di ogni partizione fisica per ottenere i risultati:
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"
Ottimizzare le query con filtri su più proprietà
Sebbene le query con filtri su più proprietà usino in genere un indice di intervallo, sono più efficienti se possono essere gestite da un indice composito. Per piccole quantità di dati, l'ottimizzazione non ha un impatto significativo. Tuttavia, nel caso di grandi quantità di dati, l'ottimizzazione può essere molto utile. È possibile ottimizzare, al massimo, un filtro di non uguaglianza per ogni indice composto. Se nella query sono presenti più filtri di non uguaglianza, selezionarne uno per l'utilizzo dell'indice composto. Il resto continua a usare gli indici di intervallo. Il filtro non di uguaglianza deve essere definito come ultimo nell'indice composto. Altre informazioni sugli indici composti.
Di seguito sono riportati alcuni esempi di query che possono essere ottimizzate con un indice composto:
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
Di seguito è riportato l'indice composto pertinente:
{
"automatic":true,
"indexingMode":"Consistent",
"includedPaths":[
{
"path":"/*"
}
],
"excludedPaths":[],
"compositeIndexes":[
[
{
"path":"/foodGroup",
"order":"ascending"
},
{
"path":"/_ts",
"order":"ascending"
}
]
]
}
Ottimizzazioni che consentono di ridurre la latenza della query
In molti casi, l'addebito UR potrebbe essere accettabile quando la latenza della query è ancora troppo elevata. Le sezioni seguenti forniscono una panoramica dei suggerimenti per la riduzione della latenza delle query. Se la stessa query viene eseguita più volte nello stesso set di dati, lo stesso addebito UR viene generalmente applicato ogni volta. La latenza delle query potrebbe tuttavia variare tra le esecuzioni della query.
Migliorare la prossimità
Le query eseguite da un'area diversa rispetto all'account Azure Cosmos DB hanno una latenza superiore rispetto a quella eseguita all'interno della stessa area. Se, ad esempio, se si esegue il codice sul computer desktop, è prevedibile che la latenza sia superiore di decine o centinaia di millisecondi (o più) rispetto a una query eseguita su una macchina virtuale all'interno della stessa area di Azure come Azure Cosmos DB. La distribuzione a livello globale dei dati in Azure Cosmos DB è un'operazione semplice che consente di migliorare la prossimità dei dati all'app.
Aumentare la velocità effettiva sottoposta a provisioning
In Azure Cosmos DB la velocità effettiva con provisioning viene misurata in Unità Richieste (UR). Si supponga di disporre di una query che utilizza 5 UR di velocità effettiva. Se, ad esempio, si esegue il provisioning di 1.000 UR, sarà possibile eseguire la query 200 volte al secondo. Se si tenta di eseguire la query quando la velocità effettiva disponibile non è sufficiente, Azure Cosmos DB restituisce un errore HTTP 429. Uno degli SDK dell'API for NoSQL correntetenterà automaticamente di eseguire la query dopo l'attesa di un breve intervallo di tempo. Le richieste limitate richiedono più tempo, quindi l'aumento della velocità effettiva con provisioning può diminuire la latenza delle query. È possibile visualizzare il numero totale di richieste limitate nel pannello delle metriche del portale di Azure.
Aumentare il valore MaxConcurrency
Le query parallele funzionano eseguendo in parallelo le query su più partizioni. Tuttavia, i dati di una singola raccolta partizionata vengono recuperati in modo seriale per quanto riguarda la query. Pertanto, se si imposta MaxConcurrency sul numero di partizioni si ha la migliore probabilità di ottenere la query più efficiente, se tutte le altre condizioni del sistema rimangono invariate. Se non si conosce il numero di partizioni, è possibile impostare un numero elevato per MaxConcurrency (o per MaxDegreesOfParallelism nelle versioni precedenti dell'SDK). Il sistema sceglie il minimo (numero di partizioni, input fornito dall'utente) come grado massimo di parallelismo.
Aumentare il valore MaxBufferedItemCount
Le query sono progettate per la prelettura dei risultati mentre il client elabora il batch di risultati corrente. La prelettura consente di migliorare la latenza complessiva di una query. L'impostazione di MaxBufferedItemCount consente di limitare il numero di risultati di prelettura. Se si imposta questo valore sul numero previsto di risultati restituiti (o su un numero più elevato), la query può ottenere il massimo vantaggio dalla prelettura. Se si imposta questo valore su -1, il sistema determina automaticamente il numero di elementi da memorizzare nel buffer.
Passaggi successivi
Vedere gli articoli seguenti per informazioni sulla misurazione delle UR per le query, sul recupero delle statistiche di esecuzione per l'ottimizzazione e per altro ancora: