Ottimizzazione delle prestazioni per caricamenti e download con .NET

Quando un'applicazione trasferisce i dati usando la libreria client di Archiviazione di Azure per .NET, esistono diversi fattori che possono influire sulla velocità, sull'utilizzo della memoria e persino sull'esito positivo o negativo della richiesta. Per ottimizzare le prestazioni e l'affidabilità per i trasferimenti di dati, è importante essere proattivi nella configurazione delle opzioni di trasferimento della libreria client in base all'ambiente in cui viene eseguita l'app.

Questo articolo illustra diverse considerazioni per l'ottimizzazione delle opzioni di trasferimento dei dati, che si applicano a qualsiasi API che accetta StorageTransferOptions come parametro. Se ottimizzata correttamente, la libreria client può distribuire in modo efficiente i dati tra più richieste, con conseguente miglioramento della velocità operativa, dell'utilizzo della memoria e della stabilità di rete.

Ottimizzazione delle prestazioni con StorageTransferOptions

L'ottimizzazione corretta dei valori in StorageTransferOptions è fondamentale per ottenere prestazioni affidabili per le operazioni di trasferimento dei dati. I trasferimenti di archiviazione vengono partizionati in diversi sottotrasferimenti in base ai valori di proprietà definiti in un'istanza di questo struct. Le dimensioni massime di trasferimento supportate variano in base all'operazione e alla versione del servizio, quindi assicurarsi di controllare la documentazione per determinare i limiti. Per altre informazioni sui limiti delle dimensioni di trasferimento per l'archiviazione BLOB, vedere Dimensionare le destinazioni per l'archiviazione BLOB.

Le proprietà seguenti di StorageTransferOptions possono essere ottimizzate in base alle esigenze dell'app:

Nota

Mentre lo struct StorageTransferOptions contiene valori nullable, le librerie client useranno le impostazioni predefinite per ogni singolo valore, se non specificato. Queste impostazioni predefinite sono in genere efficienti in un ambiente data center, ma non sono adatte per gli ambienti consumer domestici. Le opzioni StorageTransferOptions non ottimizzate possono comportare operazioni eccessivamente lunghe e persino timeout delle richieste. È consigliabile essere proattivi nel testare i valori in StorageTransferOptions e ottimizzarli in base alle esigenze dell'applicazione e dell'ambiente.

InitialTransferSize

InitialTransferSize rappresenta le dimensioni della prima richiesta di intervallo in byte. Una richiesta di intervallo HTTP è una richiesta parziale, con le dimensioni definite da InitialTransferSize in questo caso. I BLOB inferiori a queste dimensioni vengono trasferiti in una singola richiesta. I BLOB maggiori di questa dimensione continuano a essere trasferiti in blocchi di dimensioni MaximumTransferSize.

È importante notare che il valore specificato per MaximumTransferSize non limita il valore definito per InitialTransferSize. InitialTransferSize definisce una limitazione delle dimensioni separata per una richiesta iniziale in modo da eseguire l'intera operazione contemporaneamente, senza sottotrasferimenti. Spesso si vuole che InitialTransferSize sia almeno grande quanto il valore definito per MaximumTransferSize, se non più grande. A seconda delle dimensioni del trasferimento dei dati, questo approccio può essere più efficiente, poiché il trasferimento viene completato con una singola richiesta ed evita il sovraccarico di più richieste.

Se non si è certi del valore migliore per la situazione, un'opzione sicura consiste nell'impostare InitialTransferSize sullo stesso valore usato per MaximumTransferSize.

Nota

Quando si usa un oggetto BlobClient, il caricamento di un BLOB inferiore a InitialTransferSize verrà eseguito usando Put Blob, anziché Put Block.

MaximumConcurrency

MaximumConcurrency è il numero massimo di ruoli di lavoro che possono essere usati in un trasferimento parallelo. Attualmente, solo le operazioni asincrone possono parallelizzare i trasferimenti. Le operazioni sincrone ignorano questo valore e funzionano in sequenza.

L'efficacia di questo valore è soggetta ai limiti del pool di connessioni in .NET, che può limitare le prestazioni per impostazione predefinita in determinati scenari. Per altre informazioni sui limiti del pool di connessioni in .NET, vedere Limiti del pool di connessioni .NET Framework e il nuovo Azure SDK per .NET.

MaximumTransferSize

MaximumTransferSize è lunghezza massima di un trasferimento in byte. Come accennato in precedenza, questo valore non limita InitialTransferSize, che può essere maggiore di MaximumTransferSize.

Per mantenere efficiente lo spostamento dei dati, le librerie client potrebbero non raggiungere sempre il valore MaximumTransferSize per ogni trasferimento. A seconda dell'operazione, il valore massimo supportato per le dimensioni del trasferimento può variare. Ad esempio, i BLOB in blocchi che chiamano l'operazione Put Block con una versione del servizio 2019-12-12 o successiva hanno una dimensione massima del blocco pari a 4000 MiB. Per altre informazioni sui limiti delle dimensioni di trasferimento per l'archiviazione BLOB, vedere il grafico in Dimensionare le destinazioni per l'archiviazione BLOB.

Esempio di codice

La libreria client include overload per i metodi Upload e UploadAsync, che accettano un'istanza di StorageTransferOptions come parte di un parametro BlobUploadOptions. Esistono anche overload simili per i metodi DownloadTo e DownloadToAsync, usando un parametro BlobDownloadToOptions.

Nell'esempio di codice seguente viene illustrato come definire i valori per un'istanza di StorageTransferOptions e passare queste opzioni di configurazione come parametro a UploadAsync. I valori forniti in questo esempio non sono necessariamente quelli consigliati. Per ottimizzare correttamente questi valori, è necessario considerare le esigenze specifiche dell'app.

// Specify the StorageTransferOptions
BlobUploadOptions options = new BlobUploadOptions
{
    TransferOptions = new StorageTransferOptions
    {
        // Set the maximum number of parallel transfer workers
        MaximumConcurrency = 2,

        // Set the initial transfer length to 8 MiB
        InitialTransferSize = 8 * 1024 * 1024,

        // Set the maximum length of a transfer to 4 MiB
        MaximumTransferSize = 4 * 1024 * 1024
    }
};

// Upload data from a stream
await blobClient.UploadAsync(stream, options);

In questo esempio il numero di ruoli di lavoro di trasferimento parallelo viene impostato su 2, usando la proprietà MaximumConcurrency. Questa configurazione si apre contemporaneamente a due connessioni, consentendo il caricamento in parallelo. La richiesta di intervallo HTTP iniziale tenta di caricare fino a 8 MiB di dati, come definito dalla proprietà InitialTransferSize. Si noti che InitialTransferSize si applica solo per i caricamenti quando si usa un flusso ricercabile. Se le dimensioni del BLOB sono inferiori a 8 MiB, è necessaria solo una singola richiesta per completare l'operazione. Se le dimensioni del BLOB sono maggiori di 8 MiB, tutte le richieste di trasferimento successive hanno una dimensione massima di 4 MiB, impostata con la proprietà MaximumTransferSize.

Considerazioni sulle prestazioni per i caricamenti

Durante un caricamento, le librerie client di archiviazione suddividono un determinato flusso di caricamento in più sottocaricamenti in base ai valori definiti nell'istanza di StorageTransferOptions. Ogni sottocaricamento ha una propria chiamata dedicata all'operazione REST. Per un oggetto BlobClient o BlockBlobClient, questa operazione è Put Block. Per un oggetto DataLakeFileClient, questa operazione è Append Data. La libreria client di archiviazione gestisce queste operazioni REST in parallelo (a seconda delle opzioni di trasferimento) per completare il caricamento.

A seconda che il flusso di caricamento sia ricercabile o non ricercabile, la libreria client gestisce il buffering e InitialTransferSize in modo diverso, come descritto nelle sezioni seguenti. Un flusso ricercabile è un flusso che supporta l'esecuzione di query e la modifica della posizione corrente all'interno di un flusso stesso. Per altre informazioni sui flussi in .NET, vedere le informazioni di riferimento sulla classe Flusso.

Nota

I BLOB in blocchi hanno un numero massimo di 50.000 blocchi. Le dimensioni massime del BLOB in blocchi, quindi, sono pari a 50.000 volte MaximumTransferSize.

Buffering durante i caricamenti

Il livello REST di archiviazione non supporta la ripresa di un'operazione di caricamento REST che è stata interrotta. I singoli trasferimenti vengono completati o persi. Per garantire la resilienza per i caricamenti di flussi non ricercabili, le librerie client di archiviazione eseguono il buffer dei dati per ogni singola chiamata REST prima di avviare il caricamento. Oltre alle limitazioni della velocità di rete, questo comportamento di buffering è un motivo per considerare un valore più piccolo per MaximumTransferSize, anche quando si effettuano caricamenti in sequenza. La riduzione del valore di MaximumTransferSize riduce la quantità massima di dati memorizzati nel buffer per ogni richiesta e per ogni retry di una richiesta non riuscita. Se si verificano timeout frequenti durante i trasferimenti di dati di una determinata dimensione, riducendo il valore di MaximumTransferSize si riduce il tempo di memorizzazione nel buffer e si possono ottenere prestazioni migliori.

Un altro scenario in cui si verifica il buffering è quando si caricano dati con chiamate REST parallele per ottimizzare la velocità effettiva di rete. Le librerie client necessitano di origini leggibili in parallelo e, poiché i flussi sono sequenziali, le librerie client di archiviazione memorizzano nel buffer i dati per ogni singola chiamata REST prima di avviare il caricamento. Questo comportamento di buffering si verifica anche se il flusso fornito è ricercabile.

Per evitare il buffering durante una chiamata di caricamento asincrona, è necessario fornire un flusso ricercabile e impostare MaximumConcurrency su 1. Anche se questa strategia dovrebbe essere ideale per la maggior parte delle situazioni, è comunque possibile che si verifichi buffering se il codice usa altre funzionalità della libreria client che richiedono il buffering.

InitialTransferSize al caricamento

Quando viene fornito un flusso ricercabile per il caricamento, la lunghezza del flusso viene verificata rispetto al valore di InitialTransferSize. Se la lunghezza del flusso è minore di questo valore, l'intero flusso viene caricato come singola chiamata REST, indipendentemente da altri valori StorageTransferOptions. In caso contrario, il caricamento viene eseguito in più parti, come descritto in precedenza. InitialTransferSize non ha alcun effetto su un flusso non ricercabile e viene ignorato.

Considerazioni sulle prestazioni per i download

Durante un download, le librerie client di archiviazione suddividono una determinata richiesta di download in più download secondari in base ai valori definiti nell'istanza di StorageTransferOptions. Ogni sottodownload ha una propria chiamata dedicata all'operazione REST. A seconda delle opzioni di trasferimento, le librerie client gestiscono queste operazioni REST in parallelo per completare il download.

Buffering durante i download

La ricezione simultanea di più risposte HTTP con il contenuto del corpo ha implicazioni per l'utilizzo della memoria. Tuttavia, le librerie client di archiviazione non aggiungono in modo esplicito un passaggio del buffer per il contenuto scaricato. Le risposte in ingresso vengono elaborate in ordine. Le librerie client configurano un buffer da 16 kilobyte per la copia di flussi da un flusso di risposta HTTP a un flusso di destinazione o a un percorso di file fornito dal chiamante.

InitialTransferSize al download

Durante un download, le librerie client di archiviazione effettuano una richiesta di intervallo di download usando InitialTransferSize prima di eseguire qualsiasi altra operazione. Durante questa richiesta di download iniziale, le librerie client conoscono le dimensioni totali della risorsa. Se la richiesta iniziale ha scaricato tutto il contenuto, l'operazione viene completata. In caso contrario, le librerie client continuano a effettuare richieste di intervallo fino a MaximumTransferSize, fino al completamento del download.

Passaggi successivi