Thuta Learning
AdvancedProgrammingintermediate

Distributed Transactions

What you'll walk away with

  • Explain the core ideas behind Distributed Transactions
  • Study the sample diagram/code and analyze its trade-offs
  • Apply the technique correctly to the Tutorial Platform and production scenarios

Build the mental model

A single-database transaction gives you ACID guarantees for free: the database's transaction log can atomically commit or roll back every change together, since it's one system with one source of truth. Once data is split across services or shards — an order-service and an inventory-service each with their own database — no single database can coordinate that commit, yet you still need 'either both changes happen or neither does' semantics. Two-phase commit (2PC) extends the transaction idea across systems: a coordinator asks every participant to 'prepare' (lock resources, confirm it CAN commit) and waits for all to agree before telling everyone to actually commit. This gives strong atomicity, but at a real cost — if the coordinator crashes after some participants have prepared but before the final commit message, those participants are stuck holding locks indefinitely, blocking other work. The saga pattern trades that strict atomicity for resilience: instead of one blocking transaction, it's a sequence of independent local transactions, each service committing its own step immediately, with a predefined compensating action (e.g. 'refund the payment') to undo earlier steps if a later one fails. This is why sagas, not 2PC, dominate real-world microservice architectures.

Connect it to a real scenario

When a Tutorial Platform student buys a course bundle, three separate services are involved: the payments-service charges the card, the enrollment-service grants course access, and the notifications-service sends a receipt email — each with its own database, split apart precisely for the independent scaling reasons covered earlier in this course. If payment succeeds but enrollment fails, the student paid for nothing. Using 2PC here would mean payments-service holds a lock on the charge while waiting for enrollment-service and notifications-service to confirm they can proceed — risky if any one of them is slow or down. Instead, the platform uses a saga: charge the card, then grant access, then send the email, with a compensating refund step wired in if enrollment fails after payment succeeded.

Try the working example

text
TWO-PHASE COMMIT (2PC)

Coordinator          Payment-svc      Enrollment-svc     Notify-svc
    |--- PREPARE ------->|                |                 |
    |--- PREPARE --------------------->|                 |
    |--- PREPARE ---------------------------------------->|
    |<-- YES, can commit-|                |                 |
    |<-- YES, can commit-----------------|                 |
    |<-- YES, can commit----------------------------------|
    |--- COMMIT --------->|                |                 |
    |--- COMMIT -------------------------->|                 |
    |--- COMMIT --------------------------------------->|

  RISK: if coordinator dies here ^, all 3 services sit
  holding locks forever, waiting for a commit that never comes.

SAGA PATTERN

  Step 1: Payment-svc charges card        --- SUCCESS
              |
              v
  Step 2: Enrollment-svc grants access    --- FAILS!
              |
              v (trigger compensation)
  Compensating step: Payment-svc REFUNDS the charge

  Each step commits locally and immediately.
  No service ever waits, holding a lock, for another to decide.
You should see
The diagram contrasts 2PC's synchronous prepare/commit round-trip, which can leave all participants blocked if the coordinator dies mid-protocol, with a saga's sequence of immediately-committed local steps and a compensating rollback when a later step fails.

5-minute try-it

Design a saga for a 3-step 'change subscription plan' flow: (1) charge the price difference, (2) update the plan tier, (3) send a confirmation email. Write out the compensating action for each step, in order, that would run if step 2 fails.

One important caution

Assuming a saga gives you the same isolation guarantees as a real ACID transaction — between steps, other requests can observe partially-applied state (e.g. a payment charged but access not yet granted), so code reading that data must be written to tolerate intermediate states, not assume atomicity.

Writing a compensating action that itself can fail without a plan — if the 'refund the payment' step fails, you now have a customer charged with no course access and no refund; compensating actions need their own retry/alerting logic, not just a hopeful single attempt.

Wikipedia — Two-phase commit protocolSystem Design

Easy traps

  • Assuming a saga gives you the same isolation guarantees as a real ACID transaction — between steps, other requests can observe partially-applied state (e.g. a payment charged but access not yet granted), so code reading that data must be written to tolerate intermediate states, not assume atomicity.
  • Writing a compensating action that itself can fail without a plan — if the 'refund the payment' step fails, you now have a customer charged with no course access and no refund; compensating actions need their own retry/alerting logic, not just a hopeful single attempt.
  • Validate your load/traffic assumptions before applying a design decision directly to a production system.

Exercise

Design a saga for a 3-step 'change subscription plan' flow: (1) charge the price difference, (2) update the plan tier, (3) send a confirmation email. Write out the compensating action for each step, in order, that would run if step 2 fails.

You'll know it worked when: The diagram contrasts 2PC's synchronous prepare/commit round-trip, which can leave all participants blocked if the coordinator dies mid-protocol, with a saga's sequence of immediately-committed local steps and a compensating rollback when a later step fails.

Distributed Transactions | Thuta Learning