Rendimiento y escala para Event Hubs y Azure Functions

Event Hubs
Functions

Al usar Azure Functions con Event Hubs, hay muchas características y decisiones implicadas que influyen tanto en el rendimiento como en la escala. En este artículo se proporciona una guía prescriptiva para sacar el máximo partido a este emparejamiento dinámico.

Agrupación de funciones

Normalmente, una función encapsula una unidad de trabajo que procesa eventos individuales. Este trabajo puede incluir la transformación de un evento en una nueva estructura de datos, o bien un paso de enriquecimiento en una canalización de datos para aplicaciones descendentes.

La forma de agrupar las funciones puede tener un efecto directo en las funcionalidades de rendimiento y escala de las aplicaciones de funciones. Varios procedimientos recomendados sugieren la agrupación por derechos de acceso, implementación y patrones de uso que invocan el código.

Otras instrucciones para agrupar funciones, con consideraciones sobre el almacenamiento y el grupo de consumidores, incluyen las siguientes:

  • Hospedaje de una sola función dentro de una aplicación de funciones: una función aislada desencadenada por Event Hubs reduce la contención de recursos entre otras funciones en ejecución, especialmente las que consumen mucha CPU o memoria. Esta ventaja se produce porque cada función tiene su propia superficie de memoria y patrones de uso que pueden afectar directamente a la escala de la aplicación de funciones en la que se ejecutan las funciones.

  • Cuentas de almacenamiento independientes para cada aplicación de funciones: evite compartir cuentas de almacenamiento entre aplicaciones de funciones. Además, no utilice la misma cuenta de almacenamiento que se usa para la aplicación de funciones en otras operaciones o necesidades de almacenamiento. Es importante usar cuentas de almacenamiento independientes, ya que las funciones desencadenadas por Event Hubs pueden tener un gran volumen de transacciones de almacenamiento debido a los puntos de comprobación.

  • Creación de un grupo de consumidores dedicado para cada aplicación de funciones: en una solución de procesamiento de flujos, cada aplicación de consumidor equivale a un grupo de consumidores. Una aplicación de funciones es un ejemplo excelente de una aplicación de consumidor. No comparta grupos de consumidores entre aplicaciones de funciones y otras aplicaciones de consumidor. En el diagrama siguiente se proporciona un ejemplo de dos aplicaciones de funciones que leen desde un centro de eventos, donde cada aplicación tiene su propio grupo de consumidores dedicado:

    Grupos de consumidores dedicados para cada aplicación de funciones

En resumen, cada aplicación de funciones se debe considerar como una aplicación distinta con su propio grupo de consumidores asignado. Esto garantiza la integridad de desplazamiento para cada consumidor y simplifica las dependencias en una arquitectura de streaming de eventos. Esta configuración, junto con la acción de proporcionar a cada función desencadenada por el centro de eventos su propia aplicación de funciones y cuenta de almacenamiento, ayuda a establecer las bases para un rendimiento y una escala óptimos.

Planes de hospedaje de funciones

La revisión de cómo se escalan las funciones en distintos planes es un paso importante al evaluar las opciones de hospedaje.

Cuando se usa el plan de consumo, de forma predeterminada cada aplicación de funciones tiene su propio plan. Las aplicaciones de funciones del plan de consumo se escalan de forma independiente y son más eficaces cuando evitan tareas de larga duración.

Los planes Premium y Dedicado se usan a menudo para hospedar varias funciones y aplicaciones de funciones que consumen más CPU y memoria. Es importante tener en cuenta que todas las aplicaciones de funciones de estos planes comparten los mismos recursos asignados al plan. Si las funciones tienen perfiles de carga diferentes o requisitos únicos, es mejor hospedarlas en planes diferentes. Esto es especialmente cierto para las aplicaciones de procesamiento de flujos.

Escalado de Event Hubs

Cuando se trata de un espacio de nombres de Event Hubs, hay varias configuraciones importantes que deben evaluarse para garantizar un rendimiento y una escala máximos. Esta sección se centra en el nivel Estándar de Event Hubs y sus características únicas que afectan al escalado cuando se usa con Azure Functions. Para obtener más información sobre los niveles de Event Hubs, vea Nivel básico frente a estándar frente a Premium frente a dedicado.

Descripción de las unidades de procesamiento

En el nivel estándar de Event Hubs, el rendimiento se clasifica como la cantidad de datos que entra y se lee del espacio de nombres dentro de un período determinado. Una unidad de procesamiento (TU) es un mecanismo que se usa para medir y administrar la cantidad de rendimiento que admite un espacio de nombres de Event Hubs.

Como referencia, un espacio de nombres de Event Hubs se puede comparar con un clúster de Kafka. Para obtener una asignación conceptual entre Kafka y Event Hubs, revise esta tabla.

Cada unidad de procesamiento se factura por horas y se comparte entre todos los centros de eventos de un espacio de nombres. Esto significa que todas las aplicaciones y servicios, tanto los publicadores como los consumidores, deben tenerse en cuenta al elegir el número de TU asignadas. Azure Functions afecta al número de bytes y eventos que se publican y se leen desde un centro de eventos.

El énfasis para determinar el número de TU se centra en torno al punto de entrada. Pero el agregado para las aplicaciones de consumidor, incluida la velocidad a la que se procesan esos eventos, también debe incluirse en el cálculo.

Escalado vertical con inflado automático

El inflado automático se puede habilitar en un espacio de nombres de Event Hubs para dar cabida a escenarios en los que la carga aumenta más allá del número configurado de TU. Esto garantiza que no se produzca la limitación del servicio y de que el procesamiento, incluida la ingesta de eventos, pueda continuar sin interrupciones. Como las TU son una de las configuraciones importantes que también afectan a los costos, aprovechar las ventajas de la característica de inflado automático ayuda a resolver las preocupaciones sobre el sobreaprovisionamiento.

El inflado automático es una característica única de Event Hubs que a menudo se confunde con la escalabilidad automática, especialmente en el contexto de las soluciones sin servidor. Es importante tener en cuenta que esta característica de Event Hubs admite el aumento dinámico de las TU para admitir escenarios de ráfagas, pero no tiene una característica para desinflar o reducir verticalmente de manera automática.

En los escenarios en los que se necesita un alto rendimiento y no se pueden alojar con el número máximo asignado de TU, tenga en cuenta el nivel Premium de Azure Event Hubs o el clúster dedicado.

Particiones y funciones simultáneas

Después de crear un centro de eventos, es necesario especificar el número de particiones. El recuento de particiones es fijo y no se puede cambiar excepto en los niveles Premium y Dedicado. Al usar el desencadenador de Event Hubs, el número de instancias simultáneas de la aplicación de funciones puede coincidir potencialmente con el número de particiones.

En los planes Consumo y Premium, las instancias de aplicación de funciones se escalan horizontalmente de forma dinámica para satisfacer el número de particiones, cuando sea necesario. En los planes Dedicado (App Service) es necesario configurar manualmente las instancias, o bien configurar y escalar automáticamente el esquema. En última instancia, una relación uno a uno entre el número de particiones y las instancias de aplicación de funciones es el destino ideal para el rendimiento máximo en una solución de procesamiento de flujos.

El paralelismo óptimo se logra al tener varios consumidores dentro de un grupo de consumidores. En Azure Functions, esto se traduce en muchas instancias de una aplicación de funciones dentro del plan. El resultado se conoce como paralelismo de nivel de partición o el grado máximo de paralelismo.

Grado máximo de paralelismo

Inicialmente, podría tener sentido configurar tantas particiones como sea posible para lograr el máximo rendimiento y tener en cuenta la posibilidad de un mayor volumen de eventos. Pero hay varios factores importantes que se deben tener en cuenta cuando se configuran muchas particiones:

  • Más particiones podrían dar lugar a un mayor rendimiento: como el grado de paralelismo es el número de consumidores (instancias de aplicación de funciones), cuantas más particiones haya, mayor puede ser el rendimiento simultáneo. Este hecho es importante al compartir un número designado de TU para un centro de eventos con otras aplicaciones de consumidor.

  • Es posible que más funciones necesiten más memoria: a medida que aumenta el número de instancias de aplicación de funciones, también aumenta la superficie de memoria de los recursos dentro del plan. En algún momento, demasiadas particiones podrían degradar el rendimiento de los consumidores.

  • Presión de los servicios descendentes: a medida que se genera más rendimiento, puede surgir la posibilidad de sobrecargar o recibir presión de los servicios descendentes. La distribución ramificada de los consumidores debe tenerse en cuenta al considerar las consecuencias que puede tener en los recursos circundantes. Algunos ejemplos pueden ser la limitación de otros servicios, la saturación de la red y otras formas de contención de recursos que puedan producirse con un aumento del rendimiento.

  • Particiones rellenadas de forma dispersa: la combinación de muchas particiones y un bajo volumen de eventos puede dar lugar a datos que se distribuyen de manera dispersa entre las particiones. En su lugar, un número menor de particiones puede proporcionar un rendimiento y un uso de recursos óptimos para que Functions los consuma.

Disponibilidad y coherencia

Cuando no se especifica una clave de partición o un identificador, el servicio Event Hubs enruta un evento entrante a la siguiente partición disponible. Este enfoque proporciona alta disponibilidad y ayuda a aumentar el rendimiento de los consumidores.

Cuando el orden es importante, se puede especificar una partición concreta para conservar el orden de los eventos cuando se publican. Después, una aplicación de consumidor que lee de la misma partición puede procesar los eventos en orden. Esta compensación proporciona coherencia, pero pone en peligro la disponibilidad. Use solo el enfoque cuando la ordenación de eventos sea un requisito.

Para Functions, la ordenación se logra cuando los eventos se publican en una partición determinada y una función desencadenada por Event Hubs obtiene una concesión a la misma partición. Actualmente, no se admite la capacidad de configurar una partición con el enlace de salida de Event Hubs. En su lugar, el uso de uno de los SDK de Event Hubs es el mejor enfoque para realizar la publicación en una partición específica.

Para obtener una explicación más detallada de cómo se admiten la disponibilidad y la coherencia con Azure Event Hubs, se recomienda revisar este artículo.

Desencadenador de Event Hubs

Esta sección se centra en la configuración y las consideraciones implicadas al optimizar las funciones desencadenadas por Event Hubs para obtener un rendimiento máximo. Entre los factores se incluyen el procesamiento por lotes, el muestreo y las características relacionadas que influyen en el comportamiento de un enlace de desencadenador del centro de eventos.

Lotes

Las funciones desencadenadas por centros de eventos se pueden configurar para procesar una colección de eventos, o bien un evento a la vez. El procesamiento de un lote de eventos es más eficaz debido a la sobrecarga que implica la invocación de cada función. A menos que necesite procesar solo un evento, la función se debe configurar para procesar varios eventos cuando se invoca.

La habilitación del procesamiento por lotes para el enlace de desencadenador de Event Hubs varía según el lenguaje:

  • En C#, la cardinalidad se configura automáticamente cuando se designa una matriz para el tipo en el atributo EventHubTrigger.

  • En JavaScript, Python y otros lenguajes, el procesamiento por lotes se habilita cuando la propiedad de cardinalidad se establece en many en el archivo function.json de la función.

Para obtener más información sobre cómo se habilita el procesamiento por lotes, vea las opciones attribute y annotation de cada lenguaje admitido.

Configuración del desencadenador

Varias opciones de configuración del archivo host.json desempeñan un papel clave en las características de rendimiento del enlace de desencadenador de Event Hubs para Azure Functions:

  • maxBatchSize: este valor representa el número máximo de eventos que recibe la función cuando se invoca. Es importante reconocer que este no es el número mínimo de eventos, solo el máximo. Si el número de eventos recibidos es menor que esta cantidad, la función se sigue invocando con tantos eventos como estén disponibles. No se puede establecer el tamaño mínimo del lote.

  • prefetchCount: uno de los valores más importantes a la hora de optimizar el rendimiento es el recuento de captura previa. El canal AMQP subyacente hace referencia a este valor para determinar cuántos mensajes se capturarán y almacenarán en caché para el cliente. El recuento de captura previa debe ser mayor o igual que el valor maxBatchSize, y normalmente se establece en un múltiplo de esa cantidad. Establecer este valor en un número menor que maxBatchSize podría tener un impacto negativo en el rendimiento.

  • batchCheckpointFrequency: a medida que la función procesa los lotes, este valor se usa para determinar la velocidad a la que se crea un punto de control. De forma predeterminada, este valor se establece en 1, lo que significa que, después de que una función procese correctamente un lote, se genera un punto de control. Tenga en cuenta que se crea un punto de control en el nivel de partición para cada lector del grupo de consumidores. En esta interesante entrada de blog se proporciona información adicional sobre el comportamiento que puede generar este valor y cómo influye en las reproducciones y reintentos de eventos.

Los valores que establezca para el enlace de desencadenador deben determinarse a lo largo de varias iteraciones y pruebas de rendimiento. Se recomienda que los cambios se realicen de forma iterativa y se midan de forma coherente para ajustar estas opciones en consecuencia. Los valores predeterminados son un esfuerzo por proporcionar un punto de partida para la mayoría de las soluciones de procesamiento de eventos.

Puntos de control

Comprender el concepto de puntos de control es fundamental para las funciones desencadenadas por Event Hubs. Es responsabilidad del host de Functions establecer puntos de control a medida que se procesan los eventos y se cumple el valor de frecuencia del punto de comprobación por lotes.

Los siguientes conceptos son importantes para comprender la relación entre los puntos de control y cómo la función procesa los eventos:

  • Las excepciones siguen contando para la finalización correcta: si el proceso de función no se bloquea durante el procesamiento de eventos, la finalización de la función se considera correcta. La detección y el control de excepciones debe seguir siendo un enfoque defensivo en el código de la función. Cuando se realiza correctamente, el host de Functions evalúa la configuración del punto de control de frecuencia de lotes y crea un punto de control si se ha alcanzado, independientemente de las excepciones que se hayan producido durante el procesamiento.

  • La frecuencia del lote es importante: en las soluciones de streaming de gran volumen de eventos, puede ser beneficioso cambiar batchCheckpointFrequency a un valor mayor que 1. Aumentar este valor puede ayudar a reducir la velocidad de creación de un punto de control, lo que reducirá el número de operaciones de E/S a la cuenta de almacenamiento y dará un mayor rendimiento.

  • Se pueden producir reproducciones: cada vez que se invoca una función con el enlace de desencadenador de Event Hubs, usa el punto de control más reciente para determinar dónde reanudar el procesamiento. Se ha subrayado que el desplazamiento de cada consumidor se guarda en el nivel de partición para cada grupo de consumidores. Las reproducciones se producen cuando no se ha producido un punto de control durante la última invocación de la función y se invoca de nuevo. Para obtener más información sobre los duplicados y las técnicas de desduplicación, vea Idempotencia en el siguiente artículo.

Comprender los puntos de control es fundamental al considerar los procedimientos recomendados para el control de errores y los reintentos, que se tratarán en una sección posterior de este artículo.

Muestreo en Application Insights

Azure Functions proporciona compatibilidad integrada con Application Insights. Con esta característica, puede recopilar datos de registro, rendimiento e información sobre las excepciones en tiempo de ejecución que se producen dentro de las funciones. Esta configuración eficaz ofrece algunas opciones de configuración clave que afectarán al rendimiento. Algunas de las configuraciones y consideraciones importantes para la supervisión y el rendimiento son las siguientes:

  • Habilitación del muestreo de telemetría: para escenarios de alto rendimiento, debe evaluar la cantidad de telemetría e información que necesitará. Revise la característica de muestreo de telemetría en Application Insights para evitar degradar el rendimiento de la función con métricas y datos de telemetría innecesarios.

  • Configuración de valores de agregación: examine y configure la frecuencia con la que se agregan los datos y se envían a Application Insights. Esta opción de configuración está en el archivo host.json junto con muchas otras opciones relacionadas con el muestreo y el registro. Para obtener más información, vea Configuración del agregador.

  • Deshabilitación de AzureWebJobDashboard: en el caso de las aplicaciones que tienen como destino la versión 1.x del entorno de ejecución de Azure Functions, este valor almacena la cadena de conexión en una cuenta de almacenamiento que usa el SDK de Azure para conservar los registros del panel WebJobs. Si se usa Application Insights en lugar del panel WebJobs, este valor se debe quitar. Para obtener más información, vea la referencia del valor AzureWebJobsDashboard.

Es importante mencionar que cuando Application Insights se habilita sin muestreo, se envían todos los datos de telemetría. El envío de datos sobre todos los eventos puede tener un efecto perjudicial en el rendimiento general de la función, especialmente en escenarios de streaming de eventos de alto rendimiento.

Aprovechar el muestreo y evaluar continuamente la cantidad adecuada de telemetría necesaria para la supervisión es una de las muchas configuraciones cruciales disponibles para un rendimiento óptimo. La telemetría se debe usar para la evaluación general del estado de la plataforma en el agregado y para solucionar problemas ocasionales, y no para capturar métricas empresariales principales. Para más información, consulte Configuración del muestreo.

Enlace de salida

La publicación en un flujo de eventos desde una función se simplifica mediante el enlace de salida para Event Hubs. Entre las ventajas de usar este enlace se incluyen las siguientes:

  • Administración de recursos: el enlace controla de forma automática los ciclos de vida del cliente y de la conexión. Esto reduce la posibilidad de que surjan problemas con el agotamiento de puertos y la administración del grupo de conexiones.

  • Menos código: el enlace abstrae el SDK subyacente y reduce la cantidad de código necesario para publicar eventos. El código resultante es más fácil de escribir y mantener.

  • Procesamiento por lotes: para varios lenguajes, se admite el procesamiento por lotes para publicar de forma eficaz en un flujo de eventos. Esto puede mejorar el rendimiento y ayudar a simplificar el código que envía los eventos.

Se recomienda encarecidamente revisar la lista de lenguajes admitidos para Azure Functions y sus respectivas guías para desarrolladores. En la sección Enlaces de cada lenguaje se proporcionan ejemplos detallados y documentación.

Lotes

Si la función solo publica un evento único, la configuración del enlace con un valor devuelto es un enfoque común. Esto resulta útil si la ejecución de la función siempre termina con una instrucción que envía el evento. El uso del valor devuelto es un patrón que solo se debe utilizar para las funciones sincrónicas que devuelven un único evento.

Se recomienda el procesamiento por lotes para mejorar el rendimiento al enviar varios eventos a una secuencia. El procesamiento por lotes permite que el enlace publique eventos de la manera más eficaz posible.

La compatibilidad para enviar varios eventos con el enlace de salida a Event Hubs está disponible en C#, Java, Python y JavaScript.

Salida de varios eventos en C#

Use los tipos ICollector y IAsyncCollector al enviar varios eventos desde una función en C#.

  • El método ICollector<T>.Add() se puede usar en funciones sincrónicas y asincrónicas. Ejecuta la operación de adición en cuanto se llama.

  • El método IAsyncCollector<T>.AddAsync() prepara los eventos que se publicarán en el flujo de eventos. Si va a escribir una función asincrónica, debe usar IAsyncCollector para administrar mejor los eventos publicados.

Consulte la documentación para obtener ejemplos de publicación de eventos únicos y múltiples mediante C#.

Limitación y presión retroactiva

Las consideraciones de limitación también se aplican al enlace de salida, no solo para Event Hubs sino también para servicios de Azure como Azure Cosmos DB. En general, es importante familiarizarse con los límites y las cuotas que se aplican a esos servicios, y planear en consecuencia.

Para controlar los errores descendentes, puede detectar excepciones de IAsyncCollector si encapsula AddAsync y FlushAsync en un controlador de excepciones para Azure Functions de .NET, o bien no usar enlaces de salida y utilizar directamente los SDK de Event Hubs.

Código de función

En esta sección se describen las áreas clave que se deben tener en cuenta al escribir código para procesar eventos de una función desencadenada por Event Hubs.

Programación asincrónica

Se recomienda que la función use código asincrónico sin bloqueo. Esto es importante cuando hay llamadas de E/S implicadas.

Al considerar la programación asincrónica en una instancia de Functions, se deben seguir varias instrucciones básicas:

  • Todo asincrónico o todo sincrónico: si una función está configurada para ejecutarse de forma asincrónica, todas las llamadas de E/S también deben ser asincrónicas. En la mayoría de los casos, el código parcialmente asincrónico puede ser peor que el código completamente sincrónico. Elija la implementación asincrónica o sincrónica de la función, y manténgala hasta el final.

  • Evite llamadas de bloqueo: las llamadas de bloqueo solo se devuelven al autor de la llamada una vez que se completa la llamada. Esto es diferente de las llamadas asincrónicas que devuelven un valor inmediatamente. Un ejemplo en C# sería llamar a Task.Result o Task.Wait en una operación asincrónica.

Más información sobre las llamadas de bloqueo

Cuando se realizan llamadas de bloqueo en operaciones asincrónicas, se puede provocar el colapso del grupo de subprocesos y el bloqueo del proceso de la función. Esto sucede porque una llamada de bloqueo necesita que se cree otro subproceso para compensar la llamada original que ahora está en espera. Como resultado, ahora se necesitan el doble de subprocesos para completar la operación.

Evitar este enfoque sincrónico sobre asincrónico es especialmente importante cuando Event Hubs está implicado, ya que un bloqueo en la función no actualizará el punto de control. La próxima vez que se invoque la función, podría acabar en este ciclo y parecer que se ha bloqueado, o que avanza lentamente, ya que las ejecuciones de funciones agotarán el tiempo de espera.

La solución de este fenómeno suele comenzar con la revisión de la configuración del desencadenador y la ejecución de experimentos que pueden implicar el aumento del número de particiones. Las investigaciones también pueden llevar a cambiar varias de las opciones de procesamiento por lotes, como el tamaño máximo de lote o el recuento de captura previa. La impresión es que se trata de un problema de rendimiento o de un valor de configuración que solo debe ajustarse en consecuencia. Pero el problema principal está en el propio código y debe solucionarse allí para que la resolución sea adecuada.

Pasos siguientes

Antes de continuar, considere la posibilidad de revisar estos artículos relacionados: