Azure Durable Functions | Usando Netherite como Storage Provider

En mayo de este año 2021, Microsoft anunció la llegada de nuevos proveedores de almacenamiento (Storage Providers) para mejorar el rendimiento de Azure Durable Functions ya que, en algunos casos, se requiere de mayor rendimiento, fiabilidad o flexibilidad. En este artículo veremos cómo cambiar el Storage Provider a Netherite, cómo funciona y qué ventajas nos aporta.

¿Qué es Netherite?

Netherite, que aún está en Alpha, no es en sí un servicio ni un artefacto, Netherite es un motor de ejecución de flujo de Azure Durable Functions, es decir, lo que cambia es el modo en el que funcionará internamente el flujo de la orquestación durante la ejecución.

Podemos acceder al código y a los ejemplos en el repositorio de GitHub

¿Cómo funciona Netherite?

Para el correcto funcionamiento de Azure Durable Functions, es necesario el uso de algún sistema que permita ejecutar las diferentes Functions que componen cada orquestación incluyendo con ello las sub-orquestaciones. Además, se usa un sistema de almacenamiento para poder guardar el estado y la información en cada momento.

Para ello, en el modelo actual con BlobStorage como proveedor de almacenamiento, se usan Azure Storage Queues para almacenar los mensajes y Azure Storage Tables para almacenar la información del estado.

Aunque este modelo funciona muy bien, en orquestadores que requieran una gran carga de trabajo, tantas escrituras y lecturas en colas y tablas, frena el rendimiento y el escalado.

Netherite intenta solucionar este problema haciendo uso de Azure Event Hubs para almacenar los mensajes de forma persistente y poder ejecutarlos en orden y Azure PageBlobs para almacenar el estado de la instancia de Durable Function en forma de logs, permitiendo un mayor rendimiento.

Requisitos

Antes de empezar a usar Netherite, tenemos que tener nuestro entorno de desarrollo preparado con todo lo necesario para no llevarnos sorpresas a mitad de camino.

Microsoft nos propone estos requisitos mínimos. Tened cuidado si queréis usar TypeScript o Python, porque hay algunos más, así que id a la documentación

.NET Core SDK 3.1 or later. You can run dotnet --list-runtimes to check what is installed.

Azure Functions Core Tools 3.x or later. You can run func --version to check what is installed.

PowerShell 7.1 or later. You can check what is installed with pwsh --version. PowerShell is easy to install or update via dotnet tool install --global PowerShell or dotnet tool install --global PowerShell, respectively.

Azurite 3.x as storage emulator for the local execution. You can run azurite --version to check what is installed.

The Azure CLI 2.18.0 or later. You can run az --version to check what is installed.

An Azure subscription, to allocate and deploy the required resources.
‘Hello’ Sample (microsoft.github.io)

Si vamos a realizar ejecuciones sólo en local, es muy importante tener los 4 primeros requisitos, .Net SDK , Azure Functions Core Tools, PowerShell y Azurite.

Una vez tengamos todo instalado, podremos pasar al siguiente punto, que es establecer Netherite como Storage Provider.

Cómo establecer Netherite como Storage Provider

Microsoft, en su afán de hacernos las cosas más sencillas como siempre, ha puesto a nuestra disposición un ejemplo de Azure Durable Function usando Netherite como Storage Provider. Este ejemplo está desarrollado con .NET Core 3.1 y Azure Functions v3.

Pero como en la vida real no podemos vivir de ejemplos, veamos cómo lo podemos establecer nosotros.

Editar local.settings.json

Lo primero es establecer las variables de configuración necesarias para el funcionamiento de la Azure Durable Function con Netherite. Para ello, tendremos que establecer las siguiente propiedades:

AzureWebJobsStorage: Cadena de conexión a la cuenta de Storage

EventHubsConnection: Cadena de conexión a Event Hub Namespace

Si queremos ejecutar en local el archivo quedaría de la siguiente forma:

{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"EventHubsConnection": "MemoryF",

"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}

MemoryF es la expresión clave que indica que se usará Event Hub de forma local

En caso de querer usar los servicios de Blob Storage y Event Hub publicados en Azure, será necesario establecer las cadenas de conexión con la clave de acceso correspondiente a cada uno de ellos

{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=[ACCOUNTNAME];AccountKey=[ACCOUNTKEY];EndpointSuffix=core.windows.net",
"EventHubsConnection": "Endpoint=[EVENTHUB_ENDPOINT];SharedAccessKeyName=[SHAREDACCESSKEYNAME];SharedAccessKey=[SHAREDACCESSKEYVALUE]",

"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}

Editar host.json

El siguiente paso es editar el archivo host.json para, ahora que tenemos la configuración necesaria, indicar que se debe usar Netherite con esos valores. Para ello, en la documentación podemos encontrar dos ejemplos, la versión mínima y la versión recomendada.

Versión mínima indicando simplemente Netherite como Storage Provider:

{
"version": "2.0",
"extensions": {
"durableTask": {
"storageProvider": {
"type": "Netherite"
}
}
}
}

Versión recomendada configurando número de instancias concurrentes tanto de actividades como de orquestadores y número de particiones:

{
"version": "2.0",
"extensions": {
"durableTask": {

// The task hub name can be changed to start from a clean state
"hubName": "myawesomeapp-11",

// set this to true to reduce likelihood of duplicated work items
"UseGracefulShutdown": "true",

// tweak these for performance tuning (work the same for all backends)
"maxConcurrentActivityFunctions": "100",
"maxConcurrentOrchestratorFunctions": "100",

"storageProvider": {

"type": "Netherite",

// the number of partitions to use. Cannot be changed after task hub is created.
"PartitionCount": "12",

// where to look for the connection strings
"StorageConnectionName": "AzureWebJobsStorage",
"EventHubsConnectionName": "EventHubsConnection"
}
}
}
}

Ejecutando la Azure Durable Function con Netherite

Una vez hayamos editado los ficheros de configuración, ya podremos hacer uso de Netherite, local o con servicios en Azure. Así que probemos la orquestación que, en mi caso, se trata de la que genera la plantilla de Visual Studio con el único cambio del nombre de las ciudades en el Hello.

Si además revisamos las métricas de uso del servicio de Event Hub, podremos observar la cantidad de solicitudes que se generan para un ejemplo tan pequeño y que, usando Netherite, se realizan de forma eficiente y fiable.

Ventajas de usar Netherite

Ya hemos hablado de algunas de las ventajas de Netherite pero vamos a enumerarlas junto con el resto para hacer un resumen:

Significativamente un mejor rendimiento

Menor coste de escalado permitiéndonos controlar y optimizar el gasto de forma flexible

Hasta 32 particiones de datos con Event Hubs Basic y Standard SKUs

Mayor eficacia en la relación coste/rendimiento que otros proveedores

Pero como las palabras escritas nos dicen poco en la realidad de nuestros desarrollos, veamos una gráfica con el benchmark entre BlobStorage y Netherite como Storage Providers para Durable Functions que es demoledora, demostrando que puede llegar a ser hasta casi 200 veces mejor en rendimiento Netherite.

Vídeo

Si preferís ver el vídeo donde explico todo esto y lo muestro en vivo, aquí lo tenéis.

Resumen

Si se usa Azure Durable Functions para cargas de trabajo grandes donde se necesita un buen rendimiento, BlobStorage como proveedor de almacenamiento presenta algunos inconvenientes que Netherite puede resolver mejorando significativamente el rendimiento. Además es muy flexible a la hora de escalar y muy sencillo de configurar.

No obstante, siempre recomiendo analizar bien el caso donde se debe aplicar para no incurrir en un sobrecoste innecesario si BlobStorage nos aporta el rendimiento adecuado.

Espero que os haya gustado.

Enjoy coding!