Skip to content

Instantly share code, notes, and snippets.

@rponte
Last active April 3, 2024 21:45
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save rponte/9477858e619d8b986e17771c8be7827f to your computer and use it in GitHub Desktop.
Save rponte/9477858e619d8b986e17771c8be7827f to your computer and use it in GitHub Desktop.
THEORY: Distributed Transactions and why you should avoid them (2 Phase Commit , Saga Pattern, TCC, Idempotency etc)

Distributed Transactions and why you should avoid them

  1. Modern technologies won't support it (RabbitMQ, Kafka, etc.);
  2. This is a form of using Inter-Process Communication in a synchronized way and this reduces availability;
  3. All participants of the distributed transaction need to be avaiable for a distributed commit, again: reduces availability.

Implementing business transactions that span multiple services is not straightforward. Distributed transactions are best avoided because of the CAP theorem. Moreover, many modern (NoSQL) databases don’t support them. The best solution is to use the Saga Pattern.

[...]

One of the most well-known patterns for distributed transactions is called Saga. The first paper about it was published back in 1987 and has it been a popular solution since then.

There are a couple of different ways to implement a saga transaction, but the two most popular are:

  • Events/Choreography: When there is no central coordination, each service produces and listen to other service’s events and decides if an action should be taken or not;
  • Command/Orchestration: when a coordinator service is responsible for centralizing the saga’s decision making and sequencing business logic;
@rponte
Copy link
Author

rponte commented Mar 28, 2019

@rponte
Copy link
Author

rponte commented Mar 28, 2019

@rponte
Copy link
Author

rponte commented Mar 28, 2019

@rponte
Copy link
Author

rponte commented Mar 29, 2019

@rponte
Copy link
Author

rponte commented Aug 17, 2020

@rponte
Copy link
Author

rponte commented Jan 2, 2021

TCC - Try, Confirm and Cancel Pattern

Some articles about this simple way of implementing distributed transactions:

@rponte
Copy link
Author

rponte commented Mar 9, 2021

@rponte
Copy link
Author

rponte commented Feb 9, 2022

Existem diversos desafios de implementar sistemas distribuídos sem as facilidades de transações (ACID) - também conhecidos como "transações distribuídas". Alguns pontos importantes sobre lidar com sistemas distribuídos:

  • como não há transação entre serviços distribuídos precisamos investir em retries, at-least once delivery e idempotency para cada chamada remota;
  • ao investir nos itens acima significa que precisamos abraçar consistência eventual em algum nível, afinal os serviços vão estar inconsistentes em algum momento (geralmente entre os erros e retries);
  • vale salientar que implementar reliable retries requer manter estado em algum lugar, ou seja, stateful retries;
  • junto a isso, em sistemas mais complexos se faz necessário ainda compensação (rotinas de cancelamento, informar usuário sobre problemas, oferecer cupons ou descontos, agendar outro voo etc), que muitas vezes é delegada para o negócio ou operacional (intervenção humano);
  • a idéia principal aqui é que não há espaço para "achismo": um serviço A, ao se integrar com outro serviço B, deve ter certeza que sua lógica de negócio foi executada de fato, ele precisa encerrar o ciclo do workflow (seja com sucesso ou erro - codigos HTTP validos por exemplo);

E aqui alguns artigos interessantes:

@rponte
Copy link
Author

rponte commented Mar 21, 2022

Ainda sobre transações distribuídas e garantias de entrega:

What You Want Is What You Don’t: Understanding Trade-Offs in Distributed Messaging

If you’re distributed, forget about ordering and start thinking about commutativity. Forget about guaranteed delivery and start thinking about idempotence.

Outros artigos:

Mensageria e At-Least Once Delivery

Trabalhar com mensageria com garantias At-Least Once significa abraçar seus tradeoffs:

Apart from being duplicated, in-flight messages can get re-ordered. There are many reasons for this to happen, one of the most obvious being message re-delivery mechanism. If a delivery fails, a message is available for reprocessing only after some back-off period. Any other in-flight message can be processed during that time causing the respective order of those messages to change.

When combined, duplication and re-ordering can result, at the receiver side, in many different processing sequences. The only guarantee is that the resulting sequence contains at least one copy of each message sent.

image

@rponte
Copy link
Author

rponte commented Apr 25, 2022

Message Ordering e sua complexidade

Message Ordering eh das coisas mais escorregadias de se fazer em Mensageira.

Não importa seu broker, seja um #RabbitMQ, #ActiveMQ ou #Kafka.

Mesmo quando se tem 1-único-Producer e 1-único-Consumer ainda assim existem edge-cases que podem levar a mensagens fora de ordem e consequentemente inconsistência dos dados 🤡

No fim, devemos abraçar um príncipio chamado de “The End-to-End Principle” (E2E Principle).

Quando falo de E2E Principle, estou falando de trazer alguma “inteligência” para as pontas do seu sistema, para seu producer e consumer, e se nortear por:

  1. abrace At-Least Once Delivery;

  2. não confie no seu Broker Delivery Order;

  3. comutatividade em vez ordenação;

  4. idempotência e/ou deduplication;

1) abrace At-Least Once Delivery

É praticamente impossível implementar a semântica Exactly-Once Delivery, não acredite cegamente no que os vendors te falam!

O que podemos fazer eh fingir sua execução implementando mecanismos que a façam acontecer em cima da semântica At Least-Once Delivery, como o uso de idempotência, deduplication, transações etc.

2) não confie no seu Broker Delivery Order

A ordem que as mensagens entram e saem no seu broker tem pouca ou nenhuma importância, confiar nela eh a melhor forma de se dar mal.

O que importa de fato eh a ordem de negócio das mensagens (Business event order), pois eh com base nela que vc vai desenhar e implementar sua solução.

3) comutatividade em vez ordenação

Dado que a ordem de mensagens não pode ser confiada nem garantida pelo broker, aceite que as mensagens podem (e eventualmente irão) chegar fora de ordem.

Portanto, programe seu código em consumers para isso, não espere o pior acontecer.

4) idempotência e deduplication

Embora ambos os conceitos sejam fáceis de entender, eles não necessariamente são fáceis de implementar.

Especialmente se você lida com sistemas externos, ou seja, sua lógica causa efeitos colaterais em outros serviços, como RDBMS, cache ou um processo qualquer.

Concluindo

Enfim, sem E2E Principle, a tal “inteligência” nas bordas, eventualmente você terá problemas com ordem de mensagens e inconsistência de dados.

Isso pode acontecer com você agora ou em 10 anos, quem sabe 🤷‍♀️

Você precisa se preocupar? Diria que sim, mas implementar algo para resolver eh outra estória. Desenhar e implementar para tolerância a falhas tem custo e sua criticidade depende do seu contexto.

Links:

@rponte
Copy link
Author

rponte commented Mar 15, 2023

@rponte
Copy link
Author

rponte commented Mar 30, 2023

@iseki0
Copy link

iseki0 commented Nov 12, 2023

But as you said, saga is a distribution transaction pattern also. So... what is "avoid distribution transaction"?

@rponte
Copy link
Author

rponte commented Nov 17, 2023

This article has a very didact explanation of how to implement the different types of delivery semantics, as we can see below:

Offset Manager

Each message in Kafka is associated with an offset - an integer number denoting its position in the current partition. By storing this number, we essentially provide a checkpoint for our consumer. If it fails and comes back, it knows from where to continue. As such, it is vital for implementing various processing guarantees in Kafka:

  • For at-most-once, we need to save $offset + 1 before processing $offset. If our consumer fails before successfully process $offset and restarts, it will continue from $offset + 1 and not reprocess $offset.
  • For at-least-once, we need to successfully process $offset before saving $offset + 1. If our consumer fails before saving $offset + 1 and restarts, it will continue from and reprocess $offset.
  • For exactly-once using an external transactional storage - we need to process $offset and save $offset + 1 within one transaction and roll back if anything goes wrong.

@rponte
Copy link
Author

rponte commented Jan 19, 2024

@rponte
Copy link
Author

rponte commented Jan 24, 2024

Meu RT quando caiu a ficha:

excelente pergunta do @tomazfernandes_ 👏🏻👏🏻

uma solução robusta, tolerante a falhas e barata de implementar que seja retriable, idempotente e abrace consistência eventual eh justamente Outbox Pattern 🤤🤤

viva o ACID dos RDBMS + aquele job em background que roda a cada 1min 🥳

@rponte
Copy link
Author

rponte commented Apr 3, 2024

Tweet by @DominikTornow

The ingredients of failure tolerance & transparency are more basic than you would think:

1⃣ Guarantee retries via persisting the invocation
2⃣ Guarantee correctness of retries via idempotent steps

Outlined in Fault Tolerance via Idempotence

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment