Implementing Saga Pattern for Distributed Transactions

Distributed transactions in a microservice architecture often lead to data inconsistency and complex failures. We implement the Saga pattern, which ensures consistency through compensating actions, and configure it to fit your infrastructure. Our team handles the entire cycle—from scenario design to deployment and ongoing support—so the solution works reliably and scales with your business.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

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

  1. Architecture audit: identify failure points in current microservices.
  2. Saga design: describe steps, compensations, event schema.
  3. Infrastructure deployment: set up Kafka/Temporal cluster, topics, persistence.
  4. Handler implementation: implement idempotency, compensating transactions, DLQ.
  5. 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.