Ottimizzazione delle prestazioni delle query con Azure Cosmos DB

SI APPLICA A: NoSQL

Azure Cosmos DB fornisce un'API per NoSQL per le query sui dati, senza che siano necessari schemi o indici secondari. In questo articolo vengono fornite le seguenti informazioni per gli sviluppatori:

  • Informazioni generali sull'esecuzione delle query SQL di Azure Cosmos DB
  • Suggerimenti e procedure consigliate per le prestazioni delle query
  • Esempi di come usare le metriche di esecuzione delle query SQL per eseguire il debug delle prestazioni delle query

Informazioni sull'esecuzione di query SQL

In Azure Cosmos DB i dati vengono archiviati in contenitori, che possono raggiungere dimensioni di archiviazione o velocità effettive delle richieste illimitate. Azure Cosmos DB esegue automaticamente il ridimensionamento dei dati tra le partizioni fisiche per gestire la crescita dei dati o l'aumento della velocità effettiva con provisioning. È possibile eseguire query SQL su qualsiasi contenitore usando l'API REST o uno dei SDK di SQL supportati.

Una breve panoramica del partizionamento: si definisce una chiave di partizione come "city", che determina il modo in cui vengono suddivisi i dati tra le partizioni fisiche. I dati appartenenti a una singola chiave di partizione (ad esempio, "city" == "Seattle") vengono archiviati all'interno di una partizione fisica e una singola partizione fisica può archiviare i dati da più chiavi di partizione. Quando una partizione raggiunge il limite di archiviazione, il servizio la suddivide facilmente in due nuove partizioni. I dati vengono distribuiti uniformemente tra le nuove partizioni, mantenendo tutti i dati per una singola chiave di partizione. Poiché le partizioni sono temporanee, le API usano un'astrazione "intervallo di chiavi di partizione", che indica gli intervalli degli hash delle chiavi di partizione.

Quando si esegue una query in Azure Cosmos DB, l'SDK esegue i passaggi logici seguenti:

  • La query SQL viene analizzata per determinare il piano di esecuzione della query.
  • Se la query include un filtro con la chiave di partizione, ad esempio SELECT * FROM c WHERE c.city = "Seattle", viene indirizzata a una singola partizione. Se la query non dispone di un filtro sulla chiave di partizione, viene eseguita in tutte le partizioni e i risultati di ogni partizione vengono uniti sul lato client.
  • La query viene eseguita in ogni partizione in serie o in parallelo, in base alla configurazione client. All'interno di ogni partizione, la query può richiedere uno o più round trip, a seconda della complessità della query, delle dimensioni di pagina configurate e della velocità effettiva con provisioning della raccolta. Ogni esecuzione restituisce il numero di unità richiesta usate dall'esecuzione della query e le statistiche sull'esecuzione della query.
  • L'SDK riepiloga i risultati della query tra le partizioni. Ad esempio, se la query implica una clausola ORDER BY tra le partizioni, i risultati dalle singole partizioni vengono uniti e ordinati per restituire i risultati in base all'ordinamento globale. Se la query è un'aggregazione come COUNT, i conteggi relativi alle singole partizioni vengono sommati per produrre il conteggio globale.

L'SDK offre varie opzioni per l'esecuzione di query. Ad esempio, in .NET queste opzioni sono disponibili nella classe QueryRequestOptions. La tabella seguente illustra queste opzioni e il relativo impatto sul tempo di esecuzione delle query.

Opzione Descrizione
EnableScanInQuery Applicabile solo se l'indicizzazione per il percorso di filtro richiesto è disabilitata. Deve essere impostato su true se si è scelto di rifiutare esplicitamente l'indicizzazione e si desidera eseguire query usando un'analisi completa.
MaxItemCount Numero massimo di elementi da restituire per ogni round trip al server. È possibile impostarlo su -1 per consentire al server di gestire il numero di elementi da restituire.
MaxBufferedItemCount Numero massimo di elementi che possono essere memorizzati nel buffer sul lato client durante l'esecuzione di query parallele. Un valore positivo della proprietà limita il numero di elementi memorizzati nel buffer al valore impostato. È possibile impostarlo su meno di 0 per consentire al sistema di decidere automaticamente il numero di elementi da memorizzare nel buffer.
MaxConcurrency Ottiene o imposta il numero di operazioni simultanee eseguite sul lato client durante l'esecuzione di query parallele. Un valore di proprietà positivo limita il numero di operazioni simultanee al valore impostato. È possibile impostarlo su meno di 0 per consentire al sistema di decidere automaticamente il numero di operazioni simultanee da eseguire.
PopulateIndexMetrics Consente alla raccolta di metriche degli indici di comprendere in che modo il motore di query ha usato gli indici esistenti e come potrebbe usare potenziali nuovi indici. Questa opzione comporta un sovraccarico, pertanto deve essere abilitata solo quando si esegue il debug di query lente.
ResponseContinuationTokenLimitInKb È possibile limitare la dimensione massima del token di continuazione restituito dal server. Potrebbe essere necessario usare questa impostazione se l'host dell'applicazione ha limiti sulle dimensioni dell'intestazione della risposta, ma può aumentare la durata complessiva e le UR utilizzate per la query.

Ecco ad esempio una query su un contenitore partizionato da /city usando .NET SDK:

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

Ogni esecuzione di query corrisponde a un'API REST POST con intestazioni impostate per le opzioni di richiesta di query e la query SQL nel corpo. Per informazioni dettagliate sulle intestazioni e le opzioni delle richieste API REST, vedere Querying resources using the DocumentDB REST API (Esecuzione di query su risorse con l'API REST).

Procedure consigliate per le prestazioni delle query

I fattori seguenti hanno in genere il maggiore effetto sulle prestazioni delle query di Azure Cosmos DB. In questo articolo vengono descritti in dettaglio ognuno di questi fattori.

Fattore Suggerimento
Provisioning velocità effettiva Misurare le unità di richieste per ogni query e assicurarsi di disporre della velocità effettiva con provisioning richiesta per le query.
Partizionamento e chiavi di partizione Favorire le query con il valore della chiave di partizione nella clausola del filtro per una latenza bassa.
Opzioni per SDK e query Seguire le procedure consigliate dell'SDK come la connettività diretta e ottimizzare le opzioni di esecuzione delle query sul lato client.
Latenza di rete Eseguire l'applicazione nella stessa area dell'account Azure Cosmos DB, laddove possibile, per ridurre la latenza.
Criterio di indicizzazione Assicurarsi di disporre dei percorsi/criteri di indicizzazione necessari per la query.
Metriche di esecuzione delle query Analizzare le metriche di esecuzione delle query per identificare le potenziali riscritture di forme di dati e query.

Provisioning velocità effettiva

In Azure Cosmos DB è possibile creare contenitori di dati, ognuno con una velocità effettiva riservata espressa in unità richiesta (UR) al secondo. Una lettura di un documento di 1 KB è 1 UR e ogni operazione (incluse le query) è normalizzata secondo un numero fisso di UR in base alla relativa complessità. Ad esempio, se è stato effettuato il provisioning di 1000 UR/sec per il contenitore e si dispone di una query come SELECT * FROM c WHERE c.city = 'Seattle' che utilizza 5 UR, è possibile eseguire (1000 UR/sec) / (5 UR/query) = 200 di queste query al secondo.

Se si inviano più di 200 query al secondo (o altre operazioni che saturano tutte le UR di cui è stato effettuato il provisioning), il servizio inizia a limitare la frequenza delle richieste in ingresso. Gli SDK gestiscono automaticamente la limitazione della frequenza eseguendo un backoff o un retry, pertanto è possibile notare una latenza più elevata per queste query. L'aumento della velocità effettiva con provisioning al valore richiesto migliora la velocità effettiva e la latenza delle query.

Per altre informazioni sulle unità richiesta, vedere Unità richiesta.

Partizionamento e chiavi di partizione

Con Azure Cosmos DB, gli scenari seguenti per la lettura dei dati vengono ordinati da ciò che è in genere più veloce/più efficiente al più lento/meno efficiente.

  • GET in una singola chiave di partizione e ID elemento, noto anche come lettura punto
  • Query con una clausola di filtro su una singola chiave di partizione
  • Query con una clausola di filtro di uguaglianza o intervallo su qualsiasi proprietà
  • Query senza filtri

Le query che devono essere eseguite in tutte le partizioni hanno una latenza più elevata e possono usare UR più elevate. Poiché ogni partizione dispone dell'indicizzazione automatica per tutte le proprietà, in questo caso la query può essere fornita in modo efficiente dall'indice. È possibile eseguire più velocemente le query che interessano diverse partizioni usando le opzioni di parallelismo.

Per altre informazioni sul partizionamento e sulle chiavi di partizione, vedere Partizionamento in Azure Cosmos DB.

Opzioni per SDK e query

Per informazioni su come ottenere le migliori prestazioni sul lato client da Azure Cosmos DB usando gli SDK, vedere Suggerimenti sulle prestazioni delle query e Test delle prestazioni.

Latenza di rete

Per configurare la distribuzione globale e connettersi all'area più vicina, vedere Distribuzione globale di Azure Cosmos DB. La latenza di rete ha un impatto significativo sulle prestazioni delle query quando è necessario eseguire più round trip o recuperare un set di risultati di grandi dimensioni dalla query.

È possibile usare metriche di esecuzione delle query per recuperare il tempo di esecuzione del server delle query, consentendo di distinguere il tempo impiegato per l'esecuzione delle query dal tempo dedicato al transito di rete.

Criterio di indicizzazione

Per informazioni su percorsi, tipi e modalità di indicizzazione e sul relativo impatto sull'esecuzione delle query, vedere Configurazione dei criteri di indicizzazione. Per impostazione predefinita, Azure Cosmos DB applica l'indicizzazione automatica a tutti i dati e usa gli indici di intervallo per stringhe e numeri, il che è efficace per le query di uguaglianza. Per gli scenari di inserimento ad alte prestazioni, valutare la possibilità di escludere i percorsi per ridurre il costo delle UR per ogni operazione di inserimento.

È possibile usare le metriche degli indici per identificare gli indici usati per ogni query e se sono presenti indici mancanti che migliorano le prestazioni delle query.

Metriche di esecuzione delle query

Le metriche dettagliate vengono restituite per ogni esecuzione di query nella Diagnostica per la richiesta. Queste metriche descrivono dove viene impiegato tempo durante l'esecuzione della query e abilitano una risoluzione dei problemi avanzata.

Altre informazioni su come ottenere le metriche di query.

Metric Unità Descrizione
TotalTime milliseconds Durata totale dell'esecuzione della query
DocumentLoadTime milliseconds Tempo impiegato per il caricamento di documenti
DocumentWriteTime milliseconds Tempo impiegato per la scrittura e la serializzazione dei documenti di output
IndexLookupTime milliseconds Tempo impiegato nel livello di indice fisico
QueryPreparationTime milliseconds Tempo impiegato per la preparazione della query
RuntimeExecutionTime milliseconds Tempo di esecuzione totale del runtime di query
VMExecutionTime milliseconds Tempo impiegato nel runtime di query durante l'esecuzione della query
OutputDocumentCount numero Numero di documenti di output nel set di risultati
OutputDocumentSize numero Dimensioni totali dei documenti restituiti in byte
RetrievedDocumentCount numero Numero totale di documenti recuperati
RetrievedDocumentSize bytes Dimensione totale dei documenti recuperati in byte
IndexHitRatio rapporto [0,1] Rapporto del numero di documenti corrispondenti al filtro rispetto al numero di documenti caricati

Gli SDK client possono effettuare internamente più richieste di query per gestire la query all'interno di ogni partizione. Il client effettua più chiamate per ogni partizione se i risultati totali superano l'opzione di richiesta di conteggio massimo elementi, se la query supera la velocità effettiva con provisioning per la partizione, se il payload della query raggiunge la dimensione massima per ogni pagina oppure se la query raggiunge il limite di timeout allocato dal sistema. Ogni esecuzione parziale della query restituisce metriche di query per la pagina.

Di seguito sono riportate alcune query di esempio, con informazioni su come interpretare alcune delle metriche restituite dall'esecuzione delle query:

Query Metrica di esempio Descrizione
SELECT TOP 100 * FROM c "RetrievedDocumentCount": 101 Il numero di documenti recuperati è 100+1 per la corrispondenza alla clausola TOP. Il tempo della query viene impiegato per lo più in WriteOutputTime e DocumentLoadTime poiché si tratta di un'analisi.
SELECT TOP 500 * FROM c "RetrievedDocumentCount": 501 RetrievedDocumentCount è ora superiore (500+1 per la corrispondenza alla clausola TOP).
SELECT * FROM c WHERE c.N = 55 "IndexLookupTime": "00:00:00.0009500" Vengono impiegati circa 0,9 ms in IndexLookupTime per la ricerca della chiave, perché viene eseguita una ricerca nell'indice /N/?.
SELECT * FROM c WHERE c.N > 55 "IndexLookupTime": "00:00:00.0017700" Un tempo leggermente superiore (1,7 ms) viene impiegato per IndexLookupTime nel caso di un'analisi dell'intervallo, perché viene eseguita una ricerca nell'indice /N/?.
SELECT TOP 500 c.N FROM c "IndexLookupTime": "00:00:00.0017700" Il tempo trascorso in DocumentLoadTime è lo stesso delle query precedenti, ma con un valore DocumentWriteTime inferiore perché viene eseguita la proiezione di una sola proprietà.
SELECT TOP 500 udf.toPercent(c.N) FROM c "RuntimeExecutionTime": "00:00:00.2136500" Vengono impiegati circa 213 ms in RuntimeExecutionTime per l'esecuzione della funzione definita dall'utente su ogni valore di c.N.
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(c.Name, 'Den') "IndexLookupTime": "00:00:00.0006400", "RuntimeExecutionTime": "00:00:00.0074100" Vengono impiegati circa 0,6 ms in IndexLookupTime su /Name/?. La maggior parte del tempo di esecuzione della query (circa 7 ms) è in RuntimeExecutionTime.
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(LOWER(c.Name), 'den') "IndexLookupTime": "00:00:00", "RetrievedDocumentCount": 2491, "OutputDocumentCount": 500 La query viene eseguita come un'analisi perché usa LOWER e vengono restituiti 500 su 2491 documenti recuperati.

Passaggi successivi