PostgreSQL LISTEN/NOTIFY dapat mendukung dasbor dan notifikasi langsung dengan pengaturan minimal. Pelajari kapan cocok digunakan, batasannya, dan kapan menambahkan broker.

"Pembaruan langsung" di UI produk biasanya berarti layar berubah segera setelah sesuatu terjadi, tanpa pengguna melakukan refresh. Angka bertambah di dasbor, badge merah muncul di kotak masuk, admin melihat pesanan baru, atau muncul toast yang mengatakan "Build finished" atau "Payment failed". Intinya adalah timing: terasa instan, meskipun sebenarnya satu atau dua detik.
Banyak tim memulai dengan polling: browser menanyakan server “ada yang baru?” setiap beberapa detik. Polling bekerja, tapi punya dua kelemahan umum.
Pertama, terasa lambat karena pengguna hanya melihat perubahan pada poll berikutnya.
Kedua, bisa jadi mahal karena Anda melakukan cek berulang meskipun tidak ada perubahan. Kalikan itu dengan ribuan pengguna dan itu menjadi kebisingan.
PostgreSQL LISTEN/NOTIFY ada untuk kasus yang lebih sederhana: "beri tahu saya saat sesuatu berubah." Alih-alih menanyakan berulang-ulang, aplikasi Anda bisa menunggu dan bereaksi ketika database mengirim sinyal kecil.
Ini cocok untuk UI di mana sebuah dorongan saja sudah cukup. Contohnya:
Pertukaran yang terjadi adalah kesederhanaan versus jaminan. LISTEN/NOTIFY mudah ditambahkan karena sudah ada di Postgres, tetapi bukan sistem messaging penuh. Notifikasi adalah petunjuk, bukan catatan tahan lama. Jika listener terputus, dia mungkin melewatkan sinyal.
Cara praktis menggunakannya: biarkan NOTIFY membangunkan aplikasi Anda, lalu biarkan aplikasi membaca kebenaran dari tabel.
Anggap PostgreSQL LISTEN/NOTIFY sebagai bel pintu sederhana yang tertanam di database Anda. Aplikasi Anda bisa menunggu bel itu berbunyi, dan bagian lain dari sistem bisa membunyikannya saat sesuatu berubah.
Notifikasi punya dua bagian: nama channel dan payload opsional. Channel seperti label topik (misalnya orders_changed). Payload adalah pesan teks singkat yang Anda lampirkan (misalnya id order). PostgreSQL tidak memaksa struktur, jadi tim sering mengirim string JSON kecil.
Notifikasi bisa dipicu dari kode aplikasi (API Anda menjalankan NOTIFY) atau dari database itu sendiri menggunakan trigger (trigger menjalankan NOTIFY setelah insert/update/delete).
Di sisi penerima, server aplikasi membuka koneksi database dan menjalankan LISTEN channel_name. Koneksi itu tetap terbuka. Ketika NOTIFY channel_name, 'payload' terjadi, PostgreSQL mendorong pesan ke semua koneksi yang mendengarkan channel itu. Aplikasi Anda lalu bereaksi (menyegarkan cache, mengambil baris yang berubah, mendorong event WebSocket ke browser, dan seterusnya).
NOTIFY paling mudah dipahami sebagai sinyal, bukan layanan pengantaran:
Dengan penggunaan seperti ini, PostgreSQL LISTEN/NOTIFY bisa memberi kekuatan pada pembaruan UI langsung tanpa menambah infrastruktur ekstra.
LISTEN/NOTIFY bersinar ketika UI Anda hanya butuh dorongan bahwa sesuatu berubah, bukan aliran event penuh. Pikirkan "segar ulang widget ini" atau "ada item baru" daripada "proses setiap klik secara berurutan."
Ini bekerja terbaik ketika database sudah menjadi sumber kebenaran Anda dan Anda ingin UI tetap sinkron dengannya. Pola umum: tulis baris, kirim notifikasi kecil yang berisi ID, dan biarkan UI (atau API) mengambil status terbaru.
LISTEN/NOTIFY biasanya cukup ketika sebagian besar kondisi ini terpenuhi:
Contoh konkret: dashboard internal menunjukkan "tiket terbuka" dan badge untuk "catatan baru." Ketika agen menambahkan catatan, backend menulisnya ke Postgres dan NOTIFY ticket_changed dengan ID tiket. Browser menerimanya lewat koneksi WebSocket dan mengambil kembali satu kartu tiket itu. Tidak perlu infrastruktur tambahan, dan UI terasa langsung.
LISTEN/NOTIFY bisa terasa hebat pada awalnya, tapi memiliki batas keras. Batas itu muncul ketika Anda memperlakukan notifikasi seperti sistem pesan alih-alih ketukan ringan di bahu.
Kesenjangan terbesar adalah durabilitas. NOTIFY bukan pekerjaan antrean. Jika tidak ada yang mendengarkan pada saat itu, pesan akan terlewat. Bahkan saat listener terhubung, crash, deploy, gangguan jaringan, atau restart database bisa memutus koneksi. Anda tidak akan otomatis mendapatkan kembali notifikasi yang terlewat.
Disconnect sangat menyakitkan untuk fitur yang berhadapan dengan pengguna. Bayangkan dashboard yang menunjukkan pesanan baru. Tab browser tidur, WebSocket reconnects, dan UI terasa "stuck" karena melewatkan beberapa event. Anda bisa mengakali ini, tapi solusi itu bukan lagi "hanya LISTEN/NOTIFY": Anda membangun kembali state dengan query ke database dan menggunakan NOTIFY hanya sebagai petunjuk untuk menyegarkan.
Fan-out adalah masalah umum lainnya. Satu event dapat membangunkan ratusan atau ribuan listener (banyak instance app, banyak pengguna). Jika Anda menggunakan satu channel berisik seperti orders, setiap listener terbangun meskipun hanya satu pengguna yang peduli. Itu bisa menciptakan lonjakan CPU dan tekanan koneksi pada waktu terburuk.
Ukuran payload dan frekuensi adalah jebakan akhir. Payload NOTIFY kecil, dan event frekuensi tinggi bisa menumpuk lebih cepat daripada yang bisa ditangani klien.
Waspadai tanda-tanda ini:
Pada titik itu, tetap gunakan NOTIFY sebagai "poke," dan pindahkan keandalan ke tabel atau broker pesan yang tepat.
Polanya adalah menjadikan NOTIFY sebagai dorongan, bukan sumber kebenaran. Baris database adalah kebenaran; notifikasi memberi tahu aplikasi kapan harus melihat.
Lakukan penulisan dalam transaksi, dan hanya kirim notifikasi setelah perubahan data dikomit. Jika Anda notify terlalu dini, klien bisa terbangun dan tidak menemukan data.
Setup umum adalah trigger yang dipicu pada INSERT/UPDATE dan mengirim pesan kecil.
NOTIFY dashboard_updates, '{\\\"type\\\":\\\"order_changed\\\",\\\"order_id\\\":123}'::text;
Penamaan channel bekerja paling baik ketika cocok dengan cara orang berpikir tentang sistem. Contoh: dashboard_updates, user_notifications, atau per-tenant seperti tenant_42_updates.
Jaga payload tetap kecil. Letakkan identifier dan tipe, bukan record penuh. Bentuk default yang berguna:
type (apa yang terjadi)id (apa yang berubah)tenant_id atau user_idIni menjaga bandwidth rendah dan menghindari kebocoran data sensitif ke log notifikasi.
Koneksi bisa putus. Rencanakan itu.
Saat connect, jalankan LISTEN untuk semua channel yang Anda butuhkan. Saat disconnect, reconnect dengan backoff singkat. Saat reconnect, LISTEN lagi (langganan tidak bertahan). Setelah reconnect, lakukan refetch cepat dari "perubahan terbaru" untuk menutup kemungkinan event yang terlewat.
Untuk sebagian besar pembaruan UI langsung, refetch adalah langkah paling aman: klien menerima {type, id} lalu meminta server untuk keadaan terbaru.
Patch incremental bisa lebih cepat, tapi lebih mudah salah (event datang berurutan salah, kegagalan sebagian). Jalan tengah yang baik: refetch potongan kecil (satu baris order, satu kartu tiket, satu hitungan badge) dan biarkan agregat berat pada timer singkat.
Ketika Anda bergerak dari satu dasbor admin ke banyak pengguna yang menonton angka yang sama, kebiasaan baik lebih penting daripada SQL pintar. LISTEN/NOTIFY masih bisa bekerja dengan baik, tetapi Anda perlu membentuk aliran event dari database ke browser.
Baseline umum: setiap instance app membuka satu koneksi jangka panjang yang LISTEN, lalu mendorong update ke klien yang terhubung. Setup "satu listener per instance" ini sederhana dan sering cukup jika jumlah server app kecil dan Anda toleran terhadap reconnect sesekali.
Jika Anda punya banyak instance app (atau worker serverless), layanan listener bersama bisa lebih mudah. Satu proses kecil mendengarkan sekali, lalu fans out update ke sisa stack Anda. Ini juga memberi satu tempat untuk menambahkan batching, metrik, dan backpressure.
Untuk browser, biasanya Anda mendorong dengan WebSockets (dua arah, cocok untuk UI interaktif) atau Server-Sent Events (SSE) (satu arah, lebih sederhana untuk dasbor). Apapun pilihannya, hindari mengirim "refresh semuanya." Kirim sinyal ringkas seperti "order 123 changed" sehingga UI hanya mengambil apa yang diperlukan.
Untuk mencegah UI thrash, tambahkan beberapa pengaman:
Desain channel juga penting. Alih-alih satu channel global, bagi berdasarkan tenant, tim, atau fitur sehingga klien hanya menerima event relevan. Contoh: notify:tenant_42:billing dan notify:tenant_42:ops.
LISTEN/NOTIFY terasa sederhana, itulah sebabnya tim sering cepat merilisnya dan kemudian kaget di produksi. Sebagian besar masalah muncul dari memperlakukannya seperti antrean pesan yang tahan lama.
Jika aplikasi Anda reconnect (deploy, gangguan jaringan, failover DB), setiap NOTIFY yang dikirim saat Anda terputus akan hilang. Perbaikan: jadikan notifikasi sinyal, lalu periksa ulang database.
Polanya: simpan event nyata di tabel (dengan id dan created_at), lalu saat reconnect ambil apa pun yang lebih baru dari id terakhir yang Anda lihat.
Payload LISTEN/NOTIFY tidak untuk blob JSON besar. Payload besar menambah kerja, parsing, dan peluang terkena limit.
Gunakan payload hanya untuk petunjuk kecil seperti "order:123". Lalu aplikasi membaca state terbaru dari database.
Kesalahan umum adalah mendesain UI berdasarkan isi payload, seolah-olah itu sumber kebenaran. Itu membuat perubahan skema dan versi klien jadi menyulitkan.
Pecah dengan jelas: beri tahu bahwa sesuatu berubah, lalu ambil data saat ini dengan query normal.
Trigger yang NOTIFY pada setiap perubahan baris bisa membanjiri sistem Anda, terutama pada tabel sibuk.
Notify hanya pada transisi yang bermakna (misalnya perubahan status). Jika update sangat bising, batch perubahan (satu notify per transaksi atau per window waktu) atau pindahkan update itu dari jalur notify.
Meski database bisa mengirim notifikasi, UI tetap bisa kewalahan. Dasbor yang merender ulang pada setiap event bisa membeku.
Debounce update di klien, gabungkan burst menjadi satu refresh, dan lebih suka "invalidate and refetch" daripada "terapkan setiap delta." Contoh: lonceng notifikasi bisa update instan, tapi dropdown daftar sebaiknya refresh maksimal setiap beberapa detik.
LISTEN/NOTIFY bagus ketika Anda ingin sinyal kecil "sesuatu berubah" sehingga aplikasi bisa mengambil data segar. Bukan sistem messaging penuh.
Sebelum membangun UI di sekitarnya, jawab pertanyaan ini:
Aturan praktis: jika Anda bisa memperlakukan NOTIFY sebagai dorongan ("pergi baca ulang baris") daripada sebagai payload itu sendiri, Anda berada di zona aman.
Contoh: dashboard admin menunjukkan pesanan baru. Jika notifikasi terlewat, poll berikutnya atau refresh halaman masih menunjukkan jumlah yang benar. Itu cocok. Tetapi jika Anda mengirim event seperti "charge this card" atau "ship this package", melewatkannya bisa menyebabkan insiden nyata.
Bayangkan aplikasi penjualan kecil: dasbor menunjukkan pendapatan hari ini, total pesanan, dan daftar "pesanan terbaru." Pada saat yang sama, setiap tenaga penjual harus mendapat notifikasi cepat ketika pesanan mereka dibayar atau dikirim.
Pendekatan sederhana adalah menjadikan PostgreSQL sumber kebenaran, dan menggunakan LISTEN/NOTIFY hanya sebagai ketukan di bahu bahwa sesuatu berubah.
Saat pesanan dibuat atau statusnya berubah, backend melakukan dua hal dalam satu permintaan: menulis atau memperbarui baris lalu mengirim NOTIFY dengan payload kecil (sering hanya ID pesanan dan tipe event). UI tidak bergantung pada payload NOTIFY untuk data lengkap.
Alur praktisnya:
orders_events dengan {\\\"type\\\":\\\"status_changed\\\",\\\"order_id\\\":123}.Ini menjaga NOTIFY ringan dan membatasi query mahal.
Saat trafik bertambah, retak mulai muncul: lonjakan event dapat membanjiri satu listener, notifikasi bisa terlewat saat reconnect, dan Anda mulai butuh pengantaran terjamin dan replay. Biasanya saat itulah Anda menambahkan lapisan lebih andal (outbox table plus worker, lalu broker jika perlu) sambil mempertahankan Postgres sebagai sumber kebenaran.
LISTEN/NOTIFY bagus bila Anda perlu sinyal cepat "sesuatu berubah." Bukan untuk sistem messaging penuh. Ketika Anda mulai bergantung pada event sebagai sumber kebenaran, saatnya menambahkan broker.
Jika salah satu ini muncul, broker akan menghemat masalah:
LISTEN/NOTIFY tidak menyimpan pesan untuk nanti. Ini sinyal push, bukan log yang dipertahankan. Itu sempurna untuk "segar ulang widget dasbor," tapi berisiko untuk "trigger billing" atau "ship this package."
Broker memberi model aliran pesan nyata: antrean (pekerjaan yang harus dilakukan), topik (broadcast ke banyak), retensi (simpan pesan beberapa menit hingga hari), dan acknowledgment (consumer mengonfirmasi pemrosesan). Itu memungkinkan Anda memisahkan "database berubah" dari "segala sesuatu yang harus terjadi karena itu berubah."
Anda tidak harus memilih alat paling kompleks. Opsi umum yang dievaluasi orang: Redis (pub/sub atau streams), NATS, RabbitMQ, dan Kafka. Pilihan terbaik bergantung pada apakah Anda butuh antrean kerja sederhana, fan-out ke banyak layanan, atau kemampuan untuk memutar ulang riwayat.
Anda bisa pindah tanpa rewrite besar. Pola praktis: tetap gunakan NOTIFY sebagai sinyal sambil membuat broker menjadi sumber pengantaran.
Mulai dengan menulis "event row" ke tabel dalam transaksi yang sama dengan perubahan bisnis Anda, lalu worker mem-publish event itu ke broker. Selama transisi, NOTIFY tetap memberi tahu lapisan UI "cek event baru," sementara worker latar mengonsumsi dari broker dengan retry dan auditing.
Dengan cara ini, dasbor tetap responsif, dan alur kerja kritis berhenti bergantung pada notifikasi best-effort.
Pilih satu layar (ubin dasbor, jumlah badge, toast "notifikasi baru") dan sambungkan end-to-end. Dengan LISTEN/NOTIFY Anda bisa mendapatkan hasil berguna dengan cepat, asalkan menjaga ruang lingkup sempit dan mengukur apa yang terjadi di lalu lintas nyata.
Mulai dengan pola andal paling sederhana: tulis baris, commit, lalu kirim sinyal kecil bahwa sesuatu berubah. Di UI, reaksi terhadap sinyal dengan mengambil keadaan terbaru (atau potongan yang Anda butuhkan). Ini menjaga payload kecil dan menghindari bug halus saat pesan datang tidak berurutan.
Tambahkan observabilitas dasar sejak awal. Anda tidak perlu alat mahal untuk memulai, tetapi Anda perlu jawaban saat sistem menjadi bising:
Pertahankan kontrak sederhana dan tertulis. Putuskan nama channel, nama event, dan bentuk payload (meskipun hanya ID). "Katalog event" singkat di repo Anda mencegah drift.
Jika Anda membangun cepat dan ingin menjaga stack sederhana, platform seperti Koder.ai bisa membantu Anda mengirim versi pertama dengan UI React, backend Go, dan PostgreSQL, lalu iterasi saat kebutuhan Anda semakin jelas.
Gunakan LISTEN/NOTIFY ketika Anda hanya perlu sinyal singkat bahwa sesuatu berubah, misalnya untuk menyegarkan jumlah badge atau ubin dasbor. Anggap notifikasi sebagai dorongan untuk mengambil kembali data nyata dari tabel, bukan sebagai sumber datanya.
Polling memeriksa perubahan secara berkala, jadi pengguna sering melihat pembaruan terlambat dan server melakukan pekerjaan meskipun tak ada perubahan. LISTEN/NOTIFY mendorong sinyal kecil tepat saat perubahan terjadi, yang biasanya terasa lebih cepat dan mengurangi banyak permintaan kosong.
Tidak, ini best-effort. Jika listener terputus saat NOTIFY dikirim, listener itu bisa melewatkan sinyal karena notifikasi tidak disimpan untuk diputar ulang nanti.
Jaga ukurannya kecil dan anggap sebagai petunjuk. Default praktisnya adalah JSON kecil dengan type dan id, lalu aplikasi Anda query Postgres untuk keadaan terkini.
Polanya umum adalah mengirim notifikasi setelah perubahan data dikomit. Jika Anda memberi notifikasi terlalu awal, klien bisa terbangun dan tidak menemukan baris baru yang belum dikomit.
Kode aplikasi biasanya lebih mudah dipahami dan dites karena eksplisit. Trigger berguna ketika banyak penulis menyentuh tabel yang sama dan Anda mau perilaku konsisten tanpa tergantung siapa yang melakukan perubahan.
Rencanakan reconnect sebagai perilaku normal. Saat reconnect, jalankan LISTEN lagi untuk semua channel yang diperlukan dan lakukan refetch cepat dari keadaan terbaru untuk menutup celah apa pun yang mungkin terlewat saat offline.
Jangan biarkan setiap browser terhubung ke Postgres. Setup umum: satu koneksi listener panjang per instance backend, lalu backend meneruskan event ke browser melalui WebSocket atau SSE dan UI mengambil data yang diperlukan.
Gunakan channel yang lebih sempit supaya hanya konsumen yang relevan yang terbangun, dan batch lonjakan yang bising. Debounce beberapa ratus milidetik dan gabungkan update duplikat untuk mencegah UI dan backend thrash.
Beralih saat Anda membutuhkan durabilitas, retry, grup konsumen, jaminan urutan, atau audit/putar ulang. Jika melewatkan event bisa menyebabkan insiden nyata (penagihan, pengiriman), gunakan outbox + worker atau broker khusus daripada hanya NOTIFY.