Senior 5 min · June 25, 2026

Distributed Transactions and 2PC: When Atomicity Goes Wrong in Production

Distributed transactions and 2PC explained with production failures, code, and debugging.

N
Naren Founder & Principal Engineer

20+ years shipping large-scale distributed systems. Lessons pulled from things that broke in production.

Follow
Production
production tested
June 25, 2026
last updated
1,663
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

Distributed transactions use 2PC to ensure all participants commit or abort together. The coordinator sends a prepare request, waits for all votes, then sends commit or abort. If any participant votes no, the entire transaction aborts. This guarantees atomicity but adds latency and blocking risks.

✦ Definition~90s read
What is Distributed Transactions and 2PC?

A distributed transaction coordinates multiple databases or services to commit or roll back as one atomic unit. Two-Phase Commit (2PC) is a protocol that ensures all participants agree on the outcome, preventing partial updates across systems.

Imagine three friends deciding where to eat.
Plain-English First

Imagine three friends deciding where to eat. First, everyone votes (prepare phase). If all agree, they go (commit). If anyone disagrees, they stay home (abort). But if one friend goes silent after voting, the others wait forever — that's the blocking problem in 2PC.

Distributed transactions are a lie we tell ourselves. We pretend multiple databases can atomically agree on a state change, but in production, that agreement breaks under network partitions, coordinator crashes, and timeouts. I've seen a payment service double-charge customers because a 2PC coordinator crashed after the prepare phase but before commit — the participants were left in doubt, and a manual recovery script went wrong.

You need distributed transactions when a single operation spans multiple systems and must be all-or-nothing: transferring money between banks, placing an order that deducts inventory and charges a card, or booking a flight and hotel together. Without atomicity, you get inconsistent state — money lost, inventory oversold, customers furious.

By the end of this, you'll know exactly how 2PC works under the hood, where it fails, and how to debug it in production. You'll also know when to walk away and use a saga instead.

Why You Can't Just Use ACID Across Databases

ACID transactions work inside a single database because the database controls all resources — locks, logs, recovery. Across databases, there's no shared lock manager, no global transaction log. Without coordination, you get partial failures: inventory deducted but payment not charged, or vice versa.

Before 2PC, teams hacked around this with two-phase writes: write to DB1, then write to DB2. If the second write fails, you're in inconsistent state. Some added a compensating transaction (manual rollback), but that's fragile and error-prone. I've seen a team try to use database triggers to sync two MySQL instances — it ended with a split-brain and data corruption.

2PC solves this by introducing a coordinator that orchestrates the commit decision. But it's not magic — it has its own failure modes.

NoCoordination.systemdesignSYSTEMDESIGN
1
2
3
4
5
6
7
8
9
10
// io.thecodeforge — System Design tutorial

// Naive two-phase write without coordination
// Step 1: Deduct inventory
UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 123;
// Step 2: Charge payment (if this fails, inventory is already deducted)
INSERT INTO payments (order_id, amount) VALUES (456, 29.99);
// If step 2 fails, you need a compensating transaction:
UPDATE inventory SET quantity = quantity + 1 WHERE product_id = 123;
// But what if the compensation also fails? You're inconsistent.
Output
Inventory deducted but payment not charged — data inconsistency.
Production Trap:
Never rely on compensating transactions without idempotency and retry logic. I've seen a compensation loop that kept adding inventory back because the original deduction was already compensated — double inventory.
2PC Failure Modes and Production Debugging THECODEFORGE.IO 2PC Failure Modes and Production Debugging How distributed transactions freeze and how to unstick them Coordinator Manages prepare & commit across participants Prepare Phase All participants vote yes/no; locks held Blocking Problem Coordinator crash leaves participants locked Three Failure Modes Participant crash, coordinator crash, network split Saga Alternative Compensating transactions avoid blocking Unstick Transaction Query coordinator logs or force heuristic commit ⚠ Coordinator crash during commit leaves participants locked indefinitely Always implement timeout and heuristic recovery procedures THECODEFORGE.IO
thecodeforge.io
2PC Failure Modes and Production Debugging
Distributed Transactions 2Pc

How 2PC Works: The Coordinator's Dance

2PC has two phases: prepare and commit. The coordinator sends a 'prepare' request to all participants. Each participant writes the transaction to a durable log (so it can survive crashes) and replies 'yes' (ready) or 'no' (abort). If all say yes, the coordinator sends 'commit'. If any says no, it sends 'abort'.

Participants must block after voting yes — they hold locks on the modified data until they receive the final decision. This is the blocking property that makes 2PC risky for long-running transactions.

Here's a minimal implementation in Go using a coordinator and two participants (PostgreSQL and Redis). The coordinator uses a transaction log to recover after crashes.

TwoPC.goGO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// io.thecodeforge — System Design tutorial

package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"time"

	_ "github.com/lib/pq"
	"github.com/go-redis/redis/v8"
)

type Participant interface {
	Prepare(ctx context.Context, txID string) error
	Commit(ctx context.Context, txID string) error
	Abort(ctx context.Context, txID string) error
}

type PostgresParticipant struct {
	db *sql.DB
}

func (p *PostgresParticipant) Prepare(ctx context.Context, txID string) error {
	// Begin a transaction and write to a prepared transactions table
	tx, err := p.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	// Insert a record that this transaction is prepared
	_, err = tx.ExecContext(ctx, `INSERT INTO prepared_tx (tx_id, status) VALUES ($1, 'prepared')`, txID)
	if err != nil {
		tx.Rollback()
		return err
	}
	// Hold the transaction open — we'll commit or rollback later
	// In real code, you'd store the tx object and commit/rollback in Commit/Abort
	return tx.Commit() // Simplified: in real impl, keep tx open
}

func (p *PostgresParticipant) Commit(ctx context.Context, txID string) error {
	_, err := p.db.ExecContext(ctx, `UPDATE prepared_tx SET status = 'committed' WHERE tx_id = $1`, txID)
	return err
}

func (p *PostgresParticipant) Abort(ctx context.Context, txID string) error {
	_, err := p.db.ExecContext(ctx, `UPDATE prepared_tx SET status = 'aborted' WHERE tx_id = $1`, txID)
	return err
}

type RedisParticipant struct {
	client *redis.Client
}

func (r *RedisParticipant) Prepare(ctx context.Context, txID string) error {
	// Use Redis MULTI/EXEC to simulate prepare
	// In real impl, you'd use Redis transactions or Redlock
	return r.client.Set(ctx, "tx:"+txID+":status", "prepared", 0).Err()
}

func (r *RedisParticipant) Commit(ctx context.Context, txID string) error {
	return r.client.Set(ctx, "tx:"+txID+":status", "committed", 0).Err()
}

func (r *RedisParticipant) Abort(ctx context.Context, txID string) error {
	return r.client.Set(ctx, "tx:"+txID+":status", "aborted", 0).Err()
}

type Coordinator struct {
	participants []Participant
	log          []string // transaction log for recovery
}

func (c *Coordinator) Execute(ctx context.Context, txID string) error {
	// Phase 1: Prepare
	for _, p := range c.participants {
		err := p.Prepare(ctx, txID)
		if err != nil {
			// If any participant fails, abort all
			c.abortAll(ctx, txID)
			return fmt.Errorf("prepare failed: %w", err)
		}
		c.log = append(c.log, txID+":prepared")
	}

	// Phase 2: Commit
	for _, p := range c.participants {
		err := p.Commit(ctx, txID)
		if err != nil {
			// Log the error and continue — we've already committed on some
			// This is a partial commit scenario — manual recovery needed
			log.Printf("commit failed for %s: %v", txID, err)
		}
		c.log = append(c.log, txID+":committed")
	}
	return nil
}

func (c *Coordinator) abortAll(ctx context.Context, txID string) {
	for _, p := range c.participants {
		if err := p.Abort(ctx, txID); err != nil {
			log.Printf("abort failed for %s: %v", txID, err)
		}
	}
}

func main() {
	// Setup participants
	pg := &PostgresParticipant{db: /* initialize db */}
	redis := &RedisParticipant{client: /* initialize redis */}
	coord := &Coordinator{participants: []Participant{pg, redis}}

	ctx := context.Background()
	txID := "order-456"
	if err := coord.Execute(ctx, txID); err != nil {
		log.Fatal(err)
	}
	fmt.Println("Transaction committed successfully")
}
Output
Transaction committed successfully
Senior Shortcut:
In production, keep the coordinator's transaction log in a separate durable store (e.g., etcd or a dedicated database). If the coordinator crashes, it reads the log on restart and resolves in-doubt transactions by re-querying participants.
2PC Commit Protocol FlowTHECODEFORGE.IO2PC Commit Protocol FlowPrepare then commit across participantsCoordinatorSends prepare to all participantsParticipant 1Writes log, votes yes/noParticipant 2Writes log, votes yes/noCoordinatorAll yes → sends commitParticipantsApply commit, release locks⚠ If coordinator crashes after prepare, participants freezeTHECODEFORGE.IO
thecodeforge.io
2PC Commit Protocol Flow
Distributed Transactions 2Pc

The Blocking Problem: Why 2PC Can Freeze Your System

After a participant votes 'yes' in the prepare phase, it must hold locks on the modified data until it receives the final commit or abort. If the coordinator crashes after collecting all 'yes' votes but before sending commit, participants are stuck — they can't release locks because they don't know the outcome. This is the blocking problem.

In production, this means your database connections pile up, lock contention spikes, and eventually the system grinds to a halt. I've seen a PostgreSQL instance hit max_connections because 200 prepared transactions were holding locks on the same row.

The fix is to set a timeout on prepared transactions. PostgreSQL has idle_in_transaction_session_timeout — set it to a few seconds. But be careful: if the timeout fires before the coordinator recovers, you might abort a transaction that should have committed. That's why you need a robust coordinator recovery mechanism.

PostgreSQLPreparedTx.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
-- io.thecodeforge — System Design tutorial

-- Check for prepared transactions that are blocking
SELECT gid, prepared, owner, database, transaction AS xmin
FROM pg_prepared_xacts;

-- Force rollback a stuck prepared transaction (use with caution!)
ROLLBACK PREPARED 'tx-order-456';

-- Set a timeout to automatically abort idle prepared transactions
SET idle_in_transaction_session_timeout = '10s';
Output
gid | prepared | owner | database | xmin
-------------+-------------------+-------+----------+------
tx-order-456 | 2025-03-15 03:00:00 | postgres | mydb | 12345
ROLLBACK PREPARED
Never Do This:
Don't set idle_in_transaction_session_timeout to a value lower than your expected coordinator recovery time. If the coordinator takes 30 seconds to restart and your timeout is 10 seconds, you'll abort transactions that should have committed. Set it to at least 2x the coordinator's expected recovery time.

When 2PC Fails: The Three Failure Modes You'll Actually Hit

  1. Coordinator crash after prepare, before commit: Participants are in-doubt. On restart, the coordinator reads its log and sends commit/abort. If the log is lost, you must manually query each participant and decide.
  2. Participant crash after voting yes: The coordinator sees a timeout and aborts the transaction. But the participant might have already committed locally if it crashed after writing the commit to its log but before responding. This leads to a 'heuristic' outcome — the participant's actual state differs from the coordinator's decision.
  3. Network partition during commit: The coordinator sends commit but some participants don't receive it. They remain prepared. The coordinator might retry, but if the partition lasts too long, participants timeout and abort unilaterally — causing inconsistency.

For each failure mode, you need a recovery procedure. The coordinator should have a 'transaction manager' that periodically scans for in-doubt transactions and resolves them by re-contacting participants.

TransactionRecovery.goGO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// io.thecodeforge — System Design tutorial

// Recovery routine for in-doubt transactions
func (c *Coordinator) RecoverInDoubt(ctx context.Context) {
	// Read the coordinator log
	for _, entry := range c.log {
		if strings.HasSuffix(entry, ":prepared") && !strings.HasSuffix(entry, ":committed") {
			txID := strings.TrimSuffix(entry, ":prepared")
			// Query each participant for status
			allCommitted := true
			for _, p := range c.participants {
				status, err := p.GetStatus(ctx, txID)
				if err != nil || status != "committed" {
					allCommitted = false
					break
				}
			}
			if allCommitted {
				c.log = append(c.log, txID+":committed")
			} else {
				c.abortAll(ctx, txID)
			}
		}
	}
}
Interview Gold:
When asked 'How do you handle coordinator failure in 2PC?', don't just say 'use a transaction log'. Explain that the log must be written to durable storage before sending prepare requests, and that recovery must handle duplicate commits via idempotency keys.

Performance: Why 2PC Is Slow and How to Make It Less Painful

2PC adds at least two round trips per participant (prepare + commit/abort). With three participants across different data centers, that's 6 network round trips plus disk I/O for logging. Latency adds up fast.

Optimizations
  • Presumed Abort: If the coordinator doesn't hear back from a participant, assume abort. This reduces the need for a third phase but can cause premature aborts.
  • Read-Only Optimization: If a participant only reads data, it can skip the prepare phase and vote 'read-only' — it doesn't need to hold locks.
  • Batching: Group multiple transactions into one 2PC round trip. Useful for high-throughput scenarios.

But honestly, if you need high throughput, don't use 2PC. Use sagas with compensating actions. 2PC is for low-volume, high-value transactions where consistency is paramount.

LatencyCalculation.systemdesignSYSTEMDESIGN
1
2
3
4
5
6
7
8
9
10
// io.thecodeforge — System Design tutorial

// Latency breakdown for 2PC with 3 participants (RTT = round-trip time)
// Phase 1: Coordinator sends prepare to 3 participants (1 RTT each, parallel)
// Phase 2: Participants vote (1 RTT back, parallel)
// Phase 3: Coordinator sends commit (1 RTT each, parallel)
// Phase 4: Participants acknowledge (1 RTT back, parallel)
// Total: 2 RTTs (if all parallel) but with network latency, say 50ms per RTT => 100ms minimum
// Plus disk I/O for logging: ~10ms per participant => 30ms
// Total: ~130ms per transaction
Output
Estimated latency: 130ms
Production Trap:
Don't put 2PC participants in different AWS regions. Cross-region latency can be 100-200ms per RTT, making a single transaction take over a second. Keep participants in the same region or use async replication with sagas.

When NOT to Use 2PC: The Saga Alternative

2PC is overkill for most microservices architectures. If your transaction spans services that don't share a database, consider a saga — a sequence of local transactions with compensating actions for rollback.

Sagas come in two flavors: choreography (each service publishes events) and orchestration (a coordinator tells each service what to do). Orchestrated sagas look similar to 2PC but without the blocking — each step commits immediately, and compensation is a separate transaction.

Use 2PC when
  • You need strong consistency (e.g., financial transfers)
  • Participants are within the same trust boundary (e.g., same organization)
  • Transaction volume is low (e.g., < 100 TPS)
Use sagas when
  • You can tolerate eventual consistency
  • Participants are across different teams or organizations
  • You need high throughput

I've seen teams blindly adopt 2PC for every cross-service operation and then wonder why their system is slow and brittle. Don't be that team.

SagaOrchestrator.systemdesignSYSTEMDESIGN
1
2
3
4
5
6
7
8
9
10
11
12
// io.thecodeforge — System Design tutorial

// Saga orchestration for order placement
// Step 1: Reserve inventory (local transaction)
// If fails -> abort (no compensation needed)
// Step 2: Charge payment (local transaction)
// If fails -> compensate step 1 (release inventory)
// Step 3: Confirm order (local transaction)
// If fails -> compensate step 2 (refund payment) and step 1 (release inventory)

// Each step is a local ACID transaction. The saga coordinator logs each step and its compensation.
// No global locks, no blocking. But you must handle partial failures with idempotent compensations.
The Classic Bug:
Saga compensations must be idempotent. If a refund fails and you retry, you might refund twice. Use a unique idempotency key per compensation action.
2PC vs Saga for Distributed TxTHECODEFORGE.IO2PC vs Saga for Distributed TxAtomic vs compensating rollback2PCSynchronous prepare/commitHolds locks until commitBlocking on coordinator crashStrong atomicity guaranteeSagaAsync local transactionsNo global locks neededCompensating actions undoEventual consistencyUse saga for microservices; 2PC for shared DB coordinationTHECODEFORGE.IO
thecodeforge.io
2PC vs Saga for Distributed Tx
Distributed Transactions 2Pc

Production Debugging: How to Unstick a Stuck 2PC Transaction

When a 2PC transaction gets stuck, you need to identify the coordinator and participants, then manually resolve. Here's the playbook:

  1. Find the coordinator: Check the coordinator's logs for the transaction ID. If the coordinator is down, restart it and let it recover.
  2. Query participants: For each participant, check if the transaction is in 'prepared' state. In PostgreSQL: SELECT * FROM pg_prepared_xacts;. In MySQL: XA RECOVER;.
  3. Decide commit or abort: Based on business rules. If the coordinator's log says 'commit', commit on all participants. If 'abort', rollback. If log is lost, you need to infer from participant states — if all participants are prepared, it's safe to commit; if some are aborted, abort all.
  4. Execute: Use COMMIT PREPARED 'txid' or ROLLBACK PREPARED 'txid' on each participant.

Automate this with a script that queries all participants and resolves based on a majority vote or coordinator log.

resolve_2pc.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
# io.thecodeforge — System Design tutorial

# Resolve in-doubt transactions across PostgreSQL participants
TXID=$1

# Query each participant
for host in pg1 pg2 pg3; do
  STATUS=$(psql -h $host -t -c "SELECT gid FROM pg_prepared_xacts WHERE gid='$TXID';")
  if [ -n "$STATUS" ]; then
    echo "$host: prepared"
  else
    echo "$host: not found"
  fi
done

# If all participants are prepared, commit
# Otherwise, abort (or manual decision)
read -p "Commit (c) or Abort (a)? " DECISION
if [ "$DECISION" = "c" ]; then
  for host in pg1 pg2 pg3; do
    psql -h $host -c "COMMIT PREPARED '$TXID';"
  done
else
  for host in pg1 pg2 pg3; do
    psql -h $host -c "ROLLBACK PREPARED '$TXID';"
  done
fi
Senior Shortcut:
Write a monitoring alert that fires when pg_prepared_xacts has more than 5 rows. That's a sign of a stuck coordinator. Automate the resolution with a script that checks the coordinator's health and resolves in-doubt transactions.
● Production incidentPOST-MORTEMseverity: high

The 3AM Coordinator Crash That Double-Charged Customers

Symptom
Customers received duplicate charges for the same order. Payment logs showed two successful transactions for the same order ID.
Assumption
The payment gateway had a bug causing duplicate charges.
Root cause
The 2PC coordinator crashed after sending 'prepare' to the payment service but before sending 'commit'. The payment service had already reserved the funds. When the coordinator restarted, it replayed its log and sent 'commit' again — but the payment service treated the second commit as a new transaction because the coordinator's transaction ID was regenerated.
Fix
Made coordinator transaction IDs idempotent: include a unique order ID in the transaction context. Payment service now checks for duplicate transaction IDs before processing. Also added a commit_if_prepared flag that prevents double-committing.
Key lesson
  • Always make your 2PC transactions idempotent.
  • A coordinator crash and recovery can replay commits — your participants must handle that gracefully.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
Transaction stuck in 'prepared' state for hours — coordinator crashed
Fix
1. Restart coordinator. 2. Check coordinator logs for transaction ID. 3. Query each participant: SELECT * FROM pg_prepared_xacts;. 4. If coordinator log says commit, run COMMIT PREPARED 'txid' on all participants. If abort, run ROLLBACK PREPARED. If log lost, manually decide based on business rules.
Symptom · 02
Coordinator logs 'prepare timeout' for one participant
Fix
1. Check network latency to that participant. 2. Check participant load and connection pool. 3. Increase coordinator_timeout (default 60s). 4. If participant is unresponsive, abort the transaction and retry.
Symptom · 03
Participants report 'in-doubt transaction' after coordinator restart
Fix
1. Ensure coordinator has recovered and is connected to all participants. 2. Coordinator should replay its log and send commit/abort. 3. If coordinator log is lost, manually query each participant and decide commit/abort based on majority or business rules.
★ Distributed Transactions and 2PC Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
PostgreSQL: `pg_prepared_xacts` shows stuck transactions
Immediate action
Check if coordinator is alive
Commands
SELECT gid, prepared, owner FROM pg_prepared_xacts;
SELECT count(*) FROM pg_prepared_xacts;
Fix now
If coordinator is dead, restart it. If coordinator is alive but not resolving, manually commit/rollback: COMMIT PREPARED 'txid'; or ROLLBACK PREPARED 'txid';
MySQL: `XA RECOVER;` shows prepared transactions+
Immediate action
Check coordinator logs for transaction ID
Commands
XA RECOVER;
XA COMMIT 'txid'; or XA ROLLBACK 'txid';
Fix now
Manually resolve each transaction based on coordinator state.
Coordinator logs 'prepare timeout'+
Immediate action
Check participant health and network
Commands
ping <participant_host>
telnet <participant_host> <port>
Fix now
Increase coordinator_timeout in config. If participant is down, abort transaction and retry.
Application reports 'transaction aborted' but participant shows committed+
Immediate action
Check for heuristic outcome
Commands
SELECT * FROM pg_prepared_xacts WHERE gid='<txid>';
Check application logs for coordinator decision
Fix now
If participant committed but coordinator aborted, you have a heuristic mismatch. Manually reconcile by reverting the participant's changes or updating the coordinator's state.
Feature / Aspect2PCSaga
Consistency modelStrong (atomic)Eventual
BlockingYes (holds locks)No (each step commits immediately)
ThroughputLow (< 100 TPS)High (1000+ TPS)
ComplexityMedium (coordinator + participants)High (compensation logic)
Failure handlingCoordinator recovery, manual resolutionCompensation actions, retries
Use caseFinancial transfers, inventory reservationsOrder processing, multi-step workflows

Key takeaways

1
2PC guarantees atomicity across distributed systems but introduces blocking and latency
use it only for low-volume, high-value transactions.
2
Always make 2PC transactions idempotent
include a unique transaction ID and check for duplicates on commit.
3
Monitor prepared transactions in your databases
alert if count exceeds a threshold (e.g., 5) to catch stuck coordinators early.
4
The blocking problem is real
set timeouts on prepared transactions, but ensure they're longer than the coordinator's recovery time.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does 2PC handle a coordinator crash after all participants have vote...
Q02SENIOR
When would you choose 2PC over a saga in a microservices architecture?
Q03SENIOR
What happens if a participant crashes after voting yes but before receiv...
Q04JUNIOR
What is the blocking problem in 2PC?
Q05SENIOR
You find that a 2PC transaction is stuck in 'prepared' state on all part...
Q06SENIOR
How would you design a 2PC system that can handle 1000 transactions per ...
Q01 of 06SENIOR

How does 2PC handle a coordinator crash after all participants have voted yes but before commit is sent?

ANSWER
Participants are in-doubt — they hold locks and wait. On restart, the coordinator reads its transaction log and sends commit. If the log is lost, you must manually query each participant and decide commit or abort based on business rules. This is why the coordinator's log must be durable and replicated.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What is a distributed transaction and when do I need one?
02
What's the difference between 2PC and saga?
03
How do I recover a stuck 2PC transaction in PostgreSQL?
04
What happens if the coordinator crashes during 2PC?
N
Naren Founder & Principal Engineer

20+ years shipping large-scale distributed systems. Lessons pulled from things that broke in production.

Follow
Verified
production tested
June 25, 2026
last updated
1,663
articles · all by Naren
🔥

That's Distributed Systems. Mark it forged?

5 min read · try the examples if you haven't

Previous
Quorum: R + W > N
4 / 9 · Distributed Systems
Next
Distributed Locking