A Distributed Transaction Cannot Roll Back, So It Must Learn to Remember
Hatched by Kai Nguyen
Aug 09, 2026
10 min read
1 views
93%
What if the most reliable distributed transaction is not one that can be rolled back, but one that can be explained, repaired, and replayed?
That question exposes a fundamental mistake in how many teams think about microservices. They treat distributed consistency as a weakened version of a database transaction, as though the goal were to recreate one invisible, instantaneous commit across many independent systems. But once a business process crosses service boundaries, each service has its own database, timing, failures, and definition of success. The system cannot simply pretend that these boundaries do not exist.
The more durable answer is to design for a different kind of integrity. A Saga divides a business transaction into local commitments and defines what should happen when later steps fail. Kafka provides the durable, replayable stream through which those commitments, failures, and corrections become visible to many participants. Together, they suggest a powerful thesis:
In a distributed system, consistency is not a moment. It is an ongoing social process among services, maintained by durable evidence and meaningful repair.
This is more than an implementation detail. It changes how we design workflows, data models, failure handling, and even our definition of correctness.
The Distributed Transaction Is a Story, Not a Statement
Consider an online order. A customer places an order, inventory is reserved, payment is authorized, and a shipment is created. In a single database, one transaction might attempt to make these changes appear atomic. Either every operation commits, or all of them roll back.
Across services, that assumption breaks down. The inventory service may successfully reserve the last item while the payment service times out. The payment provider may authorize the charge while the shipping service is unavailable. A service may complete its local transaction and crash before notifying the next service. Network delays can make a successful operation look like a failure.
There is no universal rollback button. The inventory database cannot reach backward into the payment provider and erase an authorization. The shipping service cannot force the order service to forget that an order existed. Even if all systems technically supported reversal, a customer may have received an email, a warehouse may have begun packing, or an external provider may have imposed a fee.
A Saga accepts this reality. It breaks the process into local transactions, each committed within the authority of its own service. If a later step fails, the system invokes compensation transactions for the earlier steps that can be meaningfully counteracted. A reservation can be released. A refund can be issued. A shipment can be canceled, if it has not yet left the warehouse.
But compensation is not rollback. Rollback says, “This never happened.” Compensation says, “This happened, and we are taking a new action to correct its consequences.” That distinction is the foundation of trustworthy distributed behavior.
A refund does not make a payment authorization disappear from the provider’s history. Releasing inventory does not erase the fact that another customer may have seen the item as unavailable for several minutes. A cancellation email does not make the original confirmation unsent.
The system therefore needs a memory of what happened, not merely a current snapshot of what appears to be true.
Kafka Turns Failure Into Durable Memory
This is where an event log becomes more than plumbing. In a conventional message queue, a message is often treated as a task. One consumer receives it, performs work, and the message is removed or marked complete. That model is useful when one worker should process one job.
A publish and subscribe system offers a different model. An event is published to a topic, and multiple consumers can independently react to it. One service may update a read model, another may send an email, a third may record an audit entry, and a fourth may trigger an analytics pipeline. They do not need to coordinate through a single central database because they share a durable account of events.
Kafka strengthens this model by storing immutable messages in topics, dividing topics into partitions, and retaining those messages according to time or size policies. Consumers can read the stream, stop, restart, and catch up. A new consumer can often begin from historical events rather than asking every upstream service to reconstruct the past.
That changes the role of communication in a Saga. An event is not merely a notification that says, “Please do the next thing.” It can be a durable statement that says, “This local fact became true at this point in time.”
For example, the order service might publish OrderPlaced. The inventory service consumes it and, after its local transaction succeeds, publishes InventoryReserved. The payment service consumes that event and publishes either PaymentAuthorized or PaymentDeclined. The shipping service can listen for the combination of facts it needs without being directly called by every preceding service.
The value of this arrangement is not simply asynchronous performance. It is recoverability. If the shipping service fails after inventory has been reserved, it can resume from the relevant events. If a new fraud detection service is introduced, it can process retained order and payment events. If an audit discrepancy appears, engineers can inspect the sequence of facts rather than infer history from a mutable collection of tables.
Yet durable memory introduces its own discipline. Kafka orders messages within a partition, not across an entire topic. If events concerning one order are spread across partitions, consumers cannot assume a single global sequence. The partitioning key therefore becomes a semantic choice. Using the order identifier as the key can preserve the order’s event sequence in one partition, while still allowing different orders to be processed in parallel.
This yields an important design principle:
Scale the independent work, but preserve the sequence of decisions that must be understood together.
A poor partitioning strategy can make a logically ordered workflow appear to behave randomly. A good one turns concurrency into a controlled form of parallelism.
Choreography and Orchestration Are Two Theories of Responsibility
Saga implementations commonly take one of two forms. In orchestration, a central coordinator tells each service what to do and decides what happens next. In choreography, services publish events after completing local work, and other services react to those events.
These are often presented as competing architectural styles. A deeper view is that they answer different questions about responsibility.
Orchestration centralizes decision making. The orchestrator knows the business process, tracks progress, handles timeouts, and initiates compensation. This can make a complex workflow easier to inspect. If payment fails, the orchestrator can explicitly release inventory, mark the order as canceled, and notify the customer. The process has a visible owner.
The danger is that the orchestrator becomes a distributed transaction manager in disguise. If every new business rule requires another branch in one central component, the coordinator becomes difficult to change and easy to overload with knowledge. Services start behaving like passive databases rather than autonomous owners of their capabilities.
Choreography distributes decision making. Each service reacts to events and emits new events after its own local transaction. This reduces direct coupling and makes it easier to add independent consumers. A reporting service, for instance, can subscribe to order events without modifying the order workflow.
The danger is invisible control flow. A failure may trigger a chain of compensations that no single engineer can easily visualize. Event names can become an accidental programming language, and a small change in one service can produce surprising reactions elsewhere.
The choice should therefore depend on the shape of the workflow. Use orchestration when the process has strict sequencing, explicit deadlines, human escalation, or complicated compensation rules. Use choreography when services are genuinely independent, events have stable business meaning, and new consumers should be able to join without changing the producers.
In practice, a hybrid design is often strongest. A coordinator can own the critical business process while Kafka carries durable events to observers, projections, audit systems, and secondary reactions. The coordinator manages intent. The event stream preserves evidence.
This distinction helps avoid a common confusion: the component that decides what should happen is not necessarily the component that records what did happen.
The Real Unit of Reliability Is the Repair Loop
Many systems describe a Saga as a sequence of steps with compensating actions. That description is useful, but incomplete. A production grade Saga is better understood as a repair loop with four parts:
- Intent: What business outcome are we trying to achieve?
- Evidence: Which local facts have actually been committed?
- Reaction: What next action should those facts trigger?
- Repair: If the desired outcome is no longer possible, what new action reduces the harm?
This framework exposes several engineering requirements that are easy to miss.
First, every event should have a clear identity and business meaning. An event such as PaymentAuthorized is more useful than a vague message such as PaymentUpdated. Consumers need to know whether the event represents an action requested, an action completed, or a state observed.
Second, handlers must be idempotent. Kafka consumers can retry, crash after processing, or receive a message again. If processing InventoryReserved twice reserves two units, the system is unsafe. A consumer should record an event identifier, use a business key, or apply a conditional update so that repeated delivery produces the same result as one delivery.
Third, the local database update and the publication of the corresponding event must be treated as one reliability problem. If a service commits InventoryReserved to its database and crashes before publishing the event, downstream services may wait forever. If it publishes first and then the database commit fails, consumers may act on a fact that never became true.
A common solution is an outbox pattern. The service writes its business change and an outgoing event record in the same local database transaction. A separate publisher then sends the recorded event to Kafka and marks it as published. This does not create global atomicity, but it creates a durable bridge between local truth and shared communication.
Fourth, compensation must be modeled as a first class business operation. It needs its own event, identifier, permissions, timeout, and failure policy. A refund can fail. An item can become unavailable before a reservation is released. A cancellation can require human review. Treating compensation as a casual reverse API call leaves the most important path in the system underdesigned.
Finally, observability must follow the business process rather than individual requests. A trace identifier or saga identifier should connect the original intent, every local transaction, every emitted event, each retry, and every compensation. Otherwise, operators see isolated errors instead of one incomplete story.
Key Takeaways
-
Design for correction, not imaginary rollback. For every local commitment, define what compensation means in business terms. Ask what harm can be reduced, not how history can be erased.
-
Give every workflow a durable narrative. Use immutable events to record meaningful facts, retain enough history for recovery, and make the saga identifier available throughout the process.
-
Choose partition keys according to causal order. Keep events that must be interpreted sequentially in the same partition, while allowing unrelated business entities to scale in parallel.
-
Make consumers idempotent from the beginning. Assume retries and duplicate delivery. Use event identifiers, conditional writes, and deduplication records where necessary.
-
Separate decision from evidence. An orchestrator may direct the workflow, while an event stream records completed facts for recovery, auditing, projections, and new consumers.
The System That Remembers Can Be Trusted
A distributed system does not become reliable by hiding its partial failures. It becomes reliable by making those failures legible and giving the system a path toward a better state.
The combination of Sagas and durable event streams offers a different philosophy of software architecture. Local transactions protect what each service owns. Events preserve what the system has learned. Compensation provides a vocabulary for repair. Partitioning determines which facts must remain in order. Retention makes yesterday’s decisions available to tomorrow’s consumers.
This is why the deepest question is not, “How do we make many databases commit as one?” It is, “How do we make a collection of independent decisions converge toward an acceptable business outcome, even when some decisions cannot be undone?”
The answer is not perfect atomicity. It is accountable continuity: every important action leaves evidence, every participant can recover its place, and every failure has a defined response. Once that becomes the design goal, Kafka is no longer just a fast transport, and a Saga is no longer just a transaction pattern. Together, they form a way for software to remember, coordinate, and repair itself without pretending that distributed reality is a single database.
Sources
Hatch New Ideas with Glasp AI 🐣
Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)
Start Hatching 🐣