Optimización del rendimiento para cargas y descargas con .NET

Cuando una aplicación transfiere datos mediante la biblioteca cliente de Azure Storage para .NET, hay varios factores que pueden afectar a la velocidad, al uso de memoria e incluso a la ejecución correcta o incorrecta de la solicitud. Para maximizar el rendimiento y la confiabilidad de las transferencias de datos, es importante ser proactivo en la configuración de las opciones de transferencia de la biblioteca cliente en función del entorno en el que se ejecuta la aplicación.

En este artículo, se explican varias consideraciones para ajustar las opciones de transferencia de datos y las instrucciones se aplican a cualquier API que acepte StorageTransferOptions como parámetro. Cuando se ajusta correctamente, la biblioteca cliente puede distribuir eficazmente los datos entre varias solicitudes, lo que puede dar lugar a mejorar la velocidad de operación, el uso de memoria y la estabilidad de la red.

Optimización del rendimiento con StorageTransferOptions

Es fundamental ajustar correctamente los valores de StorageTransferOptions para obtener un rendimiento confiable en las operaciones de transferencia de datos. Las transferencias del almacenamiento se particionan en varias transferencias secundarias en función de los valores de propiedad definidos en una instancia de esta estructura. El tamaño máximo de transferencia admitido varía según la operación y la versión del servicio, por lo que asegúrese de consultar la documentación para determinar los límites. Para más información sobre los límites de tamaño de transferencia para Blob Storage, consulte Objetivos de escalabilidad de Blob Storage.

Se pueden ajustar las siguientes propiedades de StorageTransferOptions en función de las necesidades de la aplicación:

Nota

Aunque la estructura StorageTransferOptions contiene valores que aceptan valores NULL, las bibliotecas cliente usarán los valores predeterminados de cada valor individual si no se proporcionan. Estos valores predeterminados suelen tener un buen rendimiento en un entorno de centro de datos, pero es probable que no sean adecuados para entornos de consumidores domésticos. Un ajuste deficiente de StorageTransferOptions puede dar lugar a operaciones excesivamente largas e incluso tiempos de espera de solicitud. Es mejor ser proactivo al probar los valores de StorageTransferOptions y ajustarlos en función de las necesidades de la aplicación y el entorno.

InitialTransferSize

InitialTransferSize es el tamaño de la primera solicitud de intervalo en bytes. Una solicitud de intervalo HTTP es una solicitud parcial, con el tamaño definido por InitialTransferSize, en este caso. Los blobs menores que este tamaño se transfieren en una sola solicitud. Los blobs mayores que este tamaño siguen transfiriéndose en fragmentos de tamaño MaximumTransferSize.

Es importante tener en cuenta que el valor especificado para MaximumTransferSizeno limita el valor que defina para InitialTransferSize. InitialTransferSize define una limitación de tamaño independiente para que una solicitud inicial realice toda la operación a la vez, sin transferencias secundarias. A menudo, se desea que InitialTransferSize sea al menos tan grande como el valor que defina para MaximumTransferSize, si no mayor. Este enfoque puede ser más eficaz en función del tamaño de la transferencia de datos, ya que la transferencia se completa con una única solicitud y evita la sobrecarga de varias solicitudes.

Si no está seguro de qué valor es mejor para su situación, una opción segura es establecer InitialTransferSize en el mismo valor que se usa para MaximumTransferSize.

Nota

Al usar un objeto BlobClient, la carga de un blob menor que el valor de InitialTransferSize se realizará mediante Put Blob en lugar de Put Block.

MaximumConcurrency

MaximumConcurrency es el número máximo de trabajos que se pueden usar en una transferencia en paralelo. Actualmente, solo las operaciones asincrónicas pueden realizar transferencias en paralelo. Las operaciones sincrónicas omiten este valor y funcionarán en secuencia.

La eficacia de este valor está sujeta a los límites del grupo de conexiones en .NET, lo que puede restringir el rendimiento de manera predeterminada en determinados escenarios. Para más información sobre los límites del grupo de conexiones en .NET, consulte Límites del grupo de conexiones de .NET Framework y el nuevo Azure SDK para .NET.

MaximumTransferSize

MaximumTransferSize es la longitud máxima de una transferencia en bytes. Como se mencionó anteriormente, este valor no limita a InitialTransferSize, que puede ser mayor que MaximumTransferSize.

Para que los datos se muevan de forma eficaz, es posible que las bibliotecas cliente no siempre alcancen el valor de MaximumTransferSize en cada transferencia. En función de la operación, el valor máximo admitido para el tamaño de transferencia puede variar. Por ejemplo, los blobs en bloques que llaman a la operación Put Block con la versión 2019-12-12 o posterior del servicio tienen un tamaño máximo de bloque de 4000 MiB. Para más información sobre los límites de tamaño de transferencia para Blob Storage, consulte el gráfico en Objetivos de escalabilidad de Blob Storage.

Ejemplo de código

La biblioteca cliente incluye sobrecargas para los métodos Upload y UploadAsync, que aceptan una instancia de StorageTransferOptions como parte del parámetro BlobUploadOptions. También existen sobrecargas similares para los métodos DownloadTo y DownloadToAsync mediante el parámetro BlobDownloadToOptions.

En el ejemplo de código siguiente, se muestra cómo definir valores de una instancia de StorageTransferOptions y pasar estas opciones de configuración como parámetro a UploadAsync. Los valores proporcionados en este ejemplo no están diseñados para ser una recomendación. Para ajustar correctamente estos valores, debe tener en cuenta las necesidades específicas de la aplicación.

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

En este ejemplo, establecemos el número de trabajos de transferencia en paralelo en 2 mediante la propiedad MaximumConcurrency. Esta configuración abre hasta dos conexiones simultáneamente, lo que permite que la carga se produzca en paralelo. La solicitud de intervalo HTTP inicial intentan cargar hasta 8 MiB de datos, tal como se define en la propiedad InitialTransferSize. Tenga en cuenta que InitialTransferSize solo se aplica a las cargas cuando se usa una secuencia que permite búsquedas. Si el tamaño del blob es inferior a 8 MiB, solo se necesita una única solicitud para completar la operación. Si el tamaño del blob es mayor que 8 MiB, todas las solicitudes de transferencia posteriores tienen un tamaño máximo de 4 MiB, que establecemos con la propiedad MaximumTransferSize.

Consideraciones de rendimiento para las cargas

Durante una carga, las bibliotecas cliente de Storage dividen un flujo de carga determinado en varias cargas secundarias en función de los valores definidos en la instancia de StorageTransferOptions. Cada carga secundaria tiene su propia llamada dedicada a la operación REST. Para un objeto BlobClient o un objeto BlockBlobClient, esta operación es Put Block. Para un objeto DataLakeFileClient, esta operación es Append Data. La biblioteca cliente de Storage administra estas operaciones REST en paralelo (según las opciones de transferencia) para llevar a cabo la carga completa.

En función de si la secuencia de carga permite búsquedas o no, la biblioteca cliente controla el almacenamiento en búfer e InitialTransferSize de forma diferente, como se describe en las secciones siguientes. Una secuencia que permite búsquedas es una secuencia que admite la consulta y modificación de la posición actual dentro de una secuencia. Para obtener más información sobre las secuencias en .NET, consulte la referencia de la clase Stream.

Nota

Los blobs en bloques tienen un número máximo de bloques de 50 000 bloques. El tamaño máximo del blob en bloques, por tanto, es de 50 000 veces MaximumTransferSize.

Almacenamiento en búfer durante las cargas

La capa REST de Storage no admite reanudar una operación de carga REST donde se dejó; las transferencias individuales se completan o se pierden. Para garantizar la resistencia de las cargas de flujos que no permiten búsquedas, las bibliotecas cliente de Storage almacenan en búfer los datos de cada llamada REST individual antes de iniciar la carga. Además de las limitaciones de velocidad de la red, este comportamiento de almacenamiento en búfer es una razón para considerar un valor más pequeño para MaximumTransferSize, incluso cuando se carga en secuencia. Al disminuir el valor de MaximumTransferSize, se reduce la cantidad máxima de datos que se almacenan en búfer en cada solicitud y cada reintento de una solicitud con errores. Si experimenta tiempos de espera frecuentes durante las transferencias de datos de un tamaño determinado, al reducir el valor de MaximumTransferSize, se reduce el tiempo de almacenamiento en búfer y puede dar lugar a un mejor rendimiento.

Otro escenario en el que se produce el almacenamiento en búfer ocurre cuando se cargan datos con llamadas REST en paralelo para maximizar el rendimiento de red. Las bibliotecas cliente necesitan orígenes que puedan leer en paralelo y, dado que las secuencias son secuenciales, las bibliotecas cliente de Storage almacenan en búfer los datos de cada llamada REST individual antes de iniciar la carga. Este comportamiento de almacenamiento en búfer se produce incluso si la secuencia proporcionada permite búsquedas.

Para evitar el almacenamiento en búfer durante una llamada de carga asincrónica, debe proporcionar una secuencia que permita búsquedas y establecer MaximumConcurrency en 1. Aunque esta estrategia debe funcionar en la mayoría de las situaciones, todavía es posible que se produzca el almacenamiento en búfer si el código usa otras características de la biblioteca cliente que requieren almacenamiento en búfer.

InitialTransferSize al cargar

Cuando se proporciona una secuencia que permite búsquedas para la carga, se comprueba la longitud de la secuencia con el valor de InitialTransferSize. Si la longitud de la secuencia es menor que este valor, la secuencia completa se carga como una sola llamada REST, independientemente de los otros valores de StorageTransferOptions. De lo contrario, la carga se realiza en varias partes, como se ha descrito anteriormente. InitialTransferSize no tiene ningún efecto en una secuencia que no permita búsquedas y se omite.

Consideraciones de rendimiento para las descargas

Durante una descarga, las bibliotecas cliente de Storage divide una solicitud de descarga determinada en varias descargas secundarias en función de los valores definidos en la instancia de StorageTransferOptions. Cada descarga secundaria tiene su propia llamada dedicada a la operación REST. En función de las opciones de transferencia, las bibliotecas cliente administran estas operaciones REST en paralelo para llevar a cabo la descarga completa.

Almacenamiento en búfer durante las descargas

Recibir varias respuestas HTTP simultáneamente con el contenido del cuerpo tiene implicaciones en el uso de memoria. Sin embargo, las bibliotecas cliente de Storage no agregan explícitamente un paso de búfer para el contenido descargado. Las respuestas entrantes se procesan en orden. Las bibliotecas cliente configuran un búfer de 16 kilobytes para copiar las secuencias desde un flujo de respuesta HTTP a una ruta de acceso de archivo o un flujo de destino proporcionado por el autor de la llamada.

InitialTransferSize al descargar

Durante una descarga, las bibliotecas cliente de Storage realizan una solicitud de intervalo de descarga con InitialTransferSize antes de hacer nada más. Durante esta solicitud de descarga inicial, las bibliotecas cliente conocen el tamaño total del recurso. Si la solicitud inicial descargó correctamente todo el contenido, se completa la operación. De lo contrario, las bibliotecas cliente siguen realizando solicitudes de intervalo hasta el valor de MaximumTransferSize hasta que se lleve a cabo la descarga completa.

Pasos siguientes