Compartir vía


Desafíos y enfoques de control de versiones en Durable Functions (Azure Functions)

Es inevitable que las funciones se agreguen, quiten y cambien durante la vigencia de una aplicación. Durable Functions permite encadenar funciones juntas de maneras que no eran posibles anteriormente y este encadenamiento afecta a cómo puede controlar el control de versiones.

Tipos de cambios importantes

Hay varios ejemplos de cambios importantes que se deben tener en cuenta. En este artículo se describen los más comunes. El tema principal detrás de todo ellos es que tanto las orquestaciones de funciones nuevas como las existentes son afectadas por los cambios en el código de función.

Cambiar las firmas de función de actividad o entidad

Un cambio de firma hace referencia a un cambio en el nombre, la entrada o la salida de una función. Si este tipo de cambio se realiza en una función de actividad o entidad, podría interrumpir la función de orquestador que dependa de él. Esto es especialmente cierto para los lenguajes de tipado seguro. Si actualiza la función de orquestador para dar cabida a este cambio, se podrían interrumpir las instancias en curso existentes.

Por ejemplo, supongamos que tenemos la siguiente función de orquestador.

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    await context.CallActivityAsync("Bar", result);
}

Esta función simplista toma los resultados de Foo y la pasa a Bar. Supongamos que es necesario cambiar el valor devuelto de Foo de un valor booleano a una cadena para admitir una variedad más amplia de valores de resultado. El resultado tiene este aspecto:

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    string result = await context.CallActivityAsync<string>("Foo");
    await context.CallActivityAsync("Bar", result);
}

Este cambio funciona bien en todas las nuevas instancias de la función de orquestador, pero puede interrumpir cualquier instancia en curso. Por ejemplo, considere el caso en el que una instancia de orquestación llama a una función denominada Foo, obtiene un valor booleano y, a continuación, puntos de control. Si el cambio de firma se implementa en este momento, se producirá un error en la instancia con los puntos de control inmediatamente después de que se reanude y reproduzca la llamada a Foo. Este error se produce porque el resultado de la tabla de historial es un valor booleano, pero el nuevo código intenta deserializarlo en un valor string, lo que da lugar a un comportamiento inesperado o incluso una excepción en tiempo de ejecución para los lenguajes seguros de tipos.

Este ejemplo es solo una de las muchas maneras diferentes en que un cambio de firma de función puede interrumpir las instancias existentes. En general, si un orquestador necesita cambiar la forma en que llama a una función, es probable que el cambio sea problemático.

Cambio de la lógica del orquestador

El otro tipo de problemas de versiones se produce al cambiar el código de la función del orquestador de forma que cambie la ruta de acceso de ejecución de las instancias en curso.

Considere la función de orquestación siguiente:

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    await context.CallActivityAsync("Bar", result);
}

Ahora supongamos que desea realizar un cambio para agregar una nueva llamada de función entre las dos llamadas de función existentes.

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    if (result)
    {
        await context.CallActivityAsync("SendNotification");
    }

    await context.CallActivityAsync("Bar", result);
}

Este cambio agrega una nueva llamada de función a SendNotification entre Foo y Bar. No hay ningún cambio de firma. El problema surge cuando una instancia existente se reanuda después de la llamada a Bar. Durante la reproducción, si la llamada original a Foo devolvió true, la reproducción del orquestador llamará a SendNotification, que no está en su historial de ejecución. El tiempo de ejecución detecta esta incoherencia y genera un error de orquestación no determinista porque encontró una llamada a SendNotification cuando esperaba ver una llamada a Bar. Se puede producir el mismo tipo de problema al agregar llamadas de API a otras operaciones duraderas, como la creación de temporizadores duraderos, la espera de eventos externos, la llamada a orquestaciones secundarias, etc.

Estrategias de mitigación

Estas son algunas de las estrategias para tratar los desafíos de control de versiones:

  • No hacer nada (no recomendado)
  • Control de versiones de orquestación (recomendado en la mayoría de los casos)
  • Detener todas las instancias en curso
  • Implementaciones en paralelo

No hacer nada

El enfoque más simple del control de versiones es no hacer nada y dejar que las instancias de orquestación en curso no se realicen correctamente. Según el tipo de cambio, pueden producirse los siguientes tipos de errores.

  • Las orquestaciones pueden fallar con un error de orquestación no determinista.
  • Las orquestaciones pueden quedarse bloqueadas indefinidamente, informando de un estado Running.
  • Si se quita una función, cualquier función que intente llamarla puede producir un error.
  • Si una función se quita después de que se programó para ejecutarse, la aplicación puede experimentar errores en tiempo de ejecución de bajo nivel en el motor de Durable Task Framework, lo que podría provocar una degradación grave del rendimiento.

Debido a estos posibles errores, no se recomienda la estrategia "no hacer nada".

Control de versiones de orquestación

Nota:

El control de versiones de orquestación se encuentra actualmente en versión preliminar pública.

La característica de control de versiones de orquestación permite que diferentes versiones de orquestaciones coexistan y se ejecuten simultáneamente sin conflictos y problemas de no determinismo, lo que permite implementar actualizaciones al tiempo que permite que las instancias de orquestación en curso se completen sin intervención manual.

Con control de versiones de orquestación:

  • Cada instancia de orquestación obtiene una versión asociada permanentemente con ella cuando se crea.
  • Las funciones de Orquestador pueden examinar su versión y redirigir la ejecución según sea necesario.
  • Los procesos que ejecutan versiones más recientes de funciones del orquestador pueden seguir ejecutando instancias de orquestación creadas por versiones anteriores.
  • El tiempo de ejecución impide que los trabajadores que ejecutan versiones anteriores de la función de orquestador realicen orquestaciones de versiones más recientes.

Esta estrategia se recomienda para las aplicaciones que necesitan admitir cambios importantes a la vez que mantienen despliegues sin tiempo de inactividad.

Para obtener instrucciones detalladas de configuración e implementación, consulte Control de versiones de orquestación en Durable Functions.

Detener todas las instancias en curso

Otra opción es detener todas las instancias en curso. Si usa el proveedor predeterminado de Azure Storage para Durable Functions, la detención de todas las instancias se puede realizar borrando el contenido de las colas internas control-queue y workitem-queue. También puede detener la aplicación de funciones, eliminar estas colas y reiniciar la aplicación de nuevo. Las colas se volverán a crear automáticamente una vez que se reinicie la aplicación. Las instancias de orquestación anteriores pueden permanecer en el estado "En ejecución" indefinidamente, pero no desordenarán los registros con mensajes de error ni causarán ningún daño en la aplicación. Este enfoque es ideal para el desarrollo rápido de prototipos, incluido el desarrollo local.

Nota:

Este enfoque requiere acceso directo a los recursos de almacenamiento subyacentes y es posible que no sea adecuado para todos los proveedores de almacenamiento admitidos por Durable Functions.

Implementaciones en paralelo

La forma más a prueba de errores para asegurarse de que los cambios importantes se implementan de forma segura es implementarlos en paralelo con las versiones anteriores. Esto se puede hacer mediante cualquiera de las técnicas siguientes:

  • Implemente todas las actualizaciones como funciones completamente nuevas, dejando las funciones existentes as-is. Por lo general, esto no se recomienda debido a la complejidad implicada en la actualización recursiva de los autores de llamadas de las nuevas versiones de la función.
  • Implemente todas las actualizaciones como una nueva aplicación de funciones con una cuenta de almacenamiento diferente.
  • Implemente una nueva copia de la aplicación de funciones con la misma cuenta de almacenamiento, pero con un nombre actualizado del centro de tareas . Esto da como resultado la creación de nuevos artefactos de almacenamiento que puede usar la nueva versión de la aplicación. La versión anterior de la aplicación seguirá ejecutándose con el conjunto anterior de artefactos de almacenamiento.

La implementación en paralelo es la técnica recomendada para implementar nuevas versiones de las aplicaciones de funciones.

Nota:

Esta guía para la estrategia de implementación en paralelo usa términos específicos de Azure Storage, pero se aplica generalmente a todos los proveedores de almacenamiento de Durable Functions admitidos.

Ranuras de implementación

Al realizar implementaciones en paralelo en Azure Functions o Azure App Service, se recomienda implementar la nueva versión de la aplicación de funciones en una nueva ranura de implementación. Las ranuras de implementación permiten ejecutar varias copias de la aplicación de funciones en paralelo con solo una de ellas como ranura de producción activa. Cuando desee exponer la nueva lógica de orquestación en su infraestructura existente, será tan sencillo como intercambiar la nueva versión en la ranura de producción.

Nota:

Esta estrategia funciona mejor cuando se usan desencadenadores HTTP y webhook para funciones de orquestador. Para desencadenadores que no son HTTP, como colas o Event Hubs, la definición del desencadenador debe derivar de una configuración de aplicación que se actualice como parte de la operación de intercambio.

Pasos siguientes