Suggerimenti sulle prestazioni per Azure Cosmos DB e .NET

SI APPLICA A: NoSQL

Azure Cosmos DB è un database distribuito veloce, flessibile, facilmente scalabile e con livelli di latenza e velocità effettiva garantiti. Non è necessario apportare modifiche significative all'architettura o scrivere codice complesso per ridimensionare il database con Azure Cosmos DB. Aumentare o ridurre le prestazioni è semplice come eseguire una singola chiamata API. Per altre informazioni, vedere Effettuare il provisioning della velocità effettiva per un contenitore oppure Effettuare il provisioning della velocità effettiva per un database.

Poiché si accede ad Azure Cosmos DB tramite chiamate di rete, è possibile apportare ottimizzazioni lato client per ottenere prestazioni ottimali quando si usa SQL .NET SDK.

Se si sta cercando di migliorare le prestazioni del database, si prendano in considerazione le opzioni presentate nelle sezioni seguenti.

Raccomandazioni relative all'hosting

Attivare Garbage Collection lato server

In alcuni casi, può essere utile ridurre la frequenza di Garbage Collection. In .NET impostare gcServer su true.

Aumentare il carico di lavoro client

Se si esegue il test a livelli di velocità effettiva elevati o a velocità superiori a 50.000 unità richiesta al secondo (UR/s), l'applicazione client potrebbe diventare un collo di bottiglia del carico di lavoro. Ciò è dovuto al fatto che il computer potrebbe limitare l'uso della CPU o della rete. In questo caso, è possibile continuare a effettuare il push dell'account Azure Cosmos DB aumentando il numero di istanze delle applicazioni client in più server.

Nota

Un uso elevato della CPU può causare un aumento della latenza e le eccezioni di timeout delle richieste.

Operazioni sui metadati

Non verificare l'esistenza di un database e/o di un contenitore chiamando Create...IfNotExistsAsync e/o Read...Async nel percorso critico e/o prima di eseguire un'operazione sull'elemento. La convalida deve essere eseguita solo all'avvio dell'applicazione quando è necessario, se si prevede che verrà eliminata (in caso contrario non è necessario). Queste operazioni sui metadati genereranno una latenza end-to-end aggiuntiva, non hanno contratto di servizio e generano le proprie limitazioni separate che non si ridimensionano come le operazioni sui dati.

Registrazione e traccia

Per alcuni ambienti è abilitato .NET DefaultTraceListener. DefaultTraceListener pone problemi di prestazioni negli ambienti di produzione causando colli di bottiglia elevati di CPU e I/O. Controllare e assicurarsi che DefaultTraceListener sia disabilitato per l'applicazione rimuovendolo da TraceListeners negli ambienti di produzione.

Le versioni più recenti dell'SDK (successive alla 3.23.0) lo rimuovono automaticamente quando lo rilevano, con le versioni precedenti, è possibile rimuoverlo tramite:

if (!Debugger.IsAttached)
{
    Type defaultTrace = Type.GetType("Microsoft.Azure.Cosmos.Core.Trace.DefaultTrace,Microsoft.Azure.Cosmos.Direct");
    TraceSource traceSource = (TraceSource)defaultTrace.GetProperty("TraceSource").GetValue(null);
    traceSource.Listeners.Remove("Default");
    // Add your own trace listeners
}

Rete

Criteri di connessione: usare la modalità di connessione diretta

La modalità di connessione predefinita di .NET V3 SDK è diretta con il protocollo TCP. Quando si crea l'istanza CosmosClient in CosmosClientOptions, viene configurata la modalità di connessione. Per altre informazioni sulle diverse opzioni di connettività, consultare l'articolo modalità di connettività.

string connectionString = "<your-account-connection-string>";
CosmosClient client = new CosmosClient(connectionString,
new CosmosClientOptions
{
    ConnectionMode = ConnectionMode.Gateway // ConnectionMode.Direct is the default
});

Esaurimento della porta temporanea

Se si rileva un volume di connessione elevato o un uso elevato delle porte nelle istanze, verificare prima di tutto che le istanze client siano singleton. In altre parole, le istanze client devono essere univoche per la durata dell'applicazione.

Quando è in esecuzione sul protocollo TCP, il client ottimizza la latenza usando le connessioni di lunga durata. Ciò è in contrasto con il protocollo HTTPS, che termina le connessioni dopo due minuti di inattività.

Negli scenari con accesso sporadico, se si nota un numero di connessioni superiore rispetto alla modalità di accesso Gateway, è possibile:

  • Configurare la proprietà CosmosClientOptions.PortReuseMode su PrivatePortPool (efficace con le versioni del framework 4.6.1 e successive e .NET Core 2.0 e successive). Questa proprietà consente all'SDK di usare un piccolo pool di porte temporanee per vari endpoint di destinazione Azure Cosmos DB.
  • Configurare la proprietà CosmosClientOptions.IdleTcpConnectionTimeout maggiore o uguale a 10 minuti. I valori consigliati sono compresi tra 20 minuti e 24 ore.

Ai fini delle prestazioni, collocare i client nella stessa area di Azure

Quando possibile, posizionare eventuali applicazioni che chiamano Azure Cosmos DB nella stessa area del database Azure Cosmos DB. Ecco un confronto approssimativo: le chiamate ad Azure Cosmos DB eseguite nella stessa area vengono completate da 1 millisecondo (ms) a 2 ms, ma la latenza tra la costa occidentale e quella orientale degli Stati Uniti è superiore a 50 ms. Questa latenza può variare da richiesta a richiesta, in base alla route seguita dalla richiesta durante il passaggio dal client al limite del data center di Azure.

È possibile ottenere la latenza più bassa possibile assicurandosi che l'applicazione chiamante si trovi nella stessa area di Azure in cui si trova l'endpoint di Azure Cosmos DB con provisioning. Per un elenco delle aree disponibili, vedere Aree di Azure.

Collocare i client nella stessa area.

Aumentare il numero di thread/attività

Poiché le chiamate ad Azure Cosmos DB vengono eseguite sulla rete, può essere necessario modificare il grado di concorrenza delle richieste in modo che i tempi di attesa dell'applicazione client tra le richieste siano minimi. Se si usa Task Parallel Library di .NET, ad esempio, creare centinaia di attività di lettura o scrittura in Azure Cosmos DB.

Abilitare la rete accelerata per ridurre la latenza e l'instabilità della CPU

Per ottimizzare le prestazioni, è consigliabile seguire le istruzioni per abilitare la rete accelerata nella macchina virtuale di Azure con Windows (fare clic per istruzioni) o Linux (fare clic per istruzioni).

Senza rete accelerata, le operazioni di IO che transitano tra la macchina virtuale di Azure e le altre risorse di Azure potrebbero essere instradate inutilmente attraverso un host e un commutatore virtuale posizionato tra la macchina virtuale e la relativa scheda di rete. La presenza dell'host e del commutatore virtuale inline nel percorso dei dati non solo aumenta la latenza e l'instabilità nel canale di comunicazione, ma sottrae anche cicli della CPU alla macchina virtuale. Grazie alla rete accelerata, la macchina virtuale si interfaccia direttamente con la scheda di interfaccia di rete senza intermediari. Tutti i dettagli dei criteri di rete gestiti dall'host e dal commutatore virtuale vengono ora gestiti nell'hardware della scheda di interfaccia di rete. L'host e il commutatore virtuale vengono ignorati. Quando si abilita la rete accelerata, è in genere possibile prevedere una latenza più bassa e una velocità effettiva più elevata, oltre a una maggiore latenza coerente e a un minore utilizzo della CPU.

Limitazioni: la rete accelerata deve essere supportata nel sistema operativo della macchina virtuale e può essere abilitata solo quando la macchina virtuale viene arrestata e deallocata. La macchina virtuale non può essere distribuita con Azure Resource Manager. Servizio app non ha alcuna rete accelerata abilitata.

Per altri dettagli, vedere le istruzioni per Windows e Linux.

Uso dell'SDK

Installare l'SDK più recente

Agli SDK di Azure Cosmos DB vengono apportati continui miglioramenti per offrire prestazioni ottimali. Per determinare la versione di SDK più recente e verificare i miglioramenti, vedere le pagine relative agli SDK di Azure Cosmos DB.

Usare le API di flusso

.NET SDK V3 contiene API di flusso che possono ricevere e restituire dati senza serializzazione.

Le applicazioni di livello intermedio che non usano le risposte direttamente dall'SDK, ma le inoltrano ad altri livelli di applicazione possono trarre vantaggio dalle API di flusso. Per esempi di gestione dei flussi, vedere gli esempi di gestione degli elementi.

Usare un client Azure Cosmos DB singleton per la durata dell'applicazione

Ogni istanza di CosmosClient è thread-safe ed esegue la gestione efficiente delle connessioni e la memorizzazione nella cache degli indirizzi quando funziona in modalità diretta. Per consentire una gestione efficiente delle connessioni e prestazioni migliori del client SDK, è consigliabile usare una singola istanza per ogni AppDomain per la durata dell'applicazione per ogni account con cui questa interagisce.

Per le applicazioni multi-tenant che gestiscono più account, vedere le procedure consigliate correlate.

Quando si lavora su Funzioni di Azure, le istanze devono seguire le linee guida esistenti e mantenere una singola istanza.

Evitare le chiamate di blocco

L'SDK di Azure Cosmos DB deve essere progettato per elaborare più richieste contemporaneamente. Le API asincrone consentono a un piccolo pool di thread di gestire migliaia di richieste simultanee senza attendere chiamate di blocco. Anziché attendere il completamento di un'attività sincrona a esecuzione prolungata, il thread può lavorare su un'altra richiesta.

Un problema di prestazioni comune nelle app che usano l'SDK di Azure Cosmos DB sono le chiamate di blocco che potrebbero essere asincrone. Molte chiamate di blocco sincrone causano la scadenza del pool di thread e tempi di risposta degradati.

Non:

  • Bloccare l'esecuzione asincrona chiamando Task.Wait o Task.Result.
  • Usare Task.Run per rendere asincrona un'API sincrona.
  • Acquisire blocchi in percorsi di codice comuni. .NET SDK di Azure Cosmos DB è più efficiente quando progettato per eseguire codici in parallelo.
  • Chiamare Task.Run e attendere immediatamente. ASP.NET Core esegue già il codice dell'app nei normali thread del pool, quindi la chiamata a Task.Run comporta solo una pianificazione aggiuntiva del pool di thread non necessaria. Anche se il codice pianificato blocca un thread, Task.Run non lo impedisce.
  • Non usare ToList() su Container.GetItemLinqQueryable<T>() che usa chiamate di blocco per svuotare in modo sincrono la query. Usare ToFeedIterator() per svuotare la query in modo asincrono.

Fare:

  • Chiamare le API .NET di Azure Cosmos DB in modo asincrono.
  • L'intero stack di chiamate è asincrono per trarre vantaggio dai criteri async/await.

Un profiler, ad esempio PerfView, può essere usato per trovare i thread aggiunti di frequente al pool di thread. L'evento Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start indica un thread aggiunto al pool di thread.

Disabilitare la risposta al contenuto nelle operazioni di scrittura

Per i carichi di lavoro con payload di creazione intensi, impostare l'opzione di richiesta EnableContentResponseOnWrite su false. Il servizio non restituirà più la risorsa creata o aggiornata all'SDK. In genere, poiché l'applicazione ha l'oggetto che viene creato, non è necessario che il servizio lo restituisca. I valori dell'intestazione sono ancora accessibili, ad esempio un addebito della richiesta. La disabilitazione della risposta al contenuto può contribuire a migliorare le prestazioni, perché l'SDK non deve più allocare memoria o serializzare il corpo della risposta. Riduce inoltre l'uso della larghezza di banda di rete per migliorare le prestazioni.

ItemRequestOptions requestOptions = new ItemRequestOptions() { EnableContentResponseOnWrite = false };
ItemResponse<Book> itemResponse = await this.container.CreateItemAsync<Book>(book, new PartitionKey(book.pk), requestOptions);
// Resource will be null
itemResponse.Resource

Abilitare la funzionalità Bulk per ottimizzare la velocità effettiva anziché la latenza

Abilitare la funzionalità Bulk per gli scenari in cui il carico di lavoro richiede una grande quantità di velocità effettiva e la latenza non è altrettanto importante. Per ulteriori informazioni su come abilitare la funzionalità Bulk e sugli scenari da usare, consultare Introduzione al supporto Bulk.

Aumentare il valore di System.Net MaxConnections per host se si usa la modalità Gateway

Le richieste di Azure Cosmos DB vengono effettuate tramite HTTPS/REST quando si usa la modalità Gateway. Sono soggette al limite di connessione predefinito per nome host o indirizzo IP. Può essere necessario impostare MaxConnections su un valore più alto (da 100 1.000) in modo che la libreria client possa usare più connessioni simultanee ad Azure Cosmos DB. In .NET SDK 1.8.0 e versioni successive il valore predefinito per ServicePointManager.DefaultConnectionLimit è 50. Per modificare il valore, è possibile impostare Documents.Client.ConnectionPolicy.MaxConnectionLimit su un valore più alto.

Aumentare il numero di thread/attività

Vedere Aumentare il numero di thread/attività nella sezione Rete di questo articolo.

Operazioni di query

Per le operazioni di query, vedere i suggerimenti sulle prestazioni per le query.

Criterio di indicizzazione

Escludere i percorsi non usati dall'indicizzazione per scritture più veloci

I criteri di indicizzazione di Azure Cosmos DB consentono anche di specificare i percorsi dei documenti da includere o escludere dall'indicizzazione usando i percorsi di indicizzazione (IndexingPolicy.IncludedPaths e IndexingPolicy.ExcludedPaths).

L'indicizzazione solo dei percorsi necessari può migliorare le prestazioni di scrittura, ridurre gli addebiti delle UR per le operazioni di scrittura e ridurre l'archiviazione degli indici per gli scenari in cui i modelli di query sono noti in anticipo. Ciò è dovuto al fatto che i costi di indicizzazione sono correlati direttamente al numero di percorsi univoci indicizzati. Il codice seguente, ad esempio, mostra come escludere dall'indicizzazione un'intera sezione dei documenti (sottoalbero) usando il carattere jolly "*":

var containerProperties = new ContainerProperties(id: "excludedPathCollection", partitionKeyPath: "/pk" );
containerProperties.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" });
containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/nonIndexedContent/*");
Container container = await this.cosmosDatabase.CreateContainerAsync(containerProperties);

Per altre informazioni, vedere l'articolo relativo ai criteri di indicizzazione di Azure Cosmos DB.

Velocità effettiva

Misurare e ottimizzare per ridurre l'uso di UR/s

Azure Cosmos DB offre un set completo di operazioni di database. Queste operazioni includono query relazionali e gerarchiche con file UDF (Universal Disk Format), stored procedure e trigger, operative nei documenti in una raccolta di database.

I costi associati a ognuna di queste operazioni varia in base a CPU, I/O e memoria che sono necessari per il completamento dell'operazione. Invece di occuparsi della pianificazione e della gestione delle risorse hardware, sarà possibile usare un' unità richiesta come misura singola per le risorse che sono necessarie per eseguire diverse operazioni di database e rispondere a una richiesta dell'applicazione.

Viene eseguito il provisioning della velocità effettiva in base al numero di unità richiesta impostato per ogni contenitore. L'uso di unità richiesta viene valutato con una tariffa di unità al secondo. Le applicazioni che superano la frequenza di unità richiesta con provisioning previsto per il contenitore sono limitate fino al ritorno della frequenza sotto il valore riservato per il contenitore. Se l'applicazione necessita di un livello superiore di velocità effettiva, sarà possibile aumentare la velocità effettiva eseguendo il provisioning di unità richiesta aggiuntive.

La complessità di una query influisce sul numero di unità richiesta usate per un'operazione. Il numero di predicati, la natura dei predicati, il numero di file UDF e le dimensioni del set di dati di origine sono tutti fattori che incidono sul costo delle operazioni di query.

Per misurare l'overhead di qualunque operazione (create, update o delete), esaminare l'intestazione x-ms-request-charge (o la proprietà RequestCharge equivalente in ResourceResponse<T> oppure FeedResponse<T> in .NET SDK) per determinare il numero di unità richiesta usate da queste operazioni:

// Measure the performance (Request Units) of writes
ItemResponse<Book> response = await container.CreateItemAsync<Book>(myBook, new PartitionKey(myBook.PkValue));
Console.WriteLine("Insert of item consumed {0} request units", response.RequestCharge);
// Measure the performance (Request Units) of queries
FeedIterator<Book> queryable = container.GetItemQueryIterator<ToDoActivity>(queryString);
while (queryable.HasMoreResults)
    {
        FeedResponse<Book> queryResponse = await queryable.ExecuteNextAsync<Book>();
        Console.WriteLine("Query batch consumed {0} request units", queryResponse.RequestCharge);
    }

L'addebito richiesta restituito in questa intestazione è una frazione della velocità effettiva con provisioning (ovvero 2.000 UR/s). Se, ad esempio, la query precedente restituisce 1.000 documenti da 1 KB, il costo dell'operazione è 1.000. Entro un secondo, il server rispetterà quindi solo due richieste di questo tipo prima di limitare la velocità delle richieste successive. Per altre informazioni, vedere Unità richiesta e il Calcolatore di unità richiesta.

Gestire la limitazione della frequenza o una frequenza di richieste troppo elevata

Quando un client prova a superare la velocità effettiva riservata per un account, non si verifica alcun calo delle prestazioni del server e l'uso della capacità della velocità effettiva non supera il livello riservato. Il server termina preventivamente la richiesta con RequestRateTooLarge (codice di stato HTTP 429). Restituisce un'intestazione x-ms-retry-after-ms che indica la quantità di tempo, espressa in millisecondi, che l'utente deve attendere prima di ripetere la richiesta.

    HTTP Status 429,
    Status Line: RequestRateTooLarge
    x-ms-retry-after-ms :100

Tutti gli SDK intercettano implicitamente questa risposta, rispettano l'intestazione retry-after specificata dal server e ripetono la richiesta. A meno che all'account non accedano contemporaneamente più client, il tentativo successivo riuscirà.

Se si dispone di più client che operano complessivamente a una velocità costantemente superiore a quella della richiesta, il numero di tentativi predefinito attualmente impostato internamente dal client su 9 potrebbe non essere sufficiente. In tal caso, il client genera un'eccezione CosmosException con codice di stato 429 per l'applicazione.

È possibile modificare il numero di tentativi predefinito impostando RetryOptions nell'istanza CosmosClientOptions. Per impostazione predefinita, l'eccezione CosmosException con codice di stato 429 viene restituita dopo un tempo di attesa cumulativo di 30 secondi, se la richiesta continua a funzionare al di sopra della frequenza delle richieste. Questo errore si verifica anche quando il numero di ripetizioni dei tentativi corrente è inferiore al numero massimo di tentativi, indipendentemente dal fatto che si tratti del valore predefinito 9 o di un valore definito dall'utente.

Il comportamento automatizzato per la ripetizione dei tentativi consente di migliorare la resilienza e l'usabilità per la maggior parte delle applicazioni. Ma potrebbe non essere il comportamento migliore quando si eseguono benchmark delle prestazioni, soprattutto quando si misura la latenza. La latenza osservata dal client presenterà dei picchi se l'esperimento raggiunge il limite del server e fa in modo che l'SDK client ripeta automaticamente i tentativi. Per evitare i picchi di latenza durante gli esperimenti relativi alle prestazioni, misurare l'addebito che viene restituito da ogni operazione e assicurarsi che le richieste operino al di sotto della frequenza delle richieste riservata.

Per altre informazioni, vedere Unità richiesta.

Per ottenere una velocità effettiva maggiore, progettare documenti di dimensioni minori

L'addebito per le richieste (ovvero il costo di elaborazione delle richieste) per un'operazione specifica è correlato direttamente alle dimensioni del documento. Le operazioni sui documenti di grandi dimensioni sono più costose rispetto alle operazioni sui documenti di piccole dimensioni.

Passaggi successivi

Per un'applicazione di esempio che viene usata per valutare Azure Cosmos DB per scenari a prestazioni elevate su un numero ridotto di computer client, vedere Test delle prestazioni e della scalabilità con Azure Cosmos DB.

Per altre informazioni sulla progettazione dell'applicazione per scalabilità e prestazioni elevate, vedere l'articolo relativo a partizionamento e ridimensionamento in Azure Cosmos DB.