PostgreSQL atau Kafka? Membandingkan Dulu Sebelum Membangun
Endy Muhardin, ArtiVisi Intermedia
"Mengapa tidak event sourcing saja?"
Setiap kali ArtiVisi mengembangkan sistem yang mengelola uang, pertanyaan ini selalu muncul dengan alasan yang sama:
Skalabilitas
Audit trail otomatis dari event log
Arsitektur modern untuk portofolio
Di seri ini, monolit PostgreSQL dan event sourcing dengan Kafka sama-sama dibuat lalu diuji berdampingan, sehingga keputusan arsitektur diambil berdasarkan data hasil pengukuran.
Produk: Gateway Virtual Account Multi-Bank
Untuk siapa
Institusi yang terhubung langsung ke beberapa bank
SPP kampus, tagihan rumah sakit, yayasan
Pembayaran masuk langsung ke rekening penampungan milik institusi, tanpa rekening perantara
Mengapa bukan agregator
Biaya per transaksi yang nominalnya besar untuk tagihan sebesar SPP
Tanpa agregator, institusi berintegrasi sendiri dengan tiap bank, masing-masing dengan protokol dan format data yang berbeda, lalu merekonsiliasi transaksi tiap bank secara terpisah
Gateway self-hosted ini menyediakan satu API untuk banyak bank, tanpa biaya selain biaya bank
Aturan Bisnis yang Menentukan Arsitektur
Tagihan yang sama dapat dibayar melalui beberapa bank sekaligus, dan hanya pembayaran yang lebih dahulu settle yang dicatat sebagai pelunasan
Jika nomor VA disimpan di gateway, bank menunggu jawaban final saat itu juga, diterima atau ditolak, sehingga gateway tidak boleh menerima dulu lalu mengembalikan dana belakangan
Tagihan terbagi menjadi tiga jenis, yaitu CLOSED yang dibayar sekali lalu ditutup, INSTALLMENT yang dapat dicicil, dan OPEN yang selalu aktif dan dapat dibayar berulang kali, seperti rekening donasi
Jumlah nomor VA terbatas, sehingga nomor yang sama digunakan bergantian untuk tagihan yang berbeda, asalkan tidak aktif untuk dua tagihan pada saat yang sama
Rekonsiliasi harian wajib mendeteksi pembayaran ganda. Jika nomor VA disimpan di bank, seperti di BNI, bank menerima pembayaran tanpa bertanya ke gateway. Kalau tagihannya sudah lunas lewat bank lain, gateway hanya bisa mendeteksi pembayaran ganda itu, tidak bisa menolaknya.
Poin terakhir menjadi inti drama di episode 4. Simpan dulu.
Masalahnya: Satu Tagihan, Dua Pembayaran
Tanpa pengaman, kedua request membaca status AKTIF sebelum salah satunya sempat menulis LUNAS. Keduanya lalu mencatat pelunasan untuk tagihan yang sama. Opsi A dan Opsi B menjawab masalah ini dengan cara yang berbeda.
Opsi A: Monolit Relasional
Dua request mengakses baris tagihan yang sama. Row lock membuat request kedua menunggu, lalu membaca status yang sudah diperbarui.
Mekanisme: validasi dan pencatatan dalam satu transaksi database, dengan SELECT FOR UPDATE.
Kedua request mengirim event, lalu menunggu keputusan pemroses tunggal sebelum menjawab bank. Pemroses tunggal menangani event untuk tagihan yang sama satu per satu. Saat event #2 diproses, status tagihan sudah LUNAS, sehingga pembayaran kedua ditolak.
Mekanisme: event dikirim ke Kafka dengan key ID tagihan, diproses Kafka Streams sebagai single writer per tagihan dengan status di RocksDB, lalu keputusannya dikembalikan ke request. PostgreSQL hanya menjadi read model.
Trade-off di Atas Kertas, Sebelum Ada Kode
Dimensi
Opsi A
Opsi B
Ingress protokol
Synchronous request-response langsung
Synchronous, validasi lokal dulu, lalu append async ke Kafka
Source of truth
Relational database
Event log + RocksDB state store lokal
Concurrency control
Row lock per transaksi
Single writer per partition key, tanpa lock
Jaminan satu tagihan hanya lunas sekali (invariant)
Atomic multi-row transaction, bawaan
Bisa atomic, butuh desain yang tepat
Overhead operasional
1 app + 1 database
Tambah 1 Kafka broker + komponen streaming
docs/architecture-comparison.md di repo payment-gateway, ditulis sebelum baris kode pertama.
Menjamin Satu Tagihan Hanya Lunas Sekali
Opsi A
PostgreSQL sudah menyediakan antrean lock, sehingga pembayaran kedua untuk tagihan yang sama menunggu hingga transaksi pembayaran pertama selesai
Tanpa desain ekstra
Opsi B
Request harus menunggu keputusan pemroses tunggal, atau validasi dan pencatatan disatukan dalam satu transaksi RocksDB
Butuh desain ekstra
Pola yang umum dipakai, validasi di state lokal lalu append event, tidak menunggu keputusan itu, sehingga Bank B telanjur dijawab diterima sebelum pembayaran ganda terdeteksi
Jeda ini akibat pola implementasi, bukan keterbatasan RocksDB atau Kafka Streams
Di atas kertas, Opsi A yang dipilih. Beban puncak institusi berkisar puluhan hingga ratusan TPS (transaksi per detik).
Mengapa Opsi A dan Opsi B Tetap Dibuat
Klaim "PostgreSQL sanggup 5.000–10.000 TPS" masih teoretis dan belum pernah diverifikasi sendiri
Opsi B baru bisa dinilai secara objektif jika sistemnya benar-benar dibuat dan dijalankan
Syarat Agar Perbandingannya Setara
Sebelum melihat angka apa pun, perbandingan kedua opsi harus memenuhi enam syarat ini.
Pekerjaannya sama. Opsi B memeriksa nomor VA, status tagihan, dan pembayaran duplikat sebelum menjawab bank, sama seperti Opsi A.
Jalur dan bebannya sama. Adapter BSI, data awal, skrip k6, dan profil TPS yang sama, di mesin yang sama, dijalankan bergantian.
Kebenaran data diperiksa dari luar sistem. Isi database dicocokkan dengan log k6, bukan dihitung ulang dari tabel milik sistem yang diuji.
Pembayaran yang diterima dan ditolak dihitung terpisah. Adapter BSI menjawab keduanya dengan HTTP 200, sehingga angka "0% error" belum menunjukkan apa-apa.
Setiap angka bisa dilacak. Setiap angka di laporan berasal dari hasil run yang tersimpan.
Mesin uji bersih. Tidak ada proses lain yang berjalan saat pengujian.
Siapa Mengerjakan Apa
Bagian
Dikerjakan oleh
Opsi A, versi production
Claude Code
Opsi B, versi awal beserta skenario benchmark
Gemini Flash lewat Antigravity CLI
Audit, perbaikan, eksekusi benchmark
Claude Code
Seri ini turut merekam bagaimana satu AI agent menguji dan membenahi hasil kerja agent lain.
Laporan Perbandingan Pertama
Laporan ini ditulis di repo Opsi B, sebelum ada audit. Isinya membandingkan kedua opsi dan menyatakan keduanya diuji dalam kondisi identik.
Metrik
Opsi A
Opsi B
Median latency
0,57 ms
0,68 ms
p99
16,26 ms
18,45 ms
Request sukses
100%
100%
Pembayaran ganda
0
0
Audit keuangan
100% PASS
100% PASS
Selisihnya tipis dan wajar. Apakah angka ini bisa dipercaya?
scenarios/perf_benchmark_report.md §8.1 di repo payment-gateway-evtsrc, versi sebelum audit (commit b0cad3d).
Episode berikutnya
Klaim di laporan perbandingan pertama diperiksa satu per satu terhadap kode dan skrip ujinya
Enam syarat tadi dipakai sebagai daftar periksa
Angka mana yang masih bisa dipercaya setelah audit