Compartilhar via


Desafios e abordagens de controle de versão no Durable Functions (Azure Functions)

É inevitável que as funções sejam adicionadas, removidas e alteradas ao longo do tempo de vida de um aplicativo. O Durable Functions permite encadear funções em conjunto de maneiras que não eram possíveis anteriormente, e esse encadeamento afeta como você pode lidar com o controle de versão.

Tipos de alterações da falha

Há vários exemplos de alterações que causam interrupção a serem considerados. Este artigo discute os mais comuns. O tema principal por trás delas é que tanto orquestrações de função novas quando as já existentes são afetadas por alterações no código da função.

Alterando assinaturas da função de atividade ou entidade

Uma alteração de assinatura refere-se a uma alteração no nome, entrada ou saída de uma função. Se esse tipo de alteração for feita em uma função de atividade ou entidade, ela poderá interromper a função de orquestrador que depende dela. Isso é especialmente verdadeiro para linguagens fortemente tipados. Se atualizar a função de orquestrador para acomodar essa alteração, você poderá interromper instâncias existentes em curso.

Por exemplo, suponha que tenhamos a seguinte função de orquestrador.

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

Essa função simplista pega os resultados de Foo e a passa para Bar. Vamos supor que precisamos alterar o valor retornado de Foo de um booliano para uma cadeia de caracteres para dar suporte a uma variedade maior de valores de resultado. O resultado tem esta aparência:

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

Essa alteração funciona bem para todas as novas instâncias da função de orquestrador, mas poderá interromper todas as instâncias de versões piloto. Por exemplo, considere o caso em que uma instância de orquestração chama uma função nomeada Foo, obtém de volta um valor booliano e, em seguida, pontos de verificação. Se a alteração da assinatura for implantada nesse ponto, a instância que passou pela verificação falhará imediatamente quando for retomada e reproduzir a chamada para Foo. Essa falha ocorre porque o resultado na tabela de histórico é um valor booliano, mas o novo código tenta desserializá-lo em um valor string, resultando em comportamento inesperado ou até mesmo exceção de runtime para idiomas com segurança de tipo.

Este exemplo é apenas uma das muitas maneiras diferentes pelas quais uma alteração na assinatura de uma função pode interromper instâncias existentes. Em geral, se um orquestrador precisar alterar a maneira como chama uma função, a alteração provavelmente será problemática.

Alterando a lógica do orquestrador

O outro tipo de problema de controle de versão é consequente da alteração do código de função do orquestrador de uma forma que altera o caminho de execução para as instâncias em andamento.

Considere a seguinte função de orquestrador:

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

Agora vamos supor que você deseja fazer uma alteração para adicionar uma nova chamada de função entre as duas chamadas de função 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);
}

Essa alteração adiciona uma nova chamada de função a SendNotification entre Foo e Bar. Não há nenhuma alteração de assinatura. O problema surge quando uma instância existente é retomada após a chamada para Bar. Durante a reprodução, se a chamada original para Foo retornar true, a reprodução do orquestrador chamará SendNotification, que não está em seu histórico de execução. O runtime detecta essa inconsistência e gera um erro de orquestração não determinístico porque encontrou uma chamada para SendNotification quando esperava ver uma chamada para Bar. O mesmo tipo de problema pode ocorrer ao adicionar chamadas de API a outras operações duráveis, como criar temporizadores duráveis, aguardar eventos externos, chamar sub-orquestrações etc.

Estratégias de mitigação

Aqui estão algumas das estratégias para lidar com os desafios de controle de versão:

  • Não fazer nada (não recomendado)
  • Controle de versão de orquestração (recomendado na maioria dos casos)
  • Parar todas as instâncias em curso
  • Implantações lado a lado

Não fazer nada

A abordagem simples para o controle de versão é não fazer nada e deixar que as instâncias de orquestração em andamento falhem. Dependendo do tipo de alteração, os seguintes tipos de falhas podem ocorrer.

  • As orquestrações podem falhar com um erro de orquestração não determinística .
  • As orquestrações podem ficar travadas indefinidamente, relatando um status Running.
  • Se uma função for removida, qualquer função que tente chamá-la poderá falhar com um erro.
  • Se uma função for removida depois de agendada para ser executada, o aplicativo poderá enfrentar falhas de runtime de baixo nível no mecanismo da Estrutura de Tarefas Duráveis, resultando potencialmente em grave degradação de desempenho.

Devido a essas possíveis falhas, a estratégia de "não fazer nada" não é recomendada.

Controle de versão de orquestração

Observação

O controle de versão de orquestração está atualmente em versão prévia pública.

O recurso de controle de versão de orquestração permite que diferentes versões de orquestrações coexistam e executem simultaneamente sem conflitos e problemas de não determinismo, possibilitando a implantação de atualizações, permitindo a conclusão de instâncias de orquestração em voo sem intervenção manual.

Com controle de versão de orquestração:

  • Cada instância de orquestração obtém uma versão permanentemente associada a ela quando criada
  • As funções do orquestrador podem examinar a versão e a execução da ramificação de forma adequada
  • Os trabalhadores que executam versões de função de orquestrador mais recentes podem continuar executando instâncias de orquestração criadas por versões mais antigas
  • O runtime impede que os trabalhadores que executam as versões de função de orquestrador mais antigas executem orquestrações de versões mais recentes

Essa estratégia é recomendada para os aplicativos que precisam dar suporte às alterações interruptivas, mantendo implantações de tempo de inatividade zero.

Para obter diretrizes detalhadas de configuração e implementação, consulte Controle de versão de orquestração nas Funções duráveis.

Parar todas as instâncias em curso

Outra opção é parar todas as instâncias em curso. Se você estiver usando o provedor de Armazenamento do Microsoft Azure padrão para o Durable Functions, a interrupção de todas as instâncias pode ser feita limpando o conteúdo das filas internas control-queue e workitem-queue. Como alternativa, você pode parar o aplicativo de funções, excluir essas filas e reiniciar o aplicativo novamente. As filas serão recriadas automaticamente quando o aplicativo for reiniciado. As instâncias de orquestração anteriores podem permanecer no estado "Em execução" indefinidamente, mas elas não sobrecarregarão seus logs com mensagens de falha nem causarão danos à sua aplicação. Essa abordagem é ideal no desenvolvimento rápido de protótipos, incluindo o desenvolvimento local.

Observação

Essa abordagem requer acesso direto aos recursos de armazenamento subjacentes e pode não ser apropriada para todos os provedores de armazenamento compatíveis com o Durable Functions.

Implantações lado a lado

A maneira mais infalível para garantir que as alterações disruptivas sejam implantadas com segurança é implantando-as simultaneamente com suas versões mais antigas. Isso pode ser feito usando qualquer uma das seguintes técnicas:

  • Implante todas as atualizações como funções totalmente novas, deixando as funções existentes as-is. Isso geralmente não é recomendado devido à complexidade envolvida na atualização recursiva dos chamadores das novas versões de função.
  • Implante todas as atualizações como um novo aplicativo de funções com uma conta de armazenamento diferente.
  • Implante uma nova cópia do aplicativo de funções com a mesma conta de armazenamento, mas com um nome de hub de tarefas atualizado. Isso resulta na criação de novos artefatos de armazenamento que podem ser usados pela nova versão do seu aplicativo. A versão antiga do aplicativo continuará a ser executada usando o conjunto anterior de artefatos de armazenamento.

A implantação lado a lado é a técnica recomendada para implantar novas versões de seus aplicativos de funções.

Observação

Essas diretrizes para a estratégia de implantação lado a lado usam termos específicos do Armazenamento do Microsoft Azure, mas se aplica geralmente a todos os provedores de armazenamento Durable Functions com suporte.

Slots de implantação

Ao fazer implantações lado a lado no Azure Functions ou no Serviço de Aplicativo do Azure, recomendamos que você implante a nova versão do aplicativo de funções em um novo slot de implantação. Os slots de implantação permitem executar simultaneamente várias cópias do seu aplicativo de funções, com apenas uma delas como o slot de produção ativo. Quando você estiver pronto para expor a nova lógica de orquestração para sua infraestrutura existente, isso pode ser tão simples quanto colocar a nova versão no slot de produção.

Observação

Essa estratégia funciona melhor quando você usa gatilhos HTTP e webhook para funções de orquestrador. Para gatilhos não HTTP, como filas ou Hubs de Eventos, a definição do gatilho deve derivar de uma configuração de aplicativo que é atualizada como parte da operação de troca.

Próximas etapas