The Lies Distributed Systems Tell
CAP theorem gets all the attention, but the real danger in distributed systems is subtler: the assumptions we make without realizing we're making them.
Every engineer who has worked with distributed systems long enough has a story. Mine involves a payment service that had been running cleanly for eight months, until one Tuesday when network latency between two data centers spiked for eleven seconds. In those eleven seconds, forty-three customers were charged twice.
The code was not buggy in any traditional sense. The logic was clear. The tests passed. The problem was that the system had been built on an assumption that was never written down anywhere: that the network was reliable.
This is the central danger of distributed systems. It is not CAP theorem. It is not eventual consistency. It is the assumptions that live in your head, invisible until the moment they are violated.
The Eight Fallacies, Made Concrete
In 1994, Peter Deutsch wrote a list of assumptions that engineers routinely make about distributed systems. He called them fallacies. All eight remain relevant today, but two cause the most damage in practice.
The first is that the network is reliable. It is not. Packets drop. TCP connections time out. Load balancers restart. A call that usually takes 10ms sometimes takes 10 seconds, and sometimes never returns at all. Your code needs to have an answer for all three outcomes, not just the happy path.
The second is that latency is zero. This one is sneakier. When you call a remote service, you are not calling a function — you are sending a letter and waiting for a reply. The time that takes is not fixed, and it is not zero. A function that adds two numbers takes nanoseconds. A function that adds two numbers by making a database call takes milliseconds. These are not the same kind of operation, and treating them the same way leads to systems that fall apart under load.

What Idempotency Actually Means
The standard advice for distributed systems is to make your operations idempotent — that is, safe to retry. This is correct advice. But it is often stated without explaining why retries are necessary, which leads to a shallow implementation.
Retries are necessary because you cannot distinguish between three situations:
- The call never arrived.
- The call arrived, was processed, and the response was lost.
- The call arrived, processing is still happening, and the response has not come yet.
From the caller’s perspective, all three look identical: you sent a request and received no response. If you retry in scenario two, you have executed the operation twice.
The fix is to make the receiver track intent, not just execution. A payment should be keyed to a client-generated idempotency token. If the same token arrives twice, the second request should return the same result as the first without processing again.
// Without idempotency — dangerous
async function chargeCard(customerId: string, amount: number) {
return await stripe.charges.create({ customer: customerId, amount });
}
// With idempotency — safe to retry
async function chargeCard(customerId: string, amount: number, intentId: string) {
const existing = await db.payments.findByIntent(intentId);
if (existing) return existing;
const charge = await stripe.charges.create({
customer: customerId,
amount,
idempotency_key: intentId,
});
await db.payments.record({ intentId, charge });
return charge;
}
The second version is not much more code. But it has a fundamentally different relationship with failure.
The Consistency Spectrum
CAP theorem says you cannot have both consistency and availability during a network partition. What it does not say — what engineers often miss — is that consistency is not binary.
Strong consistency means every read reflects the most recent write. Eventual consistency means reads will reflect recent writes, eventually. But between those two poles is a spectrum: monotonic reads, read-your-writes, causal consistency. Each offers different trade-offs, and each is appropriate for different use cases.
A social media feed can tolerate eventual consistency — a post appearing a second late causes no harm. A bank balance cannot. A shopping cart is somewhere in between: losing items is catastrophic, but seeing a slightly stale total is acceptable.

What to Do About It
The practical takeaway is not to memorize theorems. It is to develop the habit of asking, for every service boundary in your system: what happens if this call fails silently? What happens if it succeeds, but I never find out? What happens if it runs twice?
Write the answers down. Not in comments — in tests that actually simulate those failures. Chaos engineering is a word for this, but the underlying practice is simple: prove that your system behaves correctly when things go wrong, the same way you prove it behaves correctly when things go right.
The Tuesday that cost forty-three customers a duplicate charge was not a bug in the payment logic. It was a gap in our imagination. Distributed systems are, above all, an exercise in imagining failure more thoroughly than your optimism wants to.
The network will lie to you. Build systems that assume it will.
Setiap engineer yang cukup lama bekerja dengan sistem terdistribusi punya ceritanya sendiri. Saya punya satu yang melibatkan layanan pembayaran yang berjalan bersih selama delapan bulan, sampai suatu hari Selasa ketika latensi jaringan antara dua data center melonjak selama sebelas detik. Dalam sebelas detik itu, empat puluh tiga pelanggan dikenakan tagihan dua kali.
Kodenya tidak buggy dalam pengertian tradisional apapun. Logikanya jelas. Tesnya lulus. Masalahnya adalah sistem itu dibangun di atas asumsi yang tidak pernah ditulis di mana pun: bahwa jaringan bisa diandalkan.
Inilah bahaya inti dari sistem terdistribusi. Bukan CAP theorem. Bukan eventual consistency. Melainkan asumsi-asumsi yang hidup di kepala kita, tidak terlihat sampai saat ia dilanggar.
Delapan Fallacy, Dijadikan Konkret
Pada tahun 1994, Peter Deutsch menulis daftar asumsi yang secara rutin dibuat oleh para engineer tentang sistem terdistribusi. Ia menyebutnya fallacy. Semua delapan masih relevan hingga hari ini, tapi dua yang paling banyak merusak dalam praktik.
Yang pertama adalah bahwa jaringan bisa diandalkan. Tidak bisa. Paket-paket data hilang. Koneksi TCP habis waktu. Load balancer restart. Sebuah panggilan yang biasanya membutuhkan 10ms kadang membutuhkan 10 detik, dan kadang tidak pernah kembali sama sekali. Kode kita perlu punya jawaban untuk ketiga hasil itu, bukan hanya jalur yang menyenangkan.
Yang kedua adalah bahwa latensi adalah nol. Yang ini lebih licik. Ketika kamu memanggil layanan jarak jauh, kamu tidak sedang memanggil sebuah fungsi — kamu sedang mengirim surat dan menunggu balasan. Waktu yang dibutuhkan itu tidak tetap, dan bukan nol. Sebuah fungsi yang menjumlahkan dua angka membutuhkan nanodetik. Sebuah fungsi yang menjumlahkan dua angka dengan melakukan panggilan database membutuhkan milidetik. Ini bukan operasi yang sama, dan memperlakukan keduanya sama menghasilkan sistem yang berantakan di bawah beban.

Apa yang Sebenarnya Dimaksud Idempotency
Saran standar untuk sistem terdistribusi adalah membuat operasimu idempoten — yaitu, aman untuk dicoba ulang. Ini adalah saran yang benar. Tapi ia sering dinyatakan tanpa menjelaskan mengapa percobaan ulang itu diperlukan, yang menghasilkan implementasi yang dangkal.
Percobaan ulang diperlukan karena kamu tidak bisa membedakan tiga situasi:
- Panggilan tidak pernah tiba.
- Panggilan tiba, diproses, dan responsnya hilang.
- Panggilan tiba, pemrosesan masih berlangsung, dan responsnya belum datang.
Dari perspektif pemanggil, ketiga situasi terlihat identik: kamu mengirim permintaan dan tidak menerima respons. Jika kamu mencoba ulang di skenario kedua, kamu telah mengeksekusi operasi dua kali.
Solusinya adalah membuat penerima melacak niat, bukan hanya eksekusi. Sebuah pembayaran harus dikaitkan dengan token idempotency yang dihasilkan oleh klien. Jika token yang sama tiba dua kali, permintaan kedua harus mengembalikan hasil yang sama seperti yang pertama tanpa memproses lagi.
// Tanpa idempotency — berbahaya
async function chargeCard(customerId: string, amount: number) {
return await stripe.charges.create({ customer: customerId, amount });
}
// Dengan idempotency — aman untuk dicoba ulang
async function chargeCard(customerId: string, amount: number, intentId: string) {
const existing = await db.payments.findByIntent(intentId);
if (existing) return existing;
const charge = await stripe.charges.create({
customer: customerId,
amount,
idempotency_key: intentId,
});
await db.payments.record({ intentId, charge });
return charge;
}
Versi kedua tidak jauh lebih banyak kodenya. Tapi ia memiliki hubungan yang fundamentally berbeda dengan kegagalan.
Spektrum Konsistensi
CAP theorem mengatakan kamu tidak bisa memiliki konsistensi dan ketersediaan secara bersamaan selama partisi jaringan. Yang tidak dikatakannya — yang sering dilewatkan oleh engineer — adalah bahwa konsistensi bukan biner.
Konsistensi kuat berarti setiap pembacaan mencerminkan tulisan terbaru. Eventual consistency berarti pembacaan akan mencerminkan tulisan terbaru, pada akhirnya. Tapi di antara dua kutub itu ada spektrum: monotonic reads, read-your-writes, causal consistency. Masing-masing menawarkan trade-off yang berbeda, dan masing-masing cocok untuk kasus penggunaan yang berbeda.
Feed media sosial dapat mentolerir eventual consistency — sebuah postingan yang muncul terlambat satu detik tidak menimbulkan kerugian. Saldo bank tidak bisa. Keranjang belanja ada di suatu tempat di antaranya: kehilangan item itu bencana, tapi melihat total yang sedikit basi itu dapat diterima.

Apa yang Harus Dilakukan
Kesimpulan praktisnya bukan menghafal teorema. Melainkan mengembangkan kebiasaan bertanya, untuk setiap batas layanan dalam sistemmu: apa yang terjadi jika panggilan ini gagal secara diam-diam? Apa yang terjadi jika ia berhasil, tapi saya tidak pernah tahu? Apa yang terjadi jika ia berjalan dua kali?
Tulis jawabannya. Bukan dalam komentar — dalam tes yang benar-benar mensimulasikan kegagalan tersebut. Chaos engineering adalah kata untuk ini, tapi praktik dasarnya sederhana: buktikan bahwa sistemmu berperilaku benar ketika sesuatu salah, sama seperti kamu membuktikan ia berperilaku benar ketika sesuatu benar.
Hari Selasa yang membuat empat puluh tiga pelanggan kena tagihan ganda bukan merupakan bug dalam logika pembayaran. Ini adalah celah dalam imajinasi kami. Sistem terdistribusi, di atas segalanya, adalah latihan dalam membayangkan kegagalan lebih menyeluruh daripada yang ingin dilakukan oleh optimisme kita.
Jaringan akan berbohong padamu. Bangun sistem yang mengasumsikan itu akan terjadi.