Persistência e serialização de dados em Durable Functions para Azure Functions

O runtime de Durable Functions persiste automaticamente parâmetros de função, valores retornados e outros estados no task hub para fornecer uma execução confiável. No entanto, a quantidade e a frequência dos dados persistidos para o armazenamento durável podem afetar o desempenho do aplicativo e os custos de transação de armazenamento. Dependendo do tipo de dados que seu aplicativo armazena, a retenção de dados e as políticas de privacidade também podem precisar ser consideradas.

Este artigo explica quais dados são persistentes, como lidar com grandes cargas e dados confidenciais e como personalizar a serialização para cada idioma com suporte.

Neste artigo:

Conteúdo do hub de tarefas

Os hubs de tarefas armazenam o estado atual das instâncias e todas as mensagens pendentes:

  • Os estados da instância armazenam o status atual e o histórico de uma instância. Para instâncias de orquestração, esse estado inclui o estado de runtime, o histórico de orquestração, as entradas, as saídas e o status personalizado. Para instâncias de entidade, inclui o estado da entidade.
  • As mensagens armazenam as entradas ou as saídas da função, as cargas dos eventos e os metadados que são usados para fins internos, como roteamento e correlação de ponta a ponta.

As mensagens serão excluídas após processadas, mas os estados de instância persistirão, a menos que sejam excluídos explicitamente pelo aplicativo ou por um operador. Em particular, um histórico de orquestração permanece armazenado mesmo após a conclusão da orquestração.

Para obter um exemplo de como os estados e as mensagens representam o progresso de uma orquestração, consulte o exemplo de execução do hub de tarefas.

Onde e como os estados e as mensagens serão representados no armazenamento dependem do provedor de armazenamento. O provedor padrão do Durable Functions é Armazenamento do Azure, que persiste dados em filas, tabelas e blobs em uma conta Armazenamento do Azure especificada.

Tipos de dados serializados e persistentes

A seguinte lista mostra os diferentes tipos de dados que serão serializados e persistidos quando os recursos das Durable Functions forem usados:

  • Todas as entradas e saídas de orquestrador, atividade e funções de entidade, incluindo quaisquer IDs e exceções sem tratamento
  • Nomes de funções do Orchestrator, da atividade e da entidade
  • Nomes e cargas de eventos externos
  • Conteúdos de status de orquestração personalizados
  • Mensagens de término da orquestração
  • Conteúdos de temporizador duráveis
  • URLs de solicitação e resposta de HTTP duráveis, cabeçalhos e conteúdos
  • Cargas de invocação de entidade e de sinalização
  • Conteúdos de estado de entidade

Para obter diretrizes sobre como gerenciar o tamanho da carga e proteger itens confidenciais nesta lista, consulte as seções a seguir.

Mantenha as entradas e saídas do Durable Functions pequenas

Você poderá encontrar problemas de memória se fornecer entradas e saídas grandes de e para APIs do Durable Functions. Entradas e saídas são serializadas no histórico de orquestração, o que significa que grandes volumes de dados podem, ao longo do tempo, contribuir significativamente para o crescimento ilimitado do histórico. Esse crescimento corre o risco de causar exceções de memória durante a reprodução.

Para atenuar o impacto de entradas e saídas grandes, você pode:

  • Delegar trabalho a sub-orquestradores para balancear a carga da memória de histórico entre vários orquestradores, mantendo o volume de memória de históricos individuais pequeno.
  • Armazene dados grandes no armazenamento externo (como Armazenamento de Blobs do Azure) e passe identificadores leves que permitem recuperar esses dados dentro das funções de atividade quando necessário.

Se você usar o Agendador de Tarefas Duráveis, também poderá usar suporte de carga grande para descarregar cargas maiores no Armazenamento de Blobs do Azure.

Dica

A melhor prática para lidar com dados grandes é mantê-los no armazenamento externo e materializar esses dados somente dentro de atividades, quando necessário.

Trabalhar com dados confidenciais

Entradas e saídas (incluindo exceções) de e para as APIs de Durable Functions são armazenadas de forma durável no provedor de armazenamento de sua escolha. Se essas entradas, saídas ou exceções contiverem dados confidenciais (como segredos, cadeias de conexão ou informações de identificação pessoal), qualquer pessoa com acesso de leitura aos recursos do provedor de armazenamento poderá obtê-los.

Para lidar com dados confidenciais com segurança, busque esses dados a partir de funções de atividade, seja do Azure Key Vault ou de variáveis de ambiente, e nunca comunique esses dados diretamente para ou de orquestradores ou entidades. Essa abordagem ajuda a impedir que dados confidenciais vazem em seus recursos de armazenamento.

Da mesma forma, o acesso de escrita aos recursos de armazenamento deve ser rigorosamente controlado, pois dados adulterados no armazenamento podem alterar o comportamento da orquestração. Para obter mais informações sobre como proteger o armazenamento do hub de tarefas, consulte Proteger o armazenamento do hub de tarefas.

Dica

Essa orientação também se aplica à API do orquestrador CallHttp, que persiste seus conteúdos de solicitação e resposta no armazenamento. Se os pontos de extremidade HTTP de destino exigirem autenticação, implemente a chamada HTTP dentro de uma atividade ou use o suporte integrado de identidade gerenciada oferecido pelo CallHttp, que não armazena credenciais.

Note

Evite registrar em log dados que contenham segredos, pois qualquer pessoa com acesso de leitura aos seus logs (por exemplo, no Application Insights) pode obter esses segredos.

Criptografia em repouso

Ao usar o provedor de Armazenamento do Azure, todos os dados são criptografados automaticamente em repouso. No entanto, qualquer pessoa com acesso à conta de armazenamento pode ler os dados em sua forma não criptografada. Caso você precise obter uma proteção mais forte para os dados confidenciais, considere, primeiro, a criptografia dos dados usando suas chaves de criptografia para que os dados sejam persistidos no respectivo formato previamente criptografado.

Como alternativa, os usuários do .NET têm a opção de implementar provedores de serialização personalizados que fornecem criptografia automática. Um exemplo de serialização personalizada com criptografia pode ser encontrado em este exemplo do GitHub.

Note

Se você decidir implementar a criptografia no nível do aplicativo, lembre-se de que as orquestrações e entidades podem existir por períodos indefinidos de tempo. Isso é importante quando se trata de girar suas chaves de criptografia porque uma orquestração ou entidades podem ser executadas por mais tempo do que a política de rotação de chaves. Se ocorrer uma rotação de chaves, a chave usada para criptografar seus dados poderá não estar mais disponível para descriptografá-la na próxima vez que sua orquestração ou entidade for executada. Portanto, a criptografia personalizada é recomendada somente quando as orquestrações e entidades devem ser executadas por períodos relativamente curtos de tempo.

Proteger o armazenamento do hub de tarefas

O back-end de armazenamento que hospeda o hub de tarefas é um limite de confiança crítico. O Framework de Tarefas Duráveis confia nos dados que lê do armazenamento durante a reexecução da orquestração e o processamento de mensagens. Qualquer pessoa com acesso de gravação ao armazenamento do hub de tarefas pode manipular o estado de orquestração, as mensagens pendentes ou as cargas úteis armazenadas. Isso pode alterar o comportamento do aplicativo, disparar ações não intencionais ou obter a execução remota de código no contexto do aplicativo de funções.

Importante

Não exponha suas credenciais de armazenamento do hub de tarefas nem conceda acesso de gravação a partes não confiáveis. O acesso de gravação ao armazenamento do hub de tarefas pode ser usado para alterar o comportamento do aplicativo, inclusive permitindo a execução arbitrária de código.

Responsabilidade compartilhada

Proteger o back-end de armazenamento é sua responsabilidade, o mesmo que proteger qualquer banco de dados que armazene o estado ou o código do aplicativo. A Estrutura de Tarefas Duráveis não executa a verificação de integridade em dados armazenados, portanto, ela depende dos controles de acesso da camada de armazenamento para evitar modificações não autorizadas.

Se você usar o Agendador de Tarefas Duráveis, o back-end de armazenamento será totalmente gerenciado e protegido pelo serviço, usando a autenticação de identidade gerenciada e o RBAC (controle de acesso baseado em função). Para back-ends de armazenamento do tipo traga seu próprio (BYO), como Armazenamento do Azure, MSSQL ou Netherite, você mesmo deve proteger os recursos de armazenamento subjacentes.

Note

Não compartilhe um único hub de tarefas entre locatários não confiáveis. Um hub de tarefas não impõe limites de acesso entre seus usuários, portanto, qualquer locatário que possa ler ou gravar no hub de tarefas pode afetar todas as orquestrações e entidades dentro dele. Da mesma forma, não confie em hubs de tarefas separados dentro do mesmo backend como um limite de segurança. Embora o Agendador de Tarefas Duráveis dê suporte ao RBAC com escopo para hubs de tarefas individuais, os controles de rede, como listas de permissões de IP e pontos de extremidade privados, se aplicam somente no nível do agendador, portanto, os hubs de tarefas dentro de um agendador não são um limite de isolamento de segurança. O mesmo vale para provedores BYO de armazenamento—qualquer locatário com acesso à conta de armazenamento ou ao banco de dados pode acessar todos os hubs de tarefas nessa infraestrutura de back-end. Quando você precisar de isolamento de segurança entre locatários, provisione a infraestrutura separada para cada locatário: contas de armazenamento ou bancos de dados separados para provedores BYO ou instâncias separadas do Agendador de Tarefas Duráveis.

Lista de verificação de fortalecimento do armazenamento

Aplique as seguintes práticas recomendadas para proteger o armazenamento do hub de tarefas:

  • Use conexões baseadas em identidade em vez de cadeias de conexão com chaves de armazenamento. As identidades gerenciadas fornecem controle de acesso refinado e eliminam o risco de vazamento de credenciais. Consulte Configurar uma identidade gerenciada para Durable Functions.
  • Aplique funções RBAC com privilégios mínimos. Conceda apenas as permissões mínimas necessárias. Evite conceder acesso amplo à conta de armazenamento a usuários ou serviços que não precisam dela.
  • Restrinja o acesso à rede à sua conta de armazenamento usando pontos de extremidade privados ou pontos de extremidade de serviço. Isso impede o acesso não autorizado em nível de rede aos dados do hub de tarefas.
  • Monitore o acesso ao armazenamento habilitando os logs de recursos do Azure Monitor para sua conta de armazenamento, especialmente a categoria de log StorageWrite. Encaminhe esses logs para um destino fora da conta de armazenamento monitorada, como Log Analytics, para que eles não possam ser adulterados. Consulte Logs de armazenamento.
  • Gire as credenciais regularmente se você usar cadeias de conexão. Trate as chaves da conta de armazenamento com o mesmo cuidado que qualquer outra credencial de alto privilégio.
  • Considere um back-end de armazenamento gerenciado. O Agendador de Tarefas Duráveis lida com a segurança de armazenamento automaticamente, incluindo criptografia, autenticação e isolamento de rede.

Personalizar serialização e desserialização

As opções de personalização de serialização variam de acordo com o idioma. Selecione a guia idioma para ver as opções disponíveis.

Lógica de serialização padrão

O Durable Functions para .NET em processo usa internamente o Json.NET para serializar dados de orquestração e entidade para JSON. As configurações padrão do Json.NET usadas são:

Entradas, saídas e estado:

JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.None,
    DateParseHandling = DateParseHandling.None,
}

Exceções:

JsonSerializerSettings
{
    ContractResolver = new ExceptionResolver(),
    TypeNameHandling = TypeNameHandling.Objects,
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
}

Leia a documentação mais detalhada sobre JsonSerializerSettingsaqui.

Personalizar a serialização com atributos de .NET

Durante a serialização, o Json.NET procura vários atributos em classes e propriedades que controlam como os dados são serializados e desserializados no JSON. Se você possui o código-fonte para o tipo de dados passados para as APIs da Durable Functions, considere adicionar esses atributos ao tipo para personalizar a serialização e desserialização.

Personalizar a serialização com injeção de dependência

Os aplicativos de funções que têm como alvo o .NET e são executados no ambiente de execução Functions V3 podem usar Dependency Injection (DI) para personalizar como os dados e exceções são serializados. O código de exemplo a seguir demonstra como usar o DI para substituir as configurações padrão de serialização de Json.NET usando implementações personalizadas das interfaces de serviço IMessageSerializerSettingsFactory e IErrorSerializerSettingsFactory.

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using System.Collections.Generic;

[assembly: FunctionsStartup(typeof(MyApplication.Startup))]
namespace MyApplication
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton<IMessageSerializerSettingsFactory, CustomMessageSerializerSettingsFactory>();
            builder.Services.AddSingleton<IErrorSerializerSettingsFactory, CustomErrorSerializerSettingsFactory>();
        }

        /// <summary>
        /// A factory that provides the serialization for all inputs and outputs for activities and
        /// orchestrations, as well as entity state.
        /// </summary>
        internal class CustomMessageSerializerSettingsFactory : IMessageSerializerSettingsFactory
        {
            public JsonSerializerSettings CreateJsonSerializerSettings()
            {
                // Return your custom JsonSerializerSettings here
            }
        }

        /// <summary>
        /// A factory that provides the serialization for all exceptions thrown by activities
        /// and orchestrations
        /// </summary>
        internal class CustomErrorSerializerSettingsFactory : IErrorSerializerSettingsFactory
        {
            public JsonSerializerSettings CreateJsonSerializerSettings()
            {
                // Return your custom JsonSerializerSettings here
            }
        }
    }
}

Próximas Etapas