Persistência de dados e serialização em Durable Functions for Funções do Azure

O runtime do Durable Functions persiste automaticamente os parâmetros de função, valores de retorno e outros estados no hub de tarefas para garantir uma execução fiá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, as políticas de retenção de dados e privacidade também podem precisar ser consideradas.

Este artigo explica que dados são mantidos, como lidar com grandes cargas úteis e dados sensíveis, e como personalizar a serialização para cada linguagem suportada.

Neste artigo:

Conteúdo do Task Hub

Os hubs de tarefas armazenam o estado atual das instâncias e quaisquer 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 tempo de execução, o histórico de orquestração, entradas, saídas e status personalizado. Para instâncias de entidade, inclui o estado da entidade.
  • As mensagens armazenam entradas ou saídas de funções, cargas úteis de eventos e metadados que são usados para fins internos, como roteamento e correlação de ponta a ponta.

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

Para obter um exemplo de como estados e 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 são representados no armazenamento dependem do provedor de armazenamento. O provedor padrão do Durable Functions é o Armazenamento do Azure, que persiste dados em filas, tabelas e blobs numa conta de Armazenamento do Azure que especificar.

Tipos de dados que são serializados e persistem

A lista seguinte mostra os diferentes tipos de dados que serão serializados e persistirão ao utilizar funcionalidades do Durable Functions:

  • Todas as entradas e saídas das funções de orquestrador, de atividade e de entidade, incluindo quaisquer IDs e exceções não tratadas.
  • Nomes de funções de orquestrador, atividade e entidade
  • Nomes de eventos externos e cargas úteis
  • Cargas úteis de estado de orquestração personalizadas
  • Mensagens de encerramento de orquestração
  • Cargas úteis duráveis do temporizador
  • Solicitações e respostas HTTP duráveis: URLs, cabeçalhos e cargas úteis
  • Cargas úteis de entidades para chamada e sinalização
  • Cargas úteis do estado de entidade

Para orientações sobre a gestão do tamanho da carga útil e proteção de itens sensíveis nesta lista, consulte as secções seguintes.

Mantenha as entradas e saídas das Durable Functions pequenas

Você pode enfrentar problemas de memória se fornecer grandes entradas e saídas para as APIs de Funções Duráveis. As entradas e saídas são serializadas no histórico de orquestração, o que significa que grandes cargas úteis podem, ao longo do tempo, contribuir significativamente para o crescimento ilimitado do histórico. Este crescimento corre o risco de causar exceções de memória durante a repetição.

Para mitigar o impacto de grandes entradas e saídas, pode:

  • Delegar trabalho aos suborquestradores para equilibrar a carga da memória histórica entre múltiplos orquestradores, mantendo a pegada de memória das histórias individuais pequena.
  • Armazene grandes volumes de dados em armazenamento externo (como o Armazenamento de Blobs do Azure) e passe identificadores leves que permitem recuperar esses dados dentro das funções de atividade quando necessário.

Se usares o Durable Task Scheduler, também podes usar suporte para cargas de grandes dimensões para deslocar cargas maiores para o Armazenamento de Blobs do Azure.

Sugestão

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

Trabalho com dados sensíveis

As entradas e saídas (incluindo exceções) para e das APIs Durable Functions são mantidas de forma duradoura no seu fornecedor de armazenamento preferência. Se essas entradas, saídas ou exceções contiverem dados sensíveis (como segredos, cadeias de ligação ou informações pessoais identificáveis), qualquer pessoa com acesso de leitura aos recursos do seu fornecedor de armazenamento pode obtê-los.

Para lidar com dados sensíveis de forma segura, obtém esses dados dentro das funções de atividade a partir do Azure Key Vault ou de variáveis de ambiente, e nunca comuniques esses dados diretamente para ou a partir de orquestradores ou entidades. Esta abordagem ajuda a evitar que dados sensíveis escapem para os seus recursos de armazenamento.

Sugestão

Esta orientação aplica-se também à CallHttp API do orquestrador, que mantém os seus payloads de pedido e resposta em armazenamento. Se os seus endpoints HTTP de destino requerem autenticação, implemente a chamada HTTP dentro de uma atividade, ou use o suporte de identidade gerida incorporado oferecido pelo CallHttp, que não mantém as credenciais no armazenamento.

Observação

Evite registar dados que contenham segredos, pois qualquer pessoa com acesso de leitura aos seus registos (por exemplo, no Application Insights) poderia obter esses segredos.

Encriptação em repouso

Ao utilizar o fornecedor Armazenamento do Azure, todos os dados são automaticamente encriptados em repouso. No entanto, qualquer pessoa com acesso à conta de armazenamento pode ler os dados em sua forma não criptografada. Se você precisar de uma proteção mais forte para dados confidenciais, considere primeiro criptografar os dados usando suas próprias chaves de criptografia para que os dados persistam em sua forma pré-criptografada.

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 neste exemplo do GitHub.

Observação

Se você decidir implementar a criptografia no nível do aplicativo, esteja ciente de que orquestrações e entidades podem existir por períodos indefinidos de tempo. Isso é importante quando for necessário rodar as suas chaves de criptografia, porque uma orquestração ou entidades podem ser executadas por mais tempo do que a sua política de rotação de chaves. Se ocorrer uma rotação de chaves, a chave usada para encriptar os seus dados pode não estar mais disponível para desencriptá-los na próxima vez que a sua orquestração ou entidade seja executada. A encriptação personalizada é, portanto, recomendada apenas quando se espera que orquestrações e entidades corram por períodos relativamente curtos.

Personalizar serialização e desserialização

As opções de personalização de serialização variam consoante a língua. Selecione o separador de idioma para ver as opções disponíveis.

Lógica de serialização padrão

Durable Functions para .NET em processo utilizam internamente o Json.NET para serializar dados de orquestração e de entidades em JSON. As definiçõ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 aqui JsonSerializerSettings.

Personalizar serialização com atributos .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 a partir do JSON. Se for proprietário do código-fonte do tipo de dados passado para as APIs do Durable Functions, considere adicionar estes atributos ao tipo para personalizar a serialização e a desserialização.

Personalizar serialização com Injeção de Dependências

As aplicações de funções destinadas ao .NET e que funcionam no ambiente de execução do Functions V3 podem utilizar Injeção de Dependência (DI) para personalizar como os dados e as exceções são serializados. O código de exemplo a seguir demonstra como usar DI para substituir as configurações padrão de serialização do Json.NET utilizando 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
            }
        }
    }
}

Passos seguintes