Dalam sistem terdistribusi, memastikan konsistensi data antar node dan service adalah salah satu tantangan terbesar.
Level Konsistensi:
Strong Consistency → Semua baca langsung mendapat data terbaru
Sequential Consistency → Semua node melihat operasi dalam urutan sama
Causal Consistency → Operasi yang berkaitan tampil berurutan benar
Read-your-writes → User selalu melihat perubahan yang dia buat sendiri
Eventual Consistency → Akan konsisten, tapi ada delay
Pola 1: Saga Pattern — konsistensi lintas service
Untuk transaksi yang melibatkan beberapa service, gunakan serangkaian transaksi lokal dengan kompensasi jika gagal:
// Saga: Checkout Order
// Step 1: Reserve inventory (order service)
// Step 2: Charge payment (payment service)
// Step 3: Ship order (shipping service)
// Jika Step 2 gagal → kompensasi Step 1 (release inventory)
// Jika Step 3 gagal → kompensasi Step 1 & 2
class CheckoutSaga {
async execute(order) {
let inventoryReserved = false;
let paymentCharged = false;
try {
await inventoryService.reserve(order.items);
inventoryReserved = true;
await paymentService.charge(order.userId, order.total);
paymentCharged = true;
await shippingService.ship(order);
} catch (error) {
// Kompensasi — rollback yang sudah berhasil
if (paymentCharged) {
await paymentService.refund(order.userId, order.total);
}
if (inventoryReserved) {
await inventoryService.release(order.items);
}
throw error;
}
}
}
Pola 2: Outbox Pattern — publish event yang reliable
Masalah: bagaimana memastikan database update DAN event publish keduanya berhasil atau keduanya gagal?
// SALAH — tidak atomik:
await db.createOrder(order); // berhasil
await eventBus.publish("order.created"); // gagal! Order ada tapi event tidak dikirim
// BENAR — Outbox Pattern:
// 1. Dalam 1 transaksi database, tulis order DAN event ke outbox table
await db.transaction(async (trx) => {
await trx.orders.create(order);
await trx.outbox.create({ // event tersimpan di DB
type: "order.created",
payload: JSON.stringify(order)
});
});
// 2. Background job baca outbox dan publish ke event bus
// Jika publish gagal, coba lagi — data sudah aman di DB
Pola 3: Two-Phase Commit (2PC)
Protokol untuk memastikan semua participant commit atau semua rollback:
Phase 1 — Prepare:
Coordinator → "Siap commit?" → Node A: "Ya"
→ Node B: "Ya"
→ Node C: "Ya"
Phase 2 — Commit:
Coordinator → "Commit!" → Node A, B, C → Commit
Jika ada yang "Tidak" di Phase 1:
Coordinator → "Abort!" → Semua rollback
Kekurangan: blocking, coordinator bisa menjadi single point of failure. Lebih cocok untuk database cluster yang sama, bukan lintas service yang berbeda.
Pola 4: Idempotency Key
Pastikan operasi yang sama bisa di-retry dengan aman:
// Client kirim idempotency key
const response = await fetch("/api/payments", {
method: "POST",
headers: {
"Idempotency-Key": "unique-key-123", // UUID yang sama untuk retry
"Content-Type": "application/json"
},
body: JSON.stringify({ amount: 150000 })
});
// Server cek apakah key ini pernah diproses
async function processPayment(req) {
const key = req.headers["idempotency-key"];
const existing = await db.idempotencyKeys.find(key);
if (existing) return existing.response; // return hasil sebelumnya
const result = await chargePayment(req.body);
await db.idempotencyKeys.save(key, result, { ttl: "24h" });
return result;
}
Perbandingan pola:
| Pola | Kasus Pakai | Trade-off |
|---|---|---|
| Saga | Transaksi lintas service | Eventual, butuh kompensasi |
| Outbox | Reliable event publishing | Butuh background job |
| 2PC | Atomic multi-node commit | Blocking, lambat |
| Idempotency Key | Safe retry operasi | Storage tambahan |
Pilih pola berdasarkan kebutuhan konsistensi dan toleransi terhadap complexity.
🎭 Analogi sehari-hari: Transfer uang antar bank.
- Strong consistency: ATM gak akan kasih hasil kalau kedua bank belum confirm — aman tapi lama
- Eventual consistency: sistem terima, "diproses", saldo update beberapa menit kemudian — cepat tapi bisa "uang hilang sebentar"
- Outbox pattern: bukti transfer dicatat di buku, baru dikirim — gak ada transfer yang lupa
- Saga: kalau langkah 3 gagal, undo langkah 1 dan 2 (kompensasi)
- Idempotency key: kalau ATM error tiba-tiba, kamu bisa coba lagi tanpa double-charge
💡 Kapan eventual consistency cukup: Kalau user tidak melihat operasi-nya sendiri dalam waktu kurang dari beberapa detik, biasanya gak masalah. Read-your-writes** yang penting — pastikan user lihat apa yang dia tulis sendiri segera.
⚠️ Jebakan klasik:
- 2PC di production cross-service = blocking forever kalau coordinator crash. Wajib timeout
- Saga tanpa kompensasi = inconsistent state permanent kalau langkah gagal di tengah
- Outbox dengan polling = latency tinggi. Pakai CDC (Change Data Capture) untuk push real-time
- Idempotency key tanpa TTL = storage meledak
🎯 Pilih pola:
- Single DB cluster → ACID transaksi normal
- Cross-service async → Saga + Outbox
- Cross-DB sync atomic → 2PC (jarang, mahal)
- Safe retry critical → Idempotency Key (wajib payment)
TL;DR: Strong consistency mahal di distributed system. Spectrum: strong → causal → read-your-writes → eventual. Saga (long-running), Outbox (reliable publish), 2PC (atomic mahal), Idempotency (safe retry). Pilih sesuai kebutuhan, jangan default ke strong.