ivo@setyadi:~$ ivo.blogspot.com

# catatan teknis, sejak 2001

Outbox: Ketika Tabel Database Menggantikan Message Queue & Redis

2026-09-30 · #arsitektur software #kafka #message queue #outbox #pemrograman #postgresql #redis #sistem terdistribusi

Banyak aplikasi akhirnya harus melakukan dua hal sekaligus: menyimpan data dan memberi tahu sistem lain soal itu. Pasien daftar → simpan kunjungan dan kirim laporan ke API kesehatan. Pembayaran lunas → catat transaksi dan dorong event ke sistem lain. Cara naif-nya disebut "dual write": tulis ke database, lalu kirim ke message queue. Nah, celah di antara dua langkah itulah tempat data diam-diam hilang.

Pola outbox menutup celah tersebut — dan untuk banyak sistem, artinya kamu tak butuh RabbitMQ, Kafka, atau Redis sama sekali.

Masalah "dual write"

simpanKunjungan(kunjungan)   // sudah commit ke Postgres
kirimKeQueue(event)          // ...jaringan ngadat di sini

Kalau proses crash di antara dua baris itu, atau queue sempat detik tak terjangkau, kamu dapat kunjungan tanpa event. Dibalik urutannya? Bisa dapat event untuk kunjungan yang tak pernah tersimpan. Tak ada transaksi yang membentang dari "database saya" ke "broker milik orang lain" — jadi selalu ada yang bisa lolos lewat celah itu.

Outbox itu sebenarnya apa

Bayangkan resepsionis klinik yang harus kirim laporan ke SATUSEHAT tiap pasien daftar. SATUSEHAT kadang down. Kalau nunggu jawaban SATUSEHAT dulu baru layani pasien, antre panjang.

Solusi outbox: resepsionis tulis "PR kirim" di buku catatan yang sama dengan buku transaksi, lalu langsung lanjut layani pasien. Ada petugas terpisah yang tiap menit buka buku itu, kerjakan PR yang belum beres, coret yang sudah terkirim. Teknisnya: satu baris tabel di database yang sama, ditulis dalam satu transaksi bersama data bisnis.

CREATE TABLE outbox_event (
  id          text PRIMARY KEY DEFAULT gen_random_uuid()::text,
  type        text NOT NULL,          -- 'encounter', 'payment', ...
  payload     jsonb NOT NULL,
  status      text NOT NULL DEFAULT 'pending',   -- pending | sent
  attempts    int  NOT NULL DEFAULT 0,
  created_at  timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX idx_outbox_pending ON outbox_event (created_at) WHERE status = 'pending';

Jalur tulis — satu transaksi, tak ada yang bisa desinkron:

BEGIN;
  INSERT INTO kunjungan (...) VALUES (...);
  INSERT INTO outbox_event (type, payload) VALUES ('encounter', $1);
COMMIT;

Dan worker yang men-drain-nya, pakai timer:

for (const e of await listPending()) {
  try {
    await kirimKeApiLuar(e.payload);
    await markSent(e.id);
  } catch (err) {
    await markFailed(e.id, err);   // tetap pending, dicoba lagi ronde berikut
  }
}

Segitu saja. To-do list yang tinggal di buku yang sama dengan data asli, plus petugas yang menyicilnya.

Kenapa ini bisa gantikan message queue dan Redis

Untuk tugas ini, broker sebenarnya cuma memberi satu layanan penting: menyimpan pesan yang belum terproses, tanpa hilang. Tabel database bisa lakukan itu juga.

  1. Durability (tak hilang saat restart). Baris tersimpan di disk Postgres. Restart container, redeploy, crash — baris pending tetap ada, sama seperti Redis-persistence atau MQ durable.
  2. Konsistensi — di sinilah outbox justru menang. Baris bisnis dan baris "kirim ini" commit bareng, atomik. Tak ada jendela di mana salah satu ada tanpa yang lain. Dengan broker terpisah, kamu balik ke dual-write: commit DB bisa sukses sementara publish gagal (atau sebaliknya), dan sistemmu jadi tak sinkron.
  3. Satu komponen lebih sedikit. Tak perlu server tambahan untuk di-install, diamankan, dimonitor, dan di-backup. Database yang sudah kamu jalankan mengurus data dan antrean.

Kapan broker sungguhan tetap lebih baik

KebutuhanTabel outboxMQ / Redis
Durable, anti-hilangYaYa
Atomik dengan datamuYa (keunggulannya)Tidak — dual write
Volume rendah–sedangYaYa
Throughput sangat tinggi (ribuan/detik)Tidak — polling membebani DBYa
Latency real-time (milidetik, push)Tidak — nunggu ronde pollYa
Backoff canggih, dead-letter, fan-out banyak consumerBikin manualBawaan

Kesimpulan

Kalau volume-mu sedang saja dan yang benar-benar penting adalah "jangan sampai event hilang atau dobel", pola outbox memberimu durability sekaligus konsistensi transaksi tanpa infrastruktur tambahan. Baru pakai Kafka/RabbitMQ/Redis saat kamu melampauinya — throughput tinggi, latency real-time, atau retry berjenjang — bukan sebelum itu.

Analoginya: outbox = to-do list di buku yang sama dengan buku transaksi. Message queue = kantor pos terpisah — lebih canggih, tapi kamu jadi harus menjaga dua buku tetap sinkron, dan justru di situlah bug biasa muncul.

$ cd ~/

$ cat comments/ (0)