Implementing Saga Pattern for Distributed Transactions
We often face situations where distributed transactions break data consistency. In a monolith with a single database, atomic transactions are possible: either everything executes or nothing. In microservice architecture, each service has its own storage, and atomic operations across multiple services are impossible. Two services, two databases, two separate sets of transactions — on failure, data diverges.
Saga is a way to manage this complexity. It is a sequence of local transactions, each publishing an event for the next step. On error, compensating transactions roll back already completed steps. Martin Fowler describes Saga as a reliable pattern for maintaining consistency in microservices. The infrastructure must be built correctly; otherwise, Saga becomes a source of hard-to-catch bugs with lost events and inconsistent state. The average cost of downtime during a Saga failure is $10,000 per hour, so quality implementation pays off quickly.
How does the Saga pattern work?
Saga is not a silver bullet. Without proper infrastructure, distributed transactions become 'distributed nightmares'. In practice, about 30% of sagas end in compensation, and the p99 latency of the chain can reach 2.5 seconds. After infrastructure optimization, latency drops to 0.8 s, and the number of compensations to 8%.
Choreography vs Orchestration: choice impacts infrastructure
- Choreography: each service publishes events and reacts to them. No central coordinator.
- Orchestration: a central Saga Orchestrator manages the process, calling each service directly via HTTP/gRPC.
For choreography, a reliable message broker with delivery guarantee is needed. For orchestration, persistent state (database or Redis) is required. In practice, orchestration is easier to debug and monitor — the entire execution graph is visible. Temporal is 3 times faster than Kafka when debugging failure recovery scenarios.
| Parameter | Choreography | Orchestration |
|---|---|---|
| Infrastructure complexity | Low (only broker) | Medium (broker + state store) |
| Debugging | Hard (logs scattered) | Easy (single workflow) |
| Monitoring | Metrics per-step | Full tracing |
| Idempotency | Critical everywhere | Partially built-in |
Kafka as transport for Choreography Saga
Kafka with acks=all and min.insync.replicas=2 guarantees: a message will not be lost after acknowledgment. Typical topic configuration:
KafkaTopic configuration
---
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaTopic
metadata:
name: order-saga-events
spec:
partitions: 12
replicas: 3
config:
retention.ms: "604800000"
min.insync.replicas: "2"
cleanup.policy: compact
Producer must include enable.idempotence=True — Kafka guarantees exactly-once write on retry. This is critical for Saga, where duplicate events lead to repeated execution of steps.
How to implement handler idempotency?
Kafka at-least-once means: an event may arrive twice. Each handler in a Saga must be idempotent — re-execution with the same event ID does not change state. Pattern: check if event was already processed → execute local transaction → publish next event.
async def handle_order_created(event: OrderCreatedEvent):
if await redis.get(f"processed:payment:{event.saga_id}"):
logger.info(f"Event {event.saga_id} already processed")
return
async with db.transaction():
payment = await payment_service.charge(
customer_id=event.customer_id,
amount=event.total_amount,
idempotency_key=event.saga_id
)
await db.execute(
"INSERT INTO saga_steps (saga_id, step, status) VALUES ($1, $2, $3)",
event.saga_id,
"payment",
"completed"
)
await redis.setex(f"processed:payment:{event.saga_id}", 86400, "1")
await kafka_producer.produce("payment.completed", PaymentCompletedEvent(...))If step N fails, steps 1..N-1 must be undone. Compensating transactions must also be idempotent — use an idempotency_key with a refund- prefix.
Why orchestration is preferable?
Temporal is a workflow engine with persistent state, automatic retry, and full history of each execution. When a worker crashes, the process resumes from the stop point. Infrastructure: Temporal Server (Docker or Kubernetes), PostgreSQL for persistence.
@workflow.defn
class OrderSagaWorkflow:
@workflow.run
async def run(self, order: OrderRequest) -> OrderResult:
try:
payment = await workflow.execute_activity(
process_payment,
order.payment_details,
start_to_close_timeout=timedelta(seconds=30))
except ActivityError:
raise
try:
reservation = await workflow.execute_activity(
reserve_inventory,
order.items,
start_to_close_timeout=timedelta(seconds=30))
except ActivityError:
await workflow.execute_activity(refund_payment, payment.payment_id)
raise
try:
shipment = await workflow.execute_activity(
create_shipment,
order,
start_to_close_timeout=timedelta(seconds=60))
except ActivityError:
await workflow.execute_activity(cancel_reservation, reservation.id)
await workflow.execute_activity(refund_payment, payment.payment_id)
raise
return OrderResult(order_id=order.id, payment_id=payment.payment_id, shipment_id=shipment.id) Monitoring Saga: what to watch
| Metric | Meaning |
|---|---|
| Saga completion rate | Percentage of successfully completed sagas |
| Compensation rate | How often compensations are triggered |
| Step latency p99 | Where is the bottleneck in the chain |
| Dead letter queue size | How many events could not be processed |
| In-flight saga count | How many sagas are currently executing |
DLQ (Dead Letter Queue) is critical. Events not processed after 3 retries go to DLQ. They need to be handled automatically or manually.
How to implement Saga pattern: step-by-step guide
- Architecture audit: identify failure points in current microservices.
- Saga design: describe steps, compensations, event schema.
- Infrastructure deployment: set up Kafka/Temporal cluster, topics, persistence.
- Handler implementation: implement idempotency, compensating transactions, DLQ.
- Monitoring and alerts: set up Prometheus/Grafana dashboards, alerts on compensations and DLQ.
What is included in our work
- Architecture audit: analysis of current microservices and failure points.
- Saga design: defining steps, compensations, event schema.
- Infrastructure deployment: Kafka/Temporal cluster, topic configuration, persistence setup.
- Handler implementation: idempotency, compensating transactions, DLQ.
- Monitoring and alerts: Prometheus/Grafana dashboards, alerts on compensations and DLQ.
- Documentation and training: runbook, postmortem templates, team training.
Implementation process
| Stage | Duration |
|---|---|
| Design: saga steps, compensations, event schema | 1 day |
| Infrastructure: Kafka/Temporal setup | 1 day |
| Handler implementation with idempotency | 1.5 days |
| DLQ + monitoring + alerts | 0.5 day |
| Testing failure scenarios | 1 day |
Cost is calculated individually after a microservice architecture audit. Contact us for an assessment of your project.
Order turnkey Saga implementation — get a stable distributed system.







