Padrão de transação de compensação

Azure

Quando você usa uma operação de consistência eventual que inclui uma série de etapas, o padrão Transação de Compensação pode ser útil. Especificamente, se uma ou mais etapas falharem, será possível usar o padrão Transação de Compensação para desfazer o trabalho executado pelas etapas. Geralmente, você encontra operações que seguem o modelo de consistência eventual em aplicativos hospedados na nuvem que implementam processos de negócios e fluxos de trabalho complexos.

Contexto e problema

Aplicativos em execução na nuvem com modificam os dados com frequência. Às vezes, esses dados estão distribuídos entre várias fontes de dados em diferentes localizações geográficas. Para evitar a contenção e melhorar o desempenho em um ambiente distribuído, um aplicativo não deve tentar fornecer forte consistência transacional. Em vez disso, o aplicativo deve implementar a consistência eventual. No modelo de consistência eventual, uma operação de negócios típica consiste em uma série de etapas separadas. Embora a operação execute estas etapas, a visão geral do estado do sistema pode ser inconsistente. Mas quando a operação terminar e todas as etapas forem executadas, o sistema deverá se tornar consistente novamente.

O Primer de consistência de dados fornece informações sobre por que as transações distribuídas não são bem dimensionadas. Esse recurso também lista os princípios do modelo de consistência eventual.

Um desafio no modelo de consistência eventual é como tratar uma etapa que falha. Após uma falha, talvez seja necessário desfazer todo o trabalho concluído pelas etapas anteriores da operação. No entanto, nem sempre é possível reverter os dados, porque outras instâncias simultâneas do aplicativo podem tê-los alterado. Mesmo nos casos em que as instâncias simultâneas não alteraram os dados, desfazer uma etapa pode ser mais complexo do que restaurar o estado original. Pode ser necessário aplicar diversas regras específicas do negócio. Por exemplo, consulte o site de viagens descrito na seção Exemplo posteriormente neste artigo.

Se uma operação que implementa a consistência eventual abrange vários armazenamentos de dados heterogêneos, desfazer as etapas na operação exige visitar cada armazenamento de dados por vez. Para evitar que o sistema permaneça inconsistente, você deve desfazer de forma confiável o trabalho executado em cada armazenamento de dados.

Os dados afetados por uma operação que implementa consistência eventual nem sempre são mantidos em um banco de dados. Por exemplo, considere um ambiente de arquitetura orientada a serviços (SOA). Uma operação SOA pode invocar uma ação em um serviço e causar uma alteração no estado mantido por esse serviço. Para desfazer a operação, também é necessário desfazer essa mudança de estado. Esse processo pode envolver invocar o serviço novamente e executar outra ação que reverte os efeitos da primeira.

Solução

A solução é implementar uma transação de compensação. As etapas em uma transação de compensação desfazem os efeitos das etapas na operação original. Uma abordagem intuitiva é substituir o estado atual pelo estado em que o sistema se encontrava no início da operação. Mas uma transação de compensação nem sempre pode adotar essa abordagem, porque ela pode substituir alterações feitas por outras instâncias simultâneas de um aplicativo. Em vez disso, uma transação compensatória deve ser um processo inteligente que leve em conta qualquer trabalho realizado por instâncias simultâneas. Esse processo geralmente é específico do aplicativo, acompanhando a natureza do trabalho executado pela operação original.

Uma abordagem comum é usar um fluxo de trabalho para implementar uma operação eventualmente consistente que exige compensação. À medida que a operação original prossegue, o sistema registra informações sobre cada etapa, inclusive como desfazer o trabalho executado pela etapa. Se a operação falhar em qualquer ponto, o fluxo de trabalho retrocederá pelas etapas concluídas. Em cada etapa, o fluxo de trabalho executa o trabalho que reverte essa etapa.

Dois pontos importantes são:

  • Uma transação compensatória talvez não precise desfazer o trabalho exatamente na ordem inversa da operação original.
  • Talvez seja possível executar algumas das etapas de desfazer em paralelo.

Essa abordagem é semelhante à estratégia do Sagas discutida no blog de Clemens Vasters.

Uma transação de compensação é em si uma operação de consistência eventual; portanto, também pode falhar. O sistema deve ser capaz de retomar a transação de compensação no ponto de falha e continuar. Pode ser necessário repetir uma etapa que falhou; portanto, você deve definir as etapas em uma transação de compensação como comandos idempotentes. Para mais informações, confira Padrões de Idempotência no blog de Jonathan Oliver.

Em alguns casos, a intervenção manual pode ser a única forma de recuperação de uma etapa que falhou. Nessas situações, o sistema deve acionar um alerta e fornecer o máximo possível de informações sobre o motivo da falha.

Problemas e considerações

Considere os seguintes pontos ao decidir como implementar esse padrão:

  • Pode ser difícil determinar quando uma etapa em uma operação que implementa a consistência eventual que falha. Talvez a etapa não falhe imediatamente. Em vez disso, ela pode ser bloqueada. Talvez seja necessário implementar um mecanismo de tempo limite.

  • Não é fácil generalizar a lógica da compensação. Uma transação de compensação é específica do aplicativo. Ele se baseia no aplicativo ter informações suficientes para poder desfazer os efeitos de cada etapa em uma operação com falha.

  • Transações compensatórias nem sempre funcionam. Você deve definir as etapas em uma transação de compensação como comandos idempotentes. Depois disso, as etapas poderão ser repetidas se a própria transação de compensação falhar.

  • A infraestrutura que trata das etapas deve atender aos seguintes critérios:

    • É resiliente na operação original e na transação compensatória.
    • Ele não perde as informações necessárias para compensar uma etapa com falha.
    • Ele monitora de forma confiável o progresso da lógica de compensação.
  • Uma transação de compensação não retorna necessariamente os dados do sistema ao seu estado no início da operação original. Em vez disso, a transação compensa o trabalho que a operação concluiu com êxito antes de falhar.

  • A ordem das etapas da transação de compensação não é necessariamente o oposto das etapas da operação original. Por exemplo, um armazenamento de dados pode ser mais sensível a inconsistências do que outro. As etapas da transação de compensação que desfazem as alterações nesta loja devem ocorrer primeiro.

  • Certas medidas podem ajudar a aumentar a probabilidade de sucesso da atividade global. Especificamente, você pode colocar um bloqueio de curto prazo baseado em tempo limite em cada recurso necessário para concluir uma operação. Você também pode obter esses recursos antecipadamente. Depois, execute o trabalho somente depois de adquirir todos os recursos. Finalize todas as ações antes dos bloqueios expirarem.

  • A lógica de repetição que é mais tolerante que o normal pode ajudar a minimizar as falhas que disparam uma transação de compensação. Se uma etapa em uma operação que implementa a consistência eventual falhar, tente tratar a falha como uma exceção transitória e repita a etapa. Apenas pare a operação e inicie uma transação de compensação se uma etapa falhar repetidamente ou de modo irrecuperável.

  • Ao implementar uma transação compensatória, você enfrenta muitos dos mesmos desafios que enfrenta quando implementa consistência eventual. Para mais informações, confira a seção "Considerações para implementar a consistência eventual" no Primer de consistência de dados.

Quando usar esse padrão

Use este padrão apenas para operações que devem ser desfeitas se elas falharem. Se possível, crie soluções para evitar a complexidade de exigir transações de compensação.

Design de carga de trabalho

Um arquiteto deve avaliar como o padrão Transação de Compensação pode ser usado no design da sua carga de trabalho para abordar os objetivos e princípios abordados nos pilares dos pilares da estrutura bem arquitetada do Azure. Por exemplo:

Pilar Como esse padrão apoia os objetivos do pilar
As decisões de design de confiabilidade ajudam sua carga de trabalho a se tornar resiliente ao mau funcionamento e a garantir que ela se recupere para um estado totalmente funcional após a ocorrência de uma falha. As ações de compensação abordam mau funcionamento em caminhos críticos de carga de trabalho usando processos como reversão direta de alterações de dados, quebra de bloqueios de transações ou até mesmo execução de comportamento nativo do sistema para reverter o efeito.

- RE:02 Fluxos críticos
- RE:09 Recuperação de desastre

Tal como acontece com qualquer decisão de design, considere quaisquer compensações em relação aos objetivos dos outros pilares que possam ser introduzidos com este padrão.

Exemplo

Os clientes usam um site de viagem para reservar itinerários. Um único roteiro pode ser composto por uma série de voos e hotéis. Um cliente que viaja de Seattle para Londres e depois para Paris poderia executar as seguintes etapas ao criar um roteiro:

  1. Reservar um assento no voo F1 de Seattle para Londres.
  2. Reservar um assento no voo F2 de Londres para Paris.
  3. Reservar um assento no voo F3 de Paris para Seattle.
  4. Reserve um quarto no hotel H1 em Londres.
  5. Reserve um quarto no hotel H2 em Paris.

Essas etapas constituem uma operação eventualmente consistente, embora cada uma delas seja uma ação separada. Além de realizar essas etapas, o sistema também deve registrar as operações do contador para desfazer cada etapa. Essa informação é necessária caso o cliente cancele o itinerário. As etapas necessárias para executar as operações de contador podem então ser executadas como uma transação de compensação.

As etapas da transação de compensação podem não ser exatamente o oposto das etapas originais. Além disso, a lógica em cada etapa da transação compensatória deve levar em conta regras específicas do negócio. Por exemplo, o cancelamento de uma reserva de voo pode não dar ao cliente o direito a um reembolso total.

A figura a seguir mostra as etapas de uma transação de longa duração para reservar um itinerário de viagem. Você também pode ver as etapas da transação de compensação que desfazem a transação.

Diagrama que mostra as etapas para criar um itinerário. As etapas da transação de compensação que cancela o itinerário também são mostradas.

Observação

Você poderá executar as etapas da transação de compensação em paralelo, dependendo de como você projeta a lógica de compensação para cada etapa.

Em muitas soluções empresariais, a falha de uma única etapa não exige a reversão do sistema usando uma transação de compensação. Por exemplo, considere o cenário do site de viagem. Suponha que o cliente reserve os voos F1, F2 e F3, mas não consiga reservar um quarto no hotel H1. É preferível oferecer ao cliente um quarto em outro hotel na mesma cidade do que cancelar os voos. O cliente ainda pode decidir cancelar. Nesse caso, a transação compensatória executa e desfaz as reservas dos voos F1, F2 e F3. Mas essa decisão deve ser tomada pelo cliente e não pelo sistema.

Próximas etapas

  • Primer de Consistência de Dados. O padrão de Transações de Compensação geralmente é usado para desfazer operações que implementam o modelo de consistência eventual. Esse primer fornece informações sobre as vantagens e desvantagens da consistência eventual.
  • Padrões de idempotência. Em uma transação compensatória, é melhor usar comandos idempotentes. Esta postagem do blog descreve fatores a serem considerados ao implementar a idempotência.
  • Padrão Supervisor do Agente Agendador. Este artigo descreve como implementar sistemas resilientes que executam operações de negócios que usam recursos e serviços distribuídos. Nesses sistemas, às vezes é necessário usar uma transação de compensação para desfazer o trabalho executado por uma operação.
  • Padrão de repetição. As transações de compensação podem ser computacionalmente exigentes. Você pode tentar minimizar seu uso usando o padrão de repetição para implementar uma política eficaz de repetição de operações com falha.
  • Transações distribuídas do Saga. Este artigo explica como usar o padrão Saga é uma maneira de gerenciar a consistência de dados entre microsserviços em cenários de transação distribuída. O padrão Saga lida com a recuperação de falhas com transações compensatórias.
  • Padrão de Pipes e Filtros. Este artigo descreve o padrão de Pipes e Filtros, que pode ser usado para decompor uma tarefa de processamento complexa em uma série de elementos reutilizáveis. É possível usar o padrão de Pipes e Filtros com o padrão Transação de Compensação como uma alternativa para implementar transações distribuídas.
  • Design de autorrecuperação. Este guia explica como projetar aplicativos de autocorreção. Você pode usar transações compensatórias como parte de uma abordagem de autocorreção.