Compartilhar via


Desempenho e escala nas Funções Duráveis (Azure Functions)

Para otimizar o desempenho e escalabilidade, é importante entender as características únicas da colocação em escala nas Funções Duráveis. Neste artigo, explicamos como os trabalhos são dimensionados com base na carga e como é possível ajustar os vários parâmetros.

Dimensionamento de trabalho

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

O diagrama a seguir ilustra esse conceito:

Diagrama de dimensionamento de trabalho

Dimensionamento automático

Assim como ocorre com todas as funções do Azure Functions em execução nos planos de Consumo e Elástico Premium, as Durable Functions dão suporte ao dimensionamento automático por meio do controlador de escala do Azure Functions. O Controlador de Escala monitora por quanto tempo as mensagens e tarefas precisam aguardar antes de serem processadas. Com base nessas latências, ele pode decidir se deseja adicionar ou remover trabalhos.

Observação

A partir do Durable Functions 2.0, os aplicativos de funções podem ser configurados para serem executados em pontos de extremidade de serviço protegidos por VNET no plano Elástico Premium. Nessa configuração, os gatilhos do Durable Functions iniciam solicitações de dimensionamento em vez do Controlador de Escala. Para obter mais informações, confira Monitoramento de escala de runtime.

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

Uso da CPU

As funções do Orchestrator são executadas em um único thread para assegurar que a execução possa ser determinística entre muitas repetições. Devido a essa execução de thread único, é importante que os threads de função do orquestrador não execute tarefas de uso intensivo de CPU, faça E/S ou bloqueie por algum 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 disparadas por filas regulares. Elas podem, de maneira segura, fazer E/S, executar operações com uso intensivo de CPU e usar vários threads. Como os gatilhos de atividade são sem monitoração de estado, eles podem ser expandidos 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 por vez. No entanto, as funções de entidade não têm nenhuma restrição 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 Durable Functions tratam os tempos limite máximos de função da mesma maneira que as exceções sem tratamento 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 tratará do tempo limite, assim como qualquer outra exceção: novas execuções ocorrerão se especificadas pela chamada ou um manipulador de exceção poderá ser executado.

Envio em lote da operação de entidade

Para melhorar o desempenho e reduzir o custo, um único item de trabalho pode executar um lote inteiro de operações de entidade. Em planos de consumo, cada lote é 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 pode ser configurado no arquivo host.json. Se o tamanho máximo do lote for 1, o envio em lotes será desabilitado efetivamente.

Observação

Se as operações de entidade individuais levarem muito tempo para serem executadas, pode ser útil limitar o tamanho máximo do lote para reduzir o risco de tempos limite de função, em particular nos planos de consumo.

Cache de instância

Geralmente, para processar um item de trabalho de orquestração, um trabalhador precisa de ambos

  1. Buscar histórico de orquestração.
  2. Reproduza o código do orquestrador usando o histórico.

Se o mesmo trabalho 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 trabalho, o que elimina a primeira etapa. Além disso, ele também pode armazenar em cache o orquestrador de execução intermediária, o que elimina a segunda etapa, a reprodução do histórico.

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

Atualmente, há suporte para o cache da instância pelo provedor de Armazenamento do Microsoft Azure e pelo provedor de armazenamento Netherite. A tabela abaixo fornece uma comparação.

Provedor do Armazenamento do Azure Provedor de armazenamento Netherite Provedor do armazenamento MSSQL
Cache de instância Com suporte
(somente trabalho em processo do .NET)
Com suporte Sem suporte
Configuração padrão Desabilitado habilitado N/D
Mecanismo Sessões estendidas Cache da Instância n/d
Documentação Confira Sessões estendidas Confira Cache da Instância n/d

Dica

O cache pode reduzir a frequência com que as histórias são reproduzidas, mas não pode eliminar completamente a reprodução. Ao desenvolver orquestradores, é altamente recomendável testá-los em uma configuração que desabilita o cache. O comportamento de reprodução forçada pode ser útil para detectar violações de restrições de código de função de orquestrador no tempo de 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 de cache.

  • Sessões estendidas, conforme usado pelo provedor de Armazenamento do Microsoft Azure, mantêm orquestradores de execução intermediária na memória até ficarem ociosos por algum tempo. Os parâmetros para controlar esse 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 Microsoft Azure.

Observação

As sessões estendidas têm suporte apenas no trabalho em processo do .NET.

  • O cache de instância, conforme usado pelo provedor de armazenamento Netherite, mantém o estado de todas as instâncias, incluindo seus históricos, na memória do trabalho, mantendo o controle da memória total usada. Se o tamanho do cache exceder o limite configurado por InstanceCacheSizeMB, os dados de instância menos usados recentemente serão removidos. Se CacheOrchestrationCursors estiver definido como true, o cache também armazenará os orquestradores de execução intermediária junto 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.

Observação

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

Restrições 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 trabalhos com mais eficiência. No entanto, se um trabalho tentar processar muitos itens de trabalho ao mesmo tempo, poderá esgotar os 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 trabalho individual não se comprometa demais, talvez seja necessário limitar a simultaneidade por instância. Limitando o número de funções que são executadas simultaneamente em cada trabalho, podemos evitar esgotar os limites de recursos nesse trabalho.

Observação

As limitações de simultaneidade se aplicam somente localmente, para limitar o que está sendo processado atualmente por trabalho. Portanto, essas limitações não limitam a taxa de transferência total do sistema.

Dica

Em alguns casos, a limitação da simultaneidade por trabalho pode realmente aumentar a taxa de transferência total do sistema. Isso pode ocorrer quando cada trabalho toma menos trabalho, fazendo com que o controlador de escala adicione mais trabalhos para acompanhar as filas, o que aumenta a taxa de transferência total.

Configuração de limitações

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

Observação

Orquestrações e entidades são carregadas na memória apenas quando estão processando ativamente eventos ou operações, ou se o cache de instância está habilitado. Depois de executar a lógica delas e aguardar (ou seja, atingir uma instrução await (C#) ou yield (JavaScript, Python) no código de função do orquestrador), elas são descarregadas da memória. Orquestrações e entidades que são descarregadas da memória não contam para a limitação de maxConcurrentOrchestratorFunctions. Mesmo se milhões de orquestrações ou entidades estiverem no estado "Em execução", elas só contarão para a limitação se estiverem carregadas na memória ativa. De modo semelhante, uma orquestração que agenda uma função de atividade não conta para a limitação se a orquestração está esperando o término da execução da atividade.

Funções 2.0

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

Funções 1.x

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

Considerações de runtime da linguagem de programação

O runtime da linguagem selecionada pode impor restrições de simultaneidade estritas às suas funções. Por exemplo, aplicativos de Durable Function escritos em Python ou PowerShell podem dar suporte apenas à execução de uma função por vez em apenas uma VM. Se isso não for contabilizado cuidadosamente, poderá resultar em problemas de desempenho significativos. Por exemplo, se um orquestrador efetuar fan-out para dez atividades, mas o runtime da linguagem restringir a simultaneidade a apenas uma função, nove das dez funções de atividade ficarão paralisadas, aguardando uma chance de execução. Além disso, essas nove atividades paralisadas não poderão ter balanceamento de carga para nenhum outro trabalho, pois já terão sido carregadas na memória pelo runtime do Durable Functions. Isso se torna especialmente problemático se as funções de atividade são de execução longa.

Se o runtime da linguagem de programação que você está usando colocar uma restrição na simultaneidade, você deverá atualizar as configurações de simultaneidade do Durable Functions para corresponder às configurações de simultaneidade do runtime da linguagem de programação. Isso garante que o runtime do Durable Functions não tente executar mais funções simultâneas do que permitido pelo runtime da linguagem de programação, permitindo que o balanceamento de carga de atividades pendentes seja realizado para outras VMs. Por exemplo, se você tiver um aplicativo Python que restrinja a simultaneidade a quatro funções (talvez a configuração dele seja com apenas quatro threads em apenas um processo de trabalho de linguagem de programação ou um thread em quatro processos de trabalho de linguagem de programação), você deverá configurar maxConcurrentOrchestratorFunctions e maxConcurrentActivityFunctions para 4.

Para obter mais informações e recomendações de desempenho para Python, confira Aprimorar 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 do Python podem ter um impacto substancial sobre o desempenho e escalabilidade do Durable Functions.

Contagem de partições

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

Ao usar o particionamento, os trabalhadores não competem diretamente por itens de trabalho individuais. Em vez disso, os itens de trabalho são agrupados primeiro em partições partitionCount. Essas partições são atribuídas aos trabalhos. 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, ela pode habilitar o cache de instância e aprimorar a localidade porque cria afinidade: todos os itens de trabalho para a mesma instância são processados pelo mesmo trabalho.

Observação

Os limites de particionamento são dimensionados porque, na maioria dos trabalhos partitionCount, é possível processar itens de trabalho 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 parâmetro partitionCount.

Provedor do Armazenamento do Azure Provedor de armazenamento Netherite Provedor do armazenamento MSSQL
Mensagens de instância Partitioned Partitioned Não particionado
Mensagens de atividade Não particionado Partitioned Não particionado
Padrão partitionCount 4 12 n/d
Máximo partitionCount 16 32 n/d
Documentação Confira Expansão do orquestrador Confira Considerações sobre a 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. Portanto, é aconselhável defini-la como um valor grande o suficiente para acomodar os requisitos futuros de expansão para a instância do hub de tarefas.

Configuração da contagem de partições

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

Durable Functions 2.x

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

Durable Functions 1.x

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

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

Em circunstâncias normais, as solicitações de invocação (para atividades, orquestradores, entidades etc.) devem ser processadas rapidamente. No entanto, não há garantia sobre a latência máxima de qualquer solicitação de invocação, pois ela depende de fatores como: o tipo de comportamento de escala do Plano do Serviço de Aplicativo, as configurações de simultaneidade e o tamanho da lista de pendências do aplicativo. Dessa forma, recomendamos investir em testes de estresse para medir e otimizar as latências final do aplicativo.

Destinos de desempenho

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

  • Execução de atividades sequenciais: este cenário descreve uma função do orquestrador que executa uma série de funções de atividade um após o outro. Ele é bastante semelhante ao exemplo de Encadeamento de função.
  • Execução de atividade paralela: este cenário descreve uma função do orquestrador que executa muitas funções de atividades em paralelo usando o padrão Fan-out, Fan-in.
  • Resposta de processamento paralelo: esse cenário é a segunda metade do padrão Fan-out, Fan-in. Se concentra no desempenho de fan-in. É importante observar que ao contrário do tipo fan-out, fan-in é feito por uma instância de função do orchestrator único e, portanto, só pode executar em uma única VM.
  • Processamento de eventos externos: esse cenário representa uma instância de função do orquestrador único que espera eventos externos, um de cada vez.
  • Processamento de operação de entidade: esse 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 respectiva documentação para os provedores de armazenamento. Especialmente:

Dica

Ao contrário do tipo fan-out, as operações de consolidação são limitadas a uma única VM. Se seu aplicativo usa o padrão fan-out, fan-in e você estiver preocupado sobre o desempenho de fan-in, divida abaixo a divisão da função de atividade em várias sub-orquestrações.

Próximas etapas