Postgres LISTEN/NOTIFY: Jangan Langsung Tambah Queue Baru

Kalau app lu butuh background job, realtime update, atau stream kecil, banyak developer refleks langsung mikir: “Pakai Redis, Kafka, NATS, atau queue service aja.” Padahal kadang masalahnya belum sebesar itu.

Artikel ini ngebahas kapan PostgreSQL LISTEN/NOTIFY cukup jadi mekanisme notifikasi ringan, kapan dia mulai berbahaya, dan kenapa keputusan “tambah message broker” sebaiknya datang setelah lu paham bottleneck-nya. Intinya: Postgres bukan pengganti universal Kafka, tapi buat banyak produk tahap awal sampai menengah, Postgres yang sudah lu punya bisa jadi pilihan lebih waras daripada nambah komponen baru terlalu cepat.

Angle ini penting karena overengineering sering kelihatan profesional di awal, tapi mahal di maintenance. Stack yang lebih sedikit berarti lebih sedikit dashboard, credential, failure mode, dan hal yang harus lu debug jam 2 pagi.

Apa Itu PostgreSQL LISTEN/NOTIFY?

LISTEN/NOTIFY adalah fitur bawaan PostgreSQL untuk mengirim notifikasi antar proses yang terhubung ke database yang sama. Satu koneksi bisa menjalankan LISTEN channel, lalu koneksi lain bisa menjalankan NOTIFY channel, 'payload'. Listener akan menerima event ketika transaksi pengirim sudah commit.

Bahasa sederhananya: Postgres bisa bilang, “Eh, ada data baru di channel ini,” tanpa client harus ngecek tabel terus-terusan tiap beberapa milidetik.

Dokumentasi PostgreSQL menjelaskan beberapa detail penting: notifikasi baru dikirim setelah transaksi commit, payload default harus pendek, dan kalau data yang mau dikirim besar, lebih baik simpan data itu di tabel lalu kirim key/id-nya lewat NOTIFY. Ini bukan message queue lengkap. Ini mekanisme sinyal.

Masalah yang Sering Kejadian: Polling Terlalu Gampang, Tapi Bisa Mahal

Misalnya lu punya tabel jobs atau events. Worker perlu tahu kalau ada row baru. Cara paling gampang adalah polling:

SELECT * FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 10;

Setiap 1 detik, worker ngecek lagi. Kalau traffic kecil, ini mungkin baik-baik aja. Tapi saat worker banyak, interval makin rapat, atau user butuh response cepat, polling mulai jadi pajak tersembunyi.

  • Polling terlalu jarang bikin latency terasa lambat.
  • Polling terlalu sering bikin database sibuk jawab query kosong.
  • Polling dari banyak worker bisa bikin noise yang susah dibedain dari load asli.

Di sinilah LISTEN/NOTIFY menarik. Worker bisa menunggu sinyal. Saat ada job baru, transaction yang insert job bisa mengirim NOTIFY. Worker bangun, lalu baca data dari tabel utama.

Kenapa DBOS Bilang LISTEN/NOTIFY “Actually Scales”?

DBOS menerbitkan tulisan teknis berjudul Postgres LISTEN/NOTIFY Actually Scales. Mereka membahas reputasi buruk LISTEN/NOTIFY yang sering dianggap tidak scalable karena ada global lock saat transaksi yang memanggil NOTIFY commit.

Bagian menariknya: DBOS tidak bilang kritik itu salah total. Mereka justru menjelaskan sumber masalahnya. PostgreSQL perlu menjaga urutan notifikasi sesuai urutan commit transaksi. Untuk itu, ada mekanisme internal queue dan lock yang bisa membuat performa terasa aneh kalau pola pemakaiannya tidak pas.

Tapi mereka juga menunjukkan bahwa “ada tradeoff” tidak sama dengan “fitur ini pasti tidak bisa dipakai”. Dengan desain tertentu untuk Postgres-backed streams, DBOS mengklaim bisa mencapai sekitar 60K writes per second di satu server Postgres dengan latency skala milidetik.

Gua baca ini bukan sebagai ajakan “pakai LISTEN/NOTIFY untuk semua hal”. Gua bacanya sebagai reminder: reputasi sebuah fitur sering kalah detail implementasi. Sebelum nambah komponen, pahami dulu karakteristik workload lu.

LISTEN/NOTIFY Cocok Buat Apa?

LISTEN/NOTIFY cocok saat lu butuh sinyal ringan, bukan transport data besar. Contohnya:

  • Worker dikasih tahu bahwa ada job baru di tabel.
  • Dashboard admin perlu refresh data setelah event tertentu.
  • App internal perlu invalidasi cache kecil.
  • Proses backend perlu koordinasi sederhana antar service yang sama-sama pakai Postgres.
  • Prototype AI app perlu stream status kecil sebelum arsitekturnya matang.

Polanya biasanya begini: data utama tetap disimpan di tabel yang durable, sementara NOTIFY cuma membawa channel dan payload kecil, misalnya ID record. Listener menerima event, lalu query tabel untuk ambil data lengkap.

Ini pattern yang lebih sehat dibanding masukin JSON besar ke payload NOTIFY. PostgreSQL sendiri menyarankan pakai tabel untuk data tambahan kalau informasi yang dikirim besar atau terstruktur.

Kapan Lu Harus Mulai Cari Queue Beneran?

LISTEN/NOTIFY punya tempat, tapi jangan dipaksa jadi sistem yang bukan tugasnya. Lu perlu mulai mempertimbangkan Redis Streams, NATS, Kafka, RabbitMQ, SQS, atau queue lain kalau kebutuhan lu sudah masuk area seperti ini:

  • Durable consumer state yang kompleks: banyak consumer group, replay event, offset management, dan retry policy serius.
  • Fanout besar: satu event harus dikonsumsi banyak service dengan pola berbeda.
  • Backpressure jelas: lu butuh kontrol saat producer lebih cepat dari consumer.
  • Retention panjang: event harus bisa diputar ulang beberapa jam/hari/minggu kemudian.
  • Cross-service boundary: banyak service beda ownership tidak semuanya nyaman bergantung ke satu database yang sama.
  • Payload/event besar: event membawa data besar, bukan cuma key/id.

Kalau kebutuhan lu sudah kayak gitu, message broker bukan overengineering. Itu memang alat yang tepat. Tapi kalau kebutuhan lu cuma “worker perlu tahu ada row baru”, jangan langsung bikin stack terlihat enterprise hanya karena takut keliatan junior.

Checklist Postgres-First Sebelum Tambah Broker

Sebelum lu nambah queue baru, coba jawab ini dulu:

  1. Event-nya sinyal atau data? Kalau cuma sinyal, NOTIFY bisa cukup. Kalau data besar, simpan di tabel dan kirim ID.
  2. Kalau listener mati, apa yang terjadi? Pastikan data utama tetap bisa diproses ulang dari tabel.
  3. Butuh replay historis? Kalau iya, jangan bergantung ke NOTIFY doang.
  4. Berapa fanout-nya? Satu-dua worker beda dengan puluhan consumer group.
  5. Latency target berapa? Kalau cuma butuh “cukup cepat”, jangan desain seperti sistem trading.
  6. Sudah ukur polling cost? Jangan nebak. Lihat query rate, CPU, lock, dan latency.
  7. Ada observability? Minimal log event, jumlah pending row, waktu proses, dan error retry.

Checklist ini penting karena keputusan arsitektur yang bagus bukan yang paling keren, tapi yang paling sesuai dengan fase produk. Side project, SaaS kecil, internal tool, dan produk enterprise punya risk model yang beda.

Contoh Pattern yang Masuk Akal

Gua lebih suka pattern seperti ini untuk banyak kasus awal:

BEGIN;

INSERT INTO jobs (type, payload, status)
VALUES ('send_email', '{...}', 'pending')
RETURNING id;

NOTIFY jobs_changed, 'job_id';

COMMIT;

Worker yang sedang LISTEN jobs_changed dapat sinyal, lalu dia tetap membaca job pending dari tabel. Kalau NOTIFY kelewat karena worker restart, job-nya tidak hilang karena state utama ada di tabel. Worker bisa tetap melakukan scan periodik yang lebih santai sebagai safety net, misalnya tiap 30–60 detik, bukan polling agresif terus-menerus.

Jadi NOTIFY dipakai untuk menurunkan latency, bukan sebagai satu-satunya sumber kebenaran.

Kesalahan yang Sering Bikin LISTEN/NOTIFY Jadi Jelek

  • Mengirim payload besar lewat NOTIFY.
  • Menganggap NOTIFY sebagai queue durable penuh.
  • Tidak punya fallback scan saat listener restart.
  • Membuat transaksi panjang yang menahan delivery notifikasi.
  • Tidak mengukur queue, pending job, dan processing latency.
  • Menaruh semua event domain production ke satu channel tanpa desain consumer yang jelas.

Kalau pattern lu berantakan, broker apa pun bisa tetap kacau. Kafka tidak otomatis memperbaiki domain model yang tidak jelas. Redis tidak otomatis membuat retry policy lu benar. Tool bagus tetap butuh desain yang jujur.

Takeaway Buat Developer

Pelajaran utama dari topik ini sederhana: jangan tambah infrastruktur cuma karena takut fitur bawaan terlihat terlalu sederhana.

Postgres LISTEN/NOTIFY bukan peluru perak. Tapi dia juga bukan fitur mainan. Buat sinyal ringan, worker wake-up, cache invalidation kecil, dan workflow produk tahap awal, dia bisa jadi opsi yang sangat masuk akal.

Yang paling penting: desain state utama tetap durable, kirim payload kecil, punya fallback, ukur bottleneck, lalu baru tambah broker ketika kebutuhan lu memang sudah sampai sana.

Kalau artikel ini ngebantu, simpan buat nanti pas lu lagi debat “perlu queue baru atau belum” di project. Follow rafli.dev kalau lu suka breakdown tech yang praktis, jujur, dan nggak cuma ikut hype stack terbaru.

FAQ Mini

Apakah LISTEN/NOTIFY bisa menggantikan Kafka?

Tidak untuk use case Kafka yang sebenarnya: event streaming besar, retention panjang, replay, consumer group kompleks, dan throughput lintas service yang serius. LISTEN/NOTIFY lebih cocok sebagai sinyal ringan di sekitar Postgres.

Apakah NOTIFY aman untuk payload JSON besar?

Sebaiknya jangan. Dokumentasi PostgreSQL menyebut payload default harus pendek, dan untuk data besar lebih baik simpan di tabel lalu kirim key/id lewat NOTIFY.

Kalau listener mati, apakah event hilang?

Notifikasi bukan pengganti state durable. Karena itu pattern yang lebih aman adalah simpan job/event di tabel, lalu pakai NOTIFY hanya untuk membangunkan worker. Worker tetap harus bisa scan ulang tabel.

Kapan broker eksternal mulai layak?

Saat lu butuh replay, retention panjang, consumer group, backpressure kuat, fanout besar, atau boundary antar service yang tidak lagi nyaman bergantung ke satu database.

Referensi / Bacaan Lanjut