How to Mock a Cross-Shard Saga in Python

Simulate a distributed saga with compensating transactions across multiple database shards using a lightweight Python class that tracks executed steps and rolls them back in reverse on failure.

Medium Python 3.9+ Aug 9, 2026 Database scaling & optimization 14 views 0 copies

Python code

46 lines
Python 3.9+
import json


class SagaState:
    def __init__(self, saga_id):
        self.saga_id = saga_id
        self.executed_steps = []
        self.compensations = []

    def execute_step(self, shard, step_name, operation):
        self.executed_steps.append((shard, step_name))
        print(f"[Saga {self.saga_id}] Executing step '{step_name}' on shard {shard}")
        result = operation()
        self.compensations.append((shard, step_name, result))
        return result

    def compensate(self):
        print(f"[Saga {self.saga_id}] Compensating...")
        for shard, step_name, result in reversed(self.compensations):
            print(f"  Compensating '{step_name}' on shard {shard}: undo {result}")
        self.compensations.clear()


def create_user():
    return {"user_id": 1, "status": "created"}


def create_balance():
    return {"balance": 100}


def create_account():
    return {"account_id": 10}


if __name__ == "__main__":
    saga = SagaState("saga-123")

    # Simulate cross-shard saga: user on shard 1, balance on shard 2, account on shard 1
    saga.execute_step("shard-1", "create_user", create_user)
    saga.execute_step("shard-2", "create_balance", create_balance)
    saga.execute_step("shard-1", "create_account", create_account)

    # Simulate failure mid-saga -> trigger compensation
    saga.compensate()
    print("Saga completed successfully (mock).")

Output

stdout
[Saga saga-123] Executing step 'create_user' on shard shard-1
[Saga saga-123] Executing step 'create_balance' on shard shard-2
[Saga saga-123] Executing step 'create_account' on shard shard-1
[Saga saga-123] Compensating...
  Compensating 'create_account' on shard shard-1: undo {'account_id': 10}
  Compensating 'create_balance' on shard shard-2: undo {'balance': 100}
  Compensating 'create_user' on shard shard-1: undo {'user_id': 1, 'status': 'created'}
Saga completed successfully (mock).

How it works

The SagaState class records each executed step and its result in executed_steps and compensations. When compensate() is called, it iterates through compensations in reverse order — mirroring how real saga frameworks undo the last operation first. The reversed() call ensures LIFO rollback semantics, so shard writes are undone in the opposite order they were applied. Each operation is wrapped in execute_step, which stores the shard name alongside the result to simulate cross-shard coordination. The mock ignores actual database connections, making it ideal for testing saga orchestration logic in isolation.

Common mistakes

  • Not clearing `compensations` after rollback, causing duplicate compensation on retries
  • Executing compensations in forward order instead of `reversed()` to undo the latest step first
  • Forgetting that `execute_step` must return the operation result for downstream logic
  • Hardcoding shard names instead of passing them at runtime to make the mock flexible

Variations

  1. Extend `SagaState` with a `run()` method that auto-triggers compensation on any exception raised by an operation
  2. Use a decorator on operations to automatically record and compensate without explicit `execute_step` calls

Real-world use cases

  • Validating orchestration logic in unit tests when multiple microservices write to different database shards fail mid-transaction.
  • Prototyping a new saga-based checkout flow without spinning up actual sharded databases or network services.
  • Teaching distributed-systems concepts like LIFO compensation by simulating multi-shard writes in a classroom exercise.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Database scaling & optimization

Related tutorials and quizzes for this topic.