Desafios de versionamento e estratégias de mitigação em Durable Functions

O versionamento em Durable Functions é essencial porque as funções são inevitavelmente adicionadas, removidas e alteradas ao longo da vida útil de uma aplicação. Durable Functions permite-te encadear funções de formas que antes não eram possíveis, e esta encadeamento afeta a forma como lidas com a versionação.

Este artigo ajuda-o a:

Comparação rápida de estratégias

Se já sabe que a sua mudança é disruptiva, use esta tabela para escolher uma estratégia de mitigação.

Estratégia Melhor para Detalhes
Versionamento de orquestração (recomendado) A maioria das aplicações tem alterações que causam quebras de compatibilidade. Funcionalidade de runtime integrada, funciona com qualquer sistema de armazenamento. Saltar para a secção
Implantações lado a lado Aplicações que não conseguem usar versionamento de orquestração, ou que precisam de isolamento total através de centros de tarefas separados ou contas de armazenamento. Saltar para a secção
Parar todas as instâncias em execução Prototipagem e desenvolvimento local onde a perda de orquestração em voo é aceitável. Saltar para a secção

Sugestão

Se estiver à procura da funcionalidade de versionamento de orquestração incorporada que fornece isolamento automático de versões ao nível de execução, consulte Versionamento de orquestração.

Importante

Antes de implementar, verifique se a sua alteração é uma alteração crítica.

  • Mudaste o nome, tipo de entrada ou tipo de saída de uma função de atividade ou entidade?
  • Adicionaste, removeste ou reordenaste chamadas para atividades, suborquestrações, temporizadores ou eventos externos no código do Orchestrator?
  • Renomeaste ou removeste alguma função que as orquestrações em voo ainda possam chamar?

Se respondeu sim a alguma destas questões, abaixo, use uma das estratégias de mitigação para evitar falhas ao executar orquestrações.

Tipos de alterações disruptivas

Existem vários exemplos de alterações inesperadas. Este artigo discute os tipos mais comuns. O tema principal por trás de todos eles é que as alterações ao código de funções afetam tanto as orquestrações de funções novas como as existentes.

Alterações na assinatura de função de uma 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 fizeres este tipo de alteração numa função de atividade ou entidade, pode quebrar qualquer função de orquestrador que dependa dela. Este comportamento é especialmente verdadeiro para linguagens seguras por tipos. Se você atualizar a função orchestrator para acomodar essa alteração, poderá interromper instâncias existentes em voo.

Como exemplo, considere a seguinte função orquestradora.

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

Esta função pega no resultado de Foo e passa-o para Bar. Assuma que precisa de alterar o valor de retorno do Foo de Booleano para String para suportar uma maior variedade de valores de resultado. O resultado é assim:

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

Esta alteração funciona bem para todas as novas instâncias da função do orquestrador, mas pode quebrar quaisquer instâncias em voo. Por exemplo, considere o caso em que uma instância de orquestração chama uma função chamada Foo, obtém de volta um valor booleano e, em seguida, pontos de verificação. Se a alteração da assinatura for implementada neste ponto, a instância em checkpoint falha imediatamente ao retomar e reexecutar a chamada para Foo. Esta falha ocorre porque o resultado na tabela de histórico é um valor booleano, mas o novo código tenta desserializá-lo para um valor String, resultando em comportamentos inesperados ou até numa exceção em tempo de execução para linguagens seguras por tipos.

Este exemplo é uma das muitas formas pelas quais uma alteração de assinatura de função pode quebrar instâncias existentes. Em geral, se um orquestrador precisa mudar a maneira como chama uma função, então a mudança provavelmente será problemática.

Alterações na lógica do orquestrador

A outra classe de problemas de versionamento surge da alteração do código da função do orquestrador de forma a alterar o caminho de execução das instâncias em voo.

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 assume que queres 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);
}

Esta alteração adiciona uma nova chamada de função entre SendNotificationFoo e Bar. Não existem alterações de assinatura. O problema surge quando uma instância existente retoma da chamada para Bar. Durante a reprodução, se a chamada original para Foo retornou true, então o orquestrador faz uma chamada para SendNotification, que não está no seu histórico de execução. O runtime deteta esta inconsistência e gera um erro de orquestração não determinística 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 duradouras, como criar temporizadores duráveis, esperar por eventos externos ou chamar suborquestrações.

Estratégias de mitigação

Aviso

Implementar alterações críticas sem uma estratégia de mitigação (a abordagem "não fazer nada") pode causar falhas nas orquestrações com erros de orquestração não determinísticos, ficar presas indefinidamente num Running estado, ou provocar falhas ao nível do runtime que degradam o desempenho. Use sempre uma das estratégias seguintes ao introduzir alterações que podem quebrar o funcionamento existente.

Ao contrário das outras estratégias desta secção, a versionação de orquestração é uma funcionalidade de runtime incorporada que proporciona isolamento automático de versões. Não precisa de gerir implantações separadas, centros de tarefas ou contas de armazenamento. Em vez disso, o próprio runtime acompanha a informação de versão e garante que as instâncias de orquestração são processadas por trabalhadores compatíveis.

Com versionamento de orquestração:

  • Cada instância de orquestração recebe uma versão permanentemente associada a ela quando criada.
  • As funções do Orchestrator podem examinar a sua versão e executar ramificações de acordo, mantendo caminhos de código antigo e novo na mesma base de código.
  • Os trabalhadores que executam versões mais recentes das funções do orquestrator podem continuar a executar instâncias de orquestração criadas por versões mais antigas.
  • O tempo de execução impede que os trabalhadores que executam versões antigas das funções do orquestrador executem orquestrações de versões mais recentes.

Esta abordagem requer uma configuração mínima (uma cadeia de versões e uma estratégia de correspondência opcional) e é compatível com qualquer fornecedor de armazenamento. É a estratégia recomendada para aplicações que precisam de suportar alterações disruptivas mantendo implementações sem tempo de inatividade.

Para orientações detalhadas sobre configuração e implementação, consulte Versionamento de orquestração.

Pare todas as instâncias em voo

Outra opção é parar todas as instâncias em voo. Se estiver a usar o fornecedor padrão Armazenamento do Azure para Durable Functions, pare todas as instâncias, limpando o conteúdo das filas internas control-queue e workitem-queue. Alternativamente, pare a aplicação de funções, elimine essas filas e reinicie a aplicação. As filas são recriadas automaticamente assim que a aplicação reinicia. As instâncias de orquestração anteriores podem permanecer no estado "a correr" indefinidamente, mas não enchem os logs com mensagens de falha nem causam danos à aplicação. Esta abordagem é ideal para o desenvolvimento rápido de protótipos, incluindo o desenvolvimento local.

Aviso

Esta abordagem requer acesso direto aos recursos de armazenamento subjacentes e não é adequada para todos os fornecedores de armazenamento suportados pela Durable Functions.

Implantações lado a lado

A forma mais infalível de garantir que as alterações disruptivas são implantadas em segurança é implantá-las em paralelo com as suas versões anteriores. Pode usar qualquer uma das seguintes técnicas:

  • Conta de armazenamento diferente: Implemente todas as atualizações como uma nova aplicação de funções com uma conta de armazenamento diferente. Isto isola completamente o estado da nova versão da versão antiga.
  • Hub de tarefas diferente: Implementar uma nova cópia da aplicação de funções com a mesma conta de armazenamento mas com o nome do hub de tarefas atualizado. Esta abordagem cria novos artefactos de armazenamento para a nova versão, enquanto a versão antiga continua a usar os seus artefactos existentes.

Ao fazer implementações lado a lado no Azure, pode usar slots deployment para executar ambas as versões simultaneamente, com apenas uma como slot ativo production. Quando estiveres pronto para expor a nova lógica de orquestração, troca a nova versão para o slot de produção.

Observação

Esta orientação utiliza termos específicos de Armazenamento do Azure, mas aplica-se geralmente a todos os fornecedores de armazenamento Durable Functions suportados.

Observação

As permutas de slots de implementação funcionam melhor com acopladores HTTP e webhook. Para gatilhos não HTTP como filas ou Event Hubs, a definição do gatilho deve derivar de uma configuração de aplicação que é atualizada como parte da operação de troca.

Passos seguintes