Performance and scale in Durable Functions (Azure Functions) (Desempenho e dimensionamento no Durable Functions [Funções do Azure])

Para otimizar o desempenho e a escalabilidade, é importante entender as características exclusivas de dimensionamento das funções duráveis. Neste artigo, explicamos como os trabalhadores são dimensionados com base na carga e como se pode ajustar os vários parâmetros.

Dimensionamento do trabalhador

Um benefício fundamental do conceito de hub de tarefas é que o número de trabalhadores que processam itens de trabalho do hub de tarefas pode ser ajustado continuamente. Em particular, os aplicativos podem adicionar mais trabalhadores (scale-out) se o trabalho precisar ser processado mais rapidamente e podem remover trabalhadores (scale-in) se não houver trabalho suficiente para manter os trabalhadores ocupados. É até possível dimensionar para zero se o hub de tarefas estiver completamente ocioso. Quando dimensionado para zero, não há trabalhadores; Apenas o controlador de escala e o armazenamento precisam permanecer ativos.

O diagrama a seguir ilustra esse conceito:

Diagrama de dimensionamento do trabalhador

Dimensionamento automático

Tal como acontece com todas as Funções do Azure em execução nos planos Consumo e Elastic Premium, as Funções Duráveis suportam o dimensionamento automático através do controlador de escala do Azure Functions. O Controlador de Escala monitoriza o tempo que as mensagens e tarefas têm de esperar antes de serem processadas. Com base nessas latências, ele pode decidir se adiciona ou remove trabalhadores.

Nota

A partir do Durable Functions 2.0, os aplicativos de função podem ser configurados para serem executados em pontos de extremidade de serviço protegidos por VNET no plano Elastic Premium. Nessa configuração, as Funções Duráveis acionam solicitações de escala de início em vez do Controlador de Escala. Para obter mais informações, consulte Monitoramento de escala de tempo de execução.

Em um plano premium, o dimensionamento automático pode ajudar a manter o número de trabalhadores (e, portanto, o custo operacional) aproximadamente proporcional à carga que o aplicativo está enfrentando.

Utilização da CPU

As funções do orquestrador são executadas em um único thread para garantir que a execução possa ser determinística em muitas repetições. Devido a essa execução de thread único, é importante que os threads de função do orquestrador não executem tarefas intensivas de CPU, façam E/S ou bloqueiem por qualquer motivo. Qualquer trabalho que possa exigir E/S, bloqueio ou vários threads deve ser movido para funções de atividade.

As funções de atividade têm os mesmos comportamentos que as funções acionadas por filas regulares. Eles podem fazer E/S com segurança, executar operações intensivas de CPU e usar vários threads. Como os gatilhos de atividade são sem monitoração de estado, eles podem ser dimensionados livremente para um número ilimitado de VMs.

As funções de entidade também são executadas em um único thread e as operações são processadas uma de cada vez. No entanto, as funções de entidade não têm quaisquer restrições sobre o tipo de código que pode ser executado.

Tempos limite de função

As funções de atividade, orquestrador e entidade estão sujeitas aos mesmos tempos limite de função que todas as Funções do Azure. Como regra geral, as Funções Duráveis tratam os tempos limite de função da mesma forma que as exceções não tratadas geradas pelo código do aplicativo.

Por exemplo, se uma atividade atingir o tempo limite, a execução da função será registrada como uma falha e o orquestrador será notificado e manipulará o tempo limite como qualquer outra exceção: novas tentativas ocorrerão se especificado pela chamada ou um manipulador de exceção poderá ser executado.

Lote de operação da entidade

Para melhorar o desempenho e reduzir custos, um único item de trabalho pode executar um lote inteiro de operações de entidade. Nos planos de consumo, cada lote é então cobrado como uma única execução de função.

Por padrão, o tamanho máximo do lote é 50 para planos de consumo e 5000 para todos os outros planos. O tamanho máximo do lote também pode ser configurado no arquivo host.json . Se o tamanho máximo do lote for 1, o processamento em lote será efetivamente desativado.

Nota

Se as operações de entidades individuais demorarem muito tempo a executar, pode ser benéfico limitar a dimensão máxima do lote para reduzir o risco de tempos limite de funcionamento, em especial nos planos de consumo.

Cache de instância

Geralmente, para processar um item de trabalho de orquestração, um trabalhador tem que

  1. Procure a história da orquestração.
  2. Reproduza o código do orquestrador usando o histórico.

Se o mesmo trabalhador estiver processando vários itens de trabalho para a mesma orquestração, o provedor de armazenamento poderá otimizar esse processo armazenando em cache o histórico na memória do trabalhador, o que elimina a primeira etapa. Além disso, ele pode armazenar em cache o orquestrador de execução intermediária, o que elimina a segunda etapa, a repetição do histórico, também.

O efeito típico do cache é a redução da E/S em relação ao serviço de armazenamento subjacente e a melhoria geral da taxa de transferência e da latência. Por outro lado, o cache aumenta o consumo de memória no trabalhador.

Atualmente, o cache de instância é suportado pelo provedor de Armazenamento do Azure e pelo provedor de armazenamento Netherite. A tabela abaixo fornece uma comparação.

Provedor de armazenamento do Azure Provedor de armazenamento Netherite Provedor de armazenamento MSSQL
Cache de instância Suportado
(Somente trabalhador em processo .NET)
Suportado Não suportado
Configuração padrão Disabled Ativados n/d
Mecanismo Sessões Prolongadas Cache de instâncias n/d
Documentação Ver Sessões alargadas Consulte Cache de instâncias n/d

Gorjeta

O cache pode reduzir a frequência com que os históricos são reproduzidos, mas não pode eliminar completamente a repetição. Ao desenvolver orquestradores, é altamente recomendável testá-los em uma configuração que desative o cache. Esse comportamento de repetição forçada pode ser útil para detetar violações de restrições de código de função do orquestrador no momento do desenvolvimento.

Comparação de mecanismos de cache

Os provedores usam mecanismos diferentes para implementar o cache e oferecem parâmetros diferentes para configurar o comportamento do cache.

  • As sessões estendidas, conforme usadas pelo provedor de Armazenamento do Azure, mantêm os orquestradores de execução intermediária na memória até ficarem ociosos por algum tempo. Os parâmetros para controlar este mecanismo são extendedSessionsEnabled e extendedSessionIdleTimeoutInSeconds. Para obter mais detalhes, consulte a seção Sessões estendidas da documentação do provedor de Armazenamento do Azure.

Nota

As sessões estendidas são suportadas apenas no trabalhador em processo do .NET.

  • O cache de instâncias, conforme usado pelo provedor de armazenamento Netherite, mantém o estado de todas as instâncias, incluindo seus históricos, na memória do trabalhador, enquanto controla a memória total usada. Se o tamanho do cache exceder o limite configurado pelo InstanceCacheSizeMB, os dados de instância usados menos recentemente serão removidos. Se CacheOrchestrationCursors estiver definido como true, o cache também armazenará os orquestradores de execução intermediária juntamente com o estado da instância. Para obter mais detalhes, consulte a seção Cache de instância da documentação do provedor de armazenamento Netherite.

Nota

Os caches de instância funcionam para todos os SDKs de linguagem, mas a CacheOrchestrationCursors opção está disponível apenas para o trabalhador em processo do .NET.

Aceleradores de simultaneidade

Uma única instância de trabalho pode executar vários itens de trabalho simultaneamente. Isso ajuda a aumentar o paralelismo e utilizar os trabalhadores de forma mais eficiente. No entanto, se um trabalhador tentar processar muitos itens de trabalho ao mesmo tempo, ele pode esgotar seus recursos disponíveis, como a carga da CPU, o número de conexões de rede ou a memória disponível.

Para garantir que um trabalhador individual não cometa excessos, pode ser necessário limitar a simultaneidade por instância. Ao limitar o número de funções que estão sendo executadas simultaneamente em cada trabalhador, podemos evitar o esgotamento dos limites de recursos desse trabalhador.

Nota

Os aceleradores de simultaneidade só se aplicam localmente, para limitar o que está sendo processado atualmente por trabalhador. Assim, esses aceleradores não limitam a taxa de transferência total do sistema.

Gorjeta

Em alguns casos, limitar a simultaneidade por trabalhador pode realmente aumentar a taxa de transferência total do sistema. Isso pode ocorrer quando cada trabalhador leva menos trabalho, fazendo com que o controlador de escala adicione mais trabalhadores para acompanhar as filas, o que aumenta a taxa de transferência total.

Configuração dos aceleradores

Os limites de simultaneidade de atividade, orquestrador e função de entidade podem ser configurados no arquivo host.json . As configurações relevantes são durableTask/maxConcurrentActivityFunctions para funções de atividade e durableTask/maxConcurrentOrchestratorFunctions para funções de orquestrador e entidade. Essas configurações controlam o número máximo de funções de orquestrador, entidade ou atividade que são carregadas na memória em um único trabalhador.

Nota

Orquestrações e entidades só são carregadas na memória quando estão processando ativamente eventos ou operações, ou se o cache de instância estiver habilitado. Depois de executar sua lógica e aguardar (ou seja, bater uma await instrução (C#) ou yield (JavaScript, Python) no código de função do orquestrador), eles podem ser descarregados da memória. Orquestrações e entidades que são descarregadas da memória não contam para o maxConcurrentOrchestratorFunctions acelerador. Mesmo que milhões de orquestrações ou entidades estejam no estado "Em execução", elas só contam para o limite de aceleração quando são carregadas na memória ativa. Uma orquestração que agenda uma função de atividade de forma semelhante não conta para o acelerador se a orquestração estiver esperando que a atividade termine de ser executada.

Funções 2.0

{
  "extensions": {
    "durableTask": {
      "maxConcurrentActivityFunctions": 10,
      "maxConcurrentOrchestratorFunctions": 10
    }
  }
}

Funções 1.x

{
  "durableTask": {
    "maxConcurrentActivityFunctions": 10,
    "maxConcurrentOrchestratorFunctions": 10
  }
}

Considerações sobre o tempo de execução da linguagem

O tempo de execução do idioma selecionado pode impor restrições estritas de simultaneidade ou suas funções. Por exemplo, os aplicativos de Função Durável escritos em Python ou PowerShell só podem suportar a execução de uma única função de cada vez em uma única VM. Isso pode resultar em problemas de desempenho significativos se não for cuidadosamente considerado. Por exemplo, se um orquestrador se expande para 10 atividades, mas o tempo de execução da linguagem restringe a simultaneidade a apenas uma função, então 9 das 10 funções de atividade ficarão presas esperando por uma chance de execução. Além disso, essas 9 atividades bloqueadas não poderão ser balanceadas para nenhum outro trabalhador porque o tempo de execução das Funções Duráveis já as terá carregado na memória. Isto torna-se especialmente problemático se as funções de atividade forem de longa duração.

Se o language runtime que você está usando colocar uma restrição na simultaneidade, você deverá atualizar as configurações de simultaneidade de Funções duráveis para corresponder às configurações de simultaneidade do seu language runtime. Isso garante que o tempo de execução das Funções Duráveis não tentará executar mais funções simultaneamente do que o permitido pelo tempo de execução da linguagem, permitindo que quaisquer atividades pendentes sejam balanceadas para outras VMs. Por exemplo, se você tiver um aplicativo Python que restringe a simultaneidade a 4 funções (talvez ele esteja configurado apenas com 4 threads em um único processo de trabalho de linguagem ou 1 thread em 4 processos de trabalho de linguagem), então você deve configurar ambos maxConcurrentOrchestratorFunctions e maxConcurrentActivityFunctions para 4.

Para obter mais informações e recomendações de desempenho para Python, consulte Melhorar o desempenho da taxa de transferência de aplicativos Python no Azure Functions. As técnicas mencionadas nesta documentação de referência do desenvolvedor Python podem ter um impacto substancial no desempenho e na escalabilidade do Durable Functions.

Contagem de partições

Alguns dos provedores de armazenamento usam um mecanismo de particionamento e permitem especificar um partitionCount parâmetro.

Ao usar o particionamento, os trabalhadores não competem diretamente por itens de trabalho individuais. Em vez disso, os itens de trabalho são primeiro agrupados em partitionCount partições. Essas partições são então atribuídas aos trabalhadores. Essa abordagem particionada para a distribuição de carga pode ajudar a reduzir o número total de acessos de armazenamento necessários. Além disso, ele pode habilitar o cache de instâncias e melhorar a localidade porque cria afinidade: todos os itens de trabalho para a mesma instância são processados pelo mesmo trabalhador.

Nota

Os limites de particionamento são dimensionados porque, na maioria das partitionCount vezes, os trabalhadores podem processar itens de trabalho a partir de uma fila particionada.

A tabela a seguir mostra, para cada provedor de armazenamento, quais filas são particionadas e o intervalo permitido e os valores padrão para o partitionCount parâmetro.

Provedor de armazenamento do Azure Provedor de armazenamento Netherite Provedor de armazenamento MSSQL
Mensagens de instância Particionado Particionado Não particionado
Mensagens de atividade Não particionado Particionado Não particionado
Inadimplência partitionCount 4 12 n/d
Máximo partitionCount 16 32 n/d
Documentação Veja a expansão do Orchestrator Consulte Considerações sobre contagem de partições n/d

Aviso

A contagem de partições não pode mais ser alterada após a criação de um hub de tarefas. Assim, é aconselhável defini-lo para um valor grande o suficiente para acomodar futuros requisitos de dimensionamento para a instância do hub de tarefas.

Configuração da contagem de partições

O partitionCount parâmetro pode ser especificado no arquivo host.json . O trecho host.json exemplo a seguir define a durableTask/storageProvider/partitionCount propriedade (ou durableTask/partitionCount em Durable Functions 1.x) como 3.

Funções duráveis 2.x

{
  "extensions": {
    "durableTask": {
      "storageProvider": {
        "partitionCount": 3
      }
    }
  }
}

Funções duráveis 1.x

{
  "extensions": {
    "durableTask": {
      "partitionCount": 3
    }
  }
}

Considerações para minimizar as latências de invocação

Em circunstâncias normais, os pedidos de invocação (para atividades, orquestradores, entidades, etc.) devem ser processados rapidamente. No entanto, não há garantia sobre a latência máxima de qualquer solicitação de invocação, pois depende de fatores como: o tipo de comportamento de escala do seu Plano do Serviço de Aplicativo, suas configurações de simultaneidade e o tamanho da lista de pendências do seu aplicativo. Como tal, recomendamos investir em testes de esforço para medir e otimizar as latências finais da sua aplicação.

Objetivos de desempenho

Ao planejar o uso de funções duráveis para um aplicativo de produção, é importante considerar os requisitos de desempenho no início do processo de planejamento. Alguns cenários básicos de uso incluem:

  • Execução sequencial de atividades: este cenário descreve uma função de orquestrador que executa uma série de funções de atividade uma após a outra. Ele mais se assemelha ao exemplo de encadeamento de funções.
  • Execução de atividade paralela: Este cenário descreve uma função orquestradora que executa muitas funções de atividade em paralelo usando o padrão Fan-out, Fan-in .
  • Processamento de resposta paralela: Este cenário é a segunda metade do padrão Fan-out, Fan-in . Ele se concentra no desempenho do fan-in. É importante notar que, ao contrário do fan-out, o fan-in é feito por uma única instância de função do orquestrador e, portanto, só pode ser executado em uma única VM.
  • Processamento de eventos externos: este cenário representa uma única instância de função orquestradora que aguarda eventos externos, um de cada vez.
  • Processamento de operações de entidade: este cenário testa a rapidez com que uma única entidade de Contador pode processar um fluxo constante de operações.

Fornecemos números de taxa de transferência para esses cenários na respetiva documentação para os provedores de armazenamento. Em particular:

Gorjeta

Ao contrário da distribuição, as operações de fan-in são limitadas a uma única VM. Se o seu aplicativo usa o padrão de fan-out, fan-in e você está preocupado com o desempenho de fan-in, considere subdividir o fan-out da função de atividade em várias suborquestrações.

Próximos passos