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.

Tip

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.

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

Tip

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.

Note

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.

Note

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.

Proteja o armazenamento do seu centro de tarefas

O backend de armazenamento que aloja o seu hub de tarefas é uma fronteira de confiança crítica. O Durable Task Framework confia nos dados que lê do armazenamento durante a reprodução de orquestração e o processamento de mensagens. Qualquer pessoa com acesso de escrita ao armazenamento do hub de tarefas pode alterar o estado de orquestração, mensagens pendentes ou cargas úteis armazenadas. Isto pode alterar o comportamento da aplicação, desencadear ações não intencionais ou permitir a execução remota de código no contexto da sua aplicação funcional.

Importante

Não exponha as credenciais de armazenamento do seu Task Hub nem conceda acesso de escrita a partes não confiáveis. O acesso de escrita ao armazenamento do Task Hub pode ser usado para alterar o comportamento da aplicação, incluindo o desencadeamento da execução arbitrária de código.

Responsabilidade partilhada

Proteger o backend de armazenamento é da tua responsabilidade, tal como proteger qualquer base de dados que armazene o estado ou código da aplicação. O Durable Task Framework não realiza verificação de integridade dos dados armazenados, por isso depende dos controlos de acesso da camada de armazenamento para evitar modificações não autorizadas.

Se usar o Durable Task Scheduler, o backend de armazenamento é totalmente gerido e protegido pelo serviço, utilizando autenticação de identidade gerida e controlo de acesso baseado em funções (RBAC). Para backends de armazenamento fornecidos pelo utilizador (BYO), como Armazenamento do Azure, MSSQL ou Netherite, tem de proteger os recursos de armazenamento subjacentes pelos seus próprios meios.

Note

Não partilhe um único hub de tarefas entre inquilinos não confiáveis. Um hub de tarefas não impõe limites de acesso entre os seus utilizadores, por isso qualquer inquilino que consiga ler ou escrever no hub pode afetar todas as orquestrações e entidades dentro dele. Da mesma forma, não confie em centros de tarefas separados dentro do mesmo backend como um limite de segurança. Enquanto o Durable Task Scheduler suporta RBAC com âmbito para cada task hub, controlos de rede como listas de autorização IP e endpoints privados aplicam-se apenas ao nível do scheduler, pelo que os task hubs dentro de um scheduler não constituem uma fronteira de isolamento de segurança. O mesmo se aplica aos fornecedores de armazenamento BYO — qualquer inquilino com acesso à conta ou base de dados de armazenamento pode aceder a todos os centros de tarefas nesse backend. Quando precisar de isolamento de segurança entre inquilinos, providencie infraestruturas separadas para cada inquilino: contas de armazenamento ou bases de dados separadas para fornecedores BYO, ou instâncias separadas do Durable Task Scheduler.

Lista de verificação do endurecimento de armazenamento

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

  • Utilize ligações com base na identidade em vez de cadeias de ligação com chaves de armazenamento. As identidades geridas proporcionam controlo de acesso detalhado e eliminam o risco de fuga de credenciais. Veja Configurar uma identidade gerida para Durable Functions.
  • Aplique funções RBAC de privilégio mínimo. Conceda apenas as permissões mínimas exigidas. Evite conceder acesso a contas de armazenamento amplo a utilizadores ou serviços que não precisem.
  • Restringa o acesso à rede à sua conta de armazenamento usando endpoints privados ou endpoints de serviço. Isto impede o acesso não autorizado ao nível da rede aos dados do centro de tarefas.
  • Monitorize o acesso ao armazenamento ativando registos de recursos Azure Monitor para a sua conta de armazenamento, especialmente a categoria de registo StorageWrite. Encaminhe estes registos para um destino fora da conta de armazenamento monitorizada, como o Log Analytics, para que não possam ser adulterados. Consulte Registos de armazenamento.
  • Roda as credenciais regularmente se usares strings de ligação. Trate as chaves de conta de armazenamento com o mesmo cuidado que qualquer outra credencial de alto privilégio.
  • Considere um backend de armazenamento gerido. O Durable Task Scheduler gere automaticamente a segurança do armazenamento, incluindo encriptação, autenticação e isolamento de rede.

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