PostgreSQL LISTEN/NOTIFY può alimentare dashboard live e avvisi con setup minimo. Scopri dove è adatto, i suoi limiti e quando aggiungere un broker.

"Aggiornamenti live" in un'interfaccia prodotto di solito significa che lo schermo cambia poco dopo che qualcosa è successo, senza che l'utente ricarichi. Un numero aumenta in una dashboard, appare un badge rosso nella casella, un admin vede un nuovo ordine o compare un toast che dice "Build finished" o "Payment failed". La chiave è il tempo: sembra istantaneo, anche se in realtà può essere uno o due secondi.
Molte squadre partono con il polling: il browser chiede al server "ci sono novità?" ogni pochi secondi. Il polling funziona, ma ha due svantaggi comuni.
Primo, sembra lento perché l'utente vede i cambiamenti solo alla prossima richiesta.
Secondo, può diventare costoso perché ripeti controlli anche quando nulla è cambiato. Moltiplica per migliaia di utenti e diventa rumore.
PostgreSQL LISTEN/NOTIFY esiste per un caso più semplice: "avvisami quando qualcosa è cambiato." Invece di chiedere continuamente, la tua app può aspettare e reagire quando il database invia un piccolo segnale.
È adatto per interfacce dove basta un richiamo. Per esempio:
Lo scambio è semplicità vs garanzie. LISTEN/NOTIFY è facile da aggiungere perché è già in Postgres, ma non è un sistema di messaggistica completo. La notifica è un suggerimento, non un record durevole. Se un listener è disconnesso, potrebbe perdere il segnale.
Un modo pratico per usarlo: lascia che NOTIFY svegli la tua app, poi fai leggere alla tua app la verità dalle tabelle.
Pensa a PostgreSQL LISTEN/NOTIFY come a un piccolo campanello integrato nel database. La tua app può aspettare che suoni, e un'altra parte del sistema può suonarlo quando qualcosa cambia.
Una notifica ha due parti: un nome di canale e un payload opzionale. Il canale è come un'etichetta di argomento (per esempio orders_changed). Il payload è un breve messaggio testuale che alleghi (per esempio un id ordine). PostgreSQL non impone alcuna struttura, quindi spesso si inviano piccole stringhe JSON.
Una notifica può essere attivata dal codice applicativo (il tuo server API esegue NOTIFY) o dal database stesso tramite un trigger (un trigger esegue NOTIFY dopo un insert/update/delete).
Sul lato ricevente, il tuo server apre una connessione al database ed esegue LISTEN channel_name. Quella connessione rimane aperta. Quando avviene NOTIFY channel_name, 'payload', PostgreSQL spinge un messaggio a tutte le connessioni che stanno ascoltando quel canale. La tua app reagisce (aggiorna cache, legge la riga cambiata, invia un evento WebSocket al browser, ecc.).
NOTIFY è meglio inteso come un segnale, non come un servizio di consegna:
Usato in questo modo, PostgreSQL LISTEN/NOTIFY può alimentare aggiornamenti live dell'interfaccia senza aggiungere infrastruttura extra.
LISTEN/NOTIFY brilla quando la tua UI ha bisogno solo di un promemoria che qualcosa è cambiato, non di uno stream completo di eventi. Pensa a "aggiorna questo widget" o "c'è un nuovo elemento" piuttosto che a "processa ogni click in ordine."
Funziona meglio quando il database è già la fonte di verità e vuoi che l'interfaccia rimanga sincronizzata. Un pattern comune è: scrivi la riga, invia una piccola notifica con un ID e lascia che la UI (o un'API) recuperi lo stato più aggiornato.
LISTEN/NOTIFY è di solito sufficiente quando la maggior parte di questi punti è vera:
Un esempio concreto: una dashboard interna mostra "ticket aperti" e un badge per "nuove note." Quando un agente aggiunge una nota, il backend la scrive in Postgres e NOTIFYa ticket_changed con l'ID del ticket. Il browser lo riceve via WebSocket e rifetch della singola scheda del ticket. Nessuna infrastruttura extra e la UI sembra comunque live.
LISTEN/NOTIFY può sembrare ottimo all'inizio, ma ha limiti netti. Questi emergono quando tratti le notifiche come un sistema di messaggistica invece che come un leggero "colpetto sulla spalla."
Il gap più grande è la durabilità. Un NOTIFY non è un job in coda. Se nessuno sta ascoltando in quel momento, il messaggio va perso. Anche quando un listener è connesso, un crash, un deploy, un problema di rete o un riavvio del database può interrompere la connessione. Non riceverai automaticamente le notifiche "perse".
Le disconnessioni sono particolarmente problematiche per le feature rivolte agli utenti. Immagina una dashboard che mostra nuovi ordini. Una scheda del browser entra in sleep, il WebSocket si riconnette e la UI sembra "bloccata" perché ha perso alcuni eventi. Puoi aggirare il problema, ma la soluzione non è più "solo LISTEN/NOTIFY": ricostruisci lo stato interrogando il database e usa NOTIFY solo come suggerimento per aggiornare.
Il fan-out è un altro problema comune. Un evento può svegliare centinaia o migliaia di listener (molti server app, molti utenti). Se usi un canale rumoroso come orders, ogni listener si sveglia anche se solo un utente si interessa. Questo può creare picchi di CPU e pressione sulle connessioni nei momenti peggiori.
La dimensione del payload e la frequenza sono gli ultimi tranelli. I payload di NOTIFY sono piccoli e gli eventi ad alta frequenza possono accumularsi più velocemente di quanto i client possano gestire.
Fai attenzione a questi segnali:
A quel punto, mantieni NOTIFY come un "promemoria" e sposta l'affidabilità su una tabella o su un message broker appropriato.
Un pattern affidabile con LISTEN/NOTIFY è trattare NOTIFY come un richiamo, non come sorgente di verità. La riga nel database è la verità; la notifica dice alla tua app quando guardare.
Esegui la scrittura dentro una transazione e manda la notifica solo dopo che la modifica è stata commessa. Se notifichi troppo presto, i client possono svegliarsi e non trovare i dati.
Una configurazione comune è un trigger che scatta su INSERT/UPDATE e invia un piccolo messaggio.
NOTIFY dashboard_updates, '{\\"type\\":\\"order_changed\\",\\"order_id\\":123}'::text;
La nomenclatura dei canali funziona meglio quando corrisponde al modo in cui le persone pensano al sistema. Esempi: dashboard_updates, user_notifications, o per tenant come tenant_42_updates.
Mantieni il payload piccolo. Metti identificatori e un tipo, non record completi. Una forma utile di default è:
type (cosa è successo)id (cosa è cambiato)tenant_id o user_idQuesto mantiene bassa la banda e evita di esporre dati sensibili nei log delle notifiche.
Le connessioni cadono. Pianificalo.
Al collegamento, esegui LISTEN per tutti i canali necessari. Alla disconnessione, riconnettiti con un breve backoff. Al riconnetterti, esegui di nuovo LISTEN (le sottoscrizioni non persistono). Dopo il reconnect, fai un rapido refetch delle "modifiche recenti" per coprire eventi persi.
Per la maggior parte degli aggiornamenti live, rifetchare è la scelta più sicura: il client riceve {type, id} e poi chiede al server lo stato più recente.
Il patching incrementale può essere più veloce, ma è più facile sbagliare (eventi fuori ordine, failure parziali). Un buon compromesso è: rifetch di piccoli slice (una riga ordine, una card ticket, un conteggio badge) e lasciare le aggregazioni pesanti su un timer breve.
Quando passi da una dashboard admin a molti utenti che guardano gli stessi numeri, le buone pratiche contano più delle SQL intelligenti. LISTEN/NOTIFY può ancora funzionare bene, ma devi modellare come gli eventi fluiscono dal database ai browser.
Una baseline comune è: ogni istanza app apre una connessione a lunga durata che LISTENa, poi spinge aggiornamenti ai client connessi. Questo "un listener per istanza" è semplice e spesso va bene se hai poche istanze app e tolleri disconnessioni occasionali.
Se hai molte istanze app (o worker serverless), un servizio listener condiviso può essere più semplice. Un piccolo processo ascolta una volta sola e poi effettua il fan-out agli altri componenti. Ti dà anche un posto unico per aggiungere batching, metriche e backpressure.
Per i browser, solitamente usi WebSocket (bidirezionale, ottimo per UI interattive) o Server-Sent Events (SSE) (one-way, più semplice per dashboard). In ogni caso, evita di inviare "refresh everything." Invia segnali compatti come "order 123 changed" così la UI rifetch solo ciò che serve.
Per evitare che la UI vada in thrash, aggiungi alcune protezioni:
Anche il design dei canali conta. Invece di un canale globale, partiziona per tenant, team o feature così i client ricevono solo eventi rilevanti. Esempio: notify:tenant_42:billing e notify:tenant_42:ops.
LISTEN/NOTIFY sembra semplice, per questo le squadre lo mettono subito in produzione e poi si sorprendono. La maggior parte dei problemi nasce dal trattarlo come una coda di messaggi garantita.
Se la tua app si riconnette (deploy, problema di rete, failover DB), qualsiasi NOTIFY inviato mentre eri disconnesso è andato. La soluzione è fare della notifica un segnale, poi ricontrollare il database.
Un pattern pratico: memorizza l'evento reale in una tabella (con id e created_at), poi al reconnect recupera tutto più recente dell'ultimo id visto.
I payload di LISTEN/NOTIFY non sono fatti per grossi blob JSON. Payload grandi creano lavoro extra, più parsing e più possibilità di superare limiti.
Usa payload per piccoli suggerimenti come "order:123". Poi l'app legge lo stato dal database.
Un errore comune è progettare la UI intorno al contenuto del payload, come se fosse la fonte di verità. Questo rende complicati cambi di schema e versioni client.
Mantieni una separazione chiara: notifica che qualcosa è cambiato, poi recupera i dati attuali con una query normale.
I trigger che NOTIFY su ogni cambiamento di riga possono allagare il sistema, specialmente per tabelle molto attive.
Notifica solo su transizioni significative (per esempio cambi di stato). Se hai update molto rumorosi, batcha le modifiche (un notify per transazione o per finestra temporale) o sposta quegli update fuori dal percorso di notify.
Anche se il database può inviare notifiche, la tua UI può comunque soffocare. Una dashboard che si re-rendera ad ogni evento può bloccarsi.
Debounce gli aggiornamenti sul client, collassa i burst in un solo refresh e preferisci "invalidate and refetch" piuttosto che applicare ogni delta. Per esempio: la campanella di notifica può aggiornarsi subito, ma il dropdown può rinfrescarsi al massimo una volta ogni pochi secondi.
LISTEN/NOTIFY è ottimo quando vuoi un piccolo segnale "qualcosa è cambiato" così l'app può recuperare dati freschi. Non è un sistema di messaggistica completo.
Prima di costruire la UI attorno a esso, rispondi a queste domande:
Una regola pratica: se puoi trattare NOTIFY come un colpetto ("vai a rileggere la riga") piuttosto che come il payload stesso, sei in una zona sicura.
Esempio: una dashboard admin mostra nuovi ordini. Se una notifica viene persa, il prossimo polling o il refresh della pagina mostrerà comunque il conteggio corretto. È un buon fit. Ma se stai inviando eventi tipo "addebita questa carta" o "spedisci il pacco", perderne uno è un incidente serio.
Immagina una piccola app di vendite: una dashboard mostra il fatturato di oggi, il totale ordini e una lista di "ordini recenti". Allo stesso tempo, ogni venditore deve ricevere una notifica rapida quando un ordine di sua proprietà viene pagato o spedito.
Un approccio semplice è trattare PostgreSQL come fonte di verità e usare LISTEN/NOTIFY solo come il tocco sulla spalla che qualcosa è cambiato.
Quando un ordine è creato o cambia stato, il backend fa due cose in una richiesta: scrive la riga (o la aggiorna) e poi invia un NOTIFY con un payload piccolo (spesso solo l'ID ordine e il tipo di evento). La UI non si basa sul payload di NOTIFY per i dati completi.
Un flusso pratico è:
orders_events con {\\"type\\":\\"status_changed\\",\\"order_id\\":123}.Questo mantiene NOTIFY leggero e limita query costose.
Quando il traffico cresce, emergono le crepe: un picco di eventi può sovraccaricare un singolo listener, le notifiche possono essere perse al reconnect e inizi a necessitare consegna garantita e replay. Di solito è il momento di aggiungere uno strato più affidabile (una tabella outbox più un worker, poi un broker se necessario) mantenendo Postgres come fonte di verità.
LISTEN/NOTIFY è ottimo per un rapido segnale "qualcosa è cambiato". Non è progettato per essere un sistema di messaggistica completo. Quando inizi a dipendere dagli eventi come fonte di verità, è ora di aggiungere un broker.
Se appare uno di questi, un broker ti risparmierà problemi:
LISTEN/NOTIFY non conserva i messaggi per dopo. È un push signal, non un log persistente. Perfetto per "aggiorna questa tessera della dashboard", rischioso per "esegui fatturazione" o "spedisci questo pacco."
Un broker ti dà un modello di flusso reale: code (lavoro da fare), topic (broadcast a molti), retention (mantieni messaggi per minuti o giorni) e acknowledgments (il consumer conferma l'elaborazione). Così separi "il database è cambiato" da "tutto ciò che deve succedere perché è cambiato."
Non devi scegliere lo strumento più complesso. Opzioni comuni sono Redis (pub/sub o streams), NATS, RabbitMQ e Kafka. La scelta dipende dal bisogno di code di lavoro semplici, fan-out a molti servizi o la possibilità di replay della storia.
Puoi migrare senza riscrivere tutto. Un pattern pratico è mantenere NOTIFY come wake-up mentre il broker diventa la fonte di consegna.
Inizia scrivendo una "riga evento" in una tabella nella stessa transazione della modifica di business, poi fai sì che un worker pubblichi quell'evento sul broker. Durante la transizione, NOTIFY può ancora dire al layer UI "controlla nuovi eventi", mentre i worker di background consumano dal broker con retry e auditing.
Così le dashboard restano reattive e i workflow critici smettono di dipendere da notifiche best-effort.
Scegli una schermata (una tessera della dashboard, un conteggio badge, un toast "nuova notifica") e collegala end-to-end. Con LISTEN/NOTIFY puoi ottenere un risultato utile rapidamente, purché limiti l'ambito e misuri cosa succede sotto traffico reale.
Inizia con il pattern più semplice e affidabile: scrivi la riga, committa, poi emetti un piccolo segnale che qualcosa è cambiato. In UI, reagisci al segnale recuperando lo stato più aggiornato (o la slice che ti serve). Questo mantiene i payload piccoli ed evita bug sottili quando i messaggi arrivano fuori ordine.
Aggiungi osservabilità di base presto. Non servono strumenti sofisticati per cominciare, ma hai bisogno di risposte quando il sistema diventa rumoroso:
Mantieni i contratti noiosi e documentati. Decidi nomi dei canali, nomi degli eventi e la forma di qualsiasi payload (anche se è solo un ID). Un piccolo "catalogo eventi" nel repo previene la deriva.
Se stai costruendo velocemente e vuoi mantenere lo stack semplice, una piattaforma come Koder.ai (koder.ai) può aiutarti a rilasciare la prima versione con una UI React, un backend Go e PostgreSQL, poi iterare man mano che i requisiti diventano più chiari.
Usa LISTEN/NOTIFY quando ti serve solo un rapido segnale che qualcosa è cambiato, ad esempio per aggiornare il conteggio di un badge o una tessera della dashboard. Tratta la notifica come un invito a rileggere i dati reali nelle tabelle, non come fonte di verità.
Il polling controlla i cambiamenti a intervalli regolari: gli utenti vedono aggiornamenti in ritardo e il server fa lavoro anche quando nulla è cambiato. LISTEN/NOTIFY invia un piccolo segnale nel momento del cambiamento, che spesso sembra più veloce ed evita molte richieste vuote.
No: è best-effort. Se il listener è disconnesso durante un NOTIFY, può perdere il segnale perché le notifiche non vengono memorizzate per una riproduzione successiva.
Tienilo piccolo e usalo come suggerimento. Un buon default è una piccola stringa JSON con type e id, poi l'app interroga Postgres per lo stato corrente.
Una pratica comune è inviare la notifica solo dopo che la scrittura è stata confermata (committed). Se notifichi troppo presto, il client può svegliarsi e non trovare ancora la nuova riga.
Il codice applicativo è di solito più semplice da capire e da testare perché è esplicito. I trigger sono utili quando più writer toccano le stesse tabelle e vuoi comportamento coerente indipendentemente da chi ha fatto la modifica.
Pianifica i reconnect come comportamento normale. Al riconnetterti, esegui di nuovo LISTEN per i canali necessari e fai una rapida rifetch dello stato recente per coprire eventuali eventi persi mentre eri offline.
Non far connettere ogni browser a Postgres. Una configurazione tipica è una connessione lunga per ogni istanza backend che LISTENa, poi il backend inoltra gli eventi ai browser tramite WebSocket o SSE e l'interfaccia richiede i dati necessari.
Usa canali più stretti in modo che si sveglino solo i consumatori giusti e raggruppa i burst rumorosi. Debounce per qualche centinaio di millisecondi e coalesci gli aggiornamenti duplicati per evitare che UI e backend si sovraccarichino.
Passa oltre quando ti servono durabilità, retry, consumer group, ordering o auditing/replay. Se perdere un evento può causare un incidente (fatturazione, spedizioni, workflow irreversibili), usa una tabella outbox con un worker o un broker dedicato invece di fare affidamento solo su NOTIFY.