Data Consistency Patterns — System Design

Dalam sistem terdistribusi, memastikan konsistensi data antar node dan service adalah salah satu tantangan terbesar. Level Konsistensi: Strong Consistency → Sem

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.

💡 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:

🎯 Pilih pola:

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.