PostgreSQL LISTEN/NOTIFY kan driva live-dashboards och notiser med minimal setup. Lär dig var det passar, dess begränsningar och när du behöver en broker.

"Liveuppdateringar" i en produkt-UI betyder oftast att skärmen ändras strax efter att något hänt, utan att användaren uppdaterar sidan. Ett tal ökar på en dashboard, en röd badge visas i en inkorg, en admin ser en ny order eller en toast dyker upp som säger "Build finished" eller "Payment failed". Nyckeln är timing: det känns omedelbart, även om det faktiskt är en sekund eller två.
Många team börjar med polling: webbläsaren frågar servern "något nytt?" varannan sekund. Polling fungerar, men har två vanliga nackdelar.
För det första känns det segt eftersom användaren bara ser ändringar vid nästa poll.
För det andra kan det bli dyrt eftersom du gör upprepade kontroller även när inget förändrats. Multiplicera det med tusentals användare och det blir brus.
PostgreSQL LISTEN/NOTIFY finns för ett enklare fall: "säg till mig när något ändrats." Istället för att fråga om och om igen kan din app vänta och reagera när databasen skickar en liten signal.
Det passar bra för UI där en knuff räcker. Till exempel:
Avvägningen är enkelhet kontra garantier. LISTEN/NOTIFY är lätt att lägga till eftersom det redan finns i Postgres, men det är inte ett fullfjädrat meddelandesystem. Notifikationen är en hint, inte en beständig post. Om en lyssnare är frånkopplad kan den missa signalen.
Ett praktiskt sätt att använda det: låt NOTIFY väcka din app, och låt sedan appen läsa sanningen från tabellerna.
Tänk på PostgreSQL LISTEN/NOTIFY som en enkel dörrklocka byggd in i din databas. Din app kan vänta på att klockan ringer, och en annan del av systemet kan ringa när något ändras.
En notifikation har två delar: ett kanalnamn och en valfri payload. Kanalen är som en ämnesetikett (till exempel orders_changed). Payloaden är ett kort textmeddelande du bifogar (till exempel ett order-id). PostgreSQL tvingar ingen struktur, så team skickar ofta små JSON-strängar.
En notifikation kan triggas från applikationskod (din API-server kör NOTIFY) eller från databasen själv med en trigger (en trigger kör NOTIFY efter en insert/update/delete).
På mottagarsidan öppnar din appserver en databasanslutning och kör LISTEN channel_name. Denna anslutning hålls öppen. När en NOTIFY channel_name, 'payload' sker, pushar PostgreSQL ett meddelande till alla anslutningar som lyssnar på den kanalen. Din app reagerar sedan (uppdaterar cache, hämtar den ändrade raden, pushar ett WebSocket-event till webbläsaren osv.).
NOTIFY är bäst att förstå som en signal, inte en leveranstjänst:
Använt så här kan PostgreSQL LISTEN/NOTIFY driva live UI-uppdateringar utan extra infrastruktur.
LISTEN/NOTIFY lyser när ditt UI bara behöver en knuff att något ändrats, inte en fullständig eventström. Tänk "uppdatera den här widgeten" eller "det finns ett nytt objekt" snarare än "bearbeta varje klick i ordning."
Det fungerar bäst när databasen redan är din sanningskälla och du vill att UI ska hållas synkroniserat med den. Ett vanligt mönster är: skriv raden, skicka en liten notifikation med ett ID, och låt UI (eller ett API) hämta senaste tillståndet.
LISTEN/NOTIFY räcker oftast när de flesta av dessa stämmer:
Ett konkret exempel: en intern supportdashboard visar "öppna ärenden" och en badge för "nya anteckningar." När en agent lägger till en anteckning skriver backend till Postgres och NOTIFYar ticket_changed med ticket-ID. Webbläsaren tar emot via WebSocket och hämtar om just det kortet. Ingen extra infrastruktur, och UI känns fortfarande live.
LISTEN/NOTIFY kan kännas toppen i början, men har hårda begränsningar. De syns när du behandlar notifikationer som ett meddelandesystem istället för en lätt "knuff".
Den största bristen är hållbarhet. En NOTIFY är inte ett kö-jobb. Om ingen lyssnar just då försvinner meddelandet. Även när en lyssnare är ansluten kan en krasch, deploy, nätverksstörning eller databas-omstart bryta anslutningen. Du får inte automatiskt tillbaka "missade" notifikationer.
Frånkopplingar är extra smärtsamma för användargränssnitt. Föreställ dig en dashboard som visar nya order. En webbläsarflik går i vila, WebSocket återansluter och UI ser "stagnant" eftersom den missade några event. Du kan arbeta runt det, men workarounds innebär ofta att du återuppbygger tillstånd via frågningar och använder NOTIFY endast som en hint att uppdatera.
Fan-out är ett annat vanligt problem. Ett event kan väcka hundratals eller tusentals lyssnare (många appservrar, många användare). Om du använder en högljudd kanal som orders vaknar alla, även om bara en användare bryr sig. Det kan skapa CPU- och anslutningsspikar vid fel tillfälle.
Payload-storlek och frekvens är slutliga fällor. NOTIFY-payloads är små, och högfrekventa event kan stapla upp snabbare än klienter hinner hantera.
Håll utkik efter dessa tecken:
Då är det en bra idé att fortsätta använda NOTIFY som ett "poke" och flytta pålitligheten till en tabell eller en riktig meddelandebroker.
Ett pålitligt mönster med LISTEN/NOTIFY är att behandla NOTIFY som en knuff, inte som sanningskällan. Databasraden är sanningen; notifikationen talar om för din app när den ska titta.
Gör skrivningen i en transaktion och skicka notifikationen först efter att databasändringen är committad. Notificar du för tidigt kan klienter vakna upp och inte hitta datan än.
Ett vanligt upplägg är en trigger som körs på INSERT/UPDATE och skickar ett litet meddelande.
NOTIFY dashboard_updates, '{\\\"type\\\":\\\"order_changed\\\",\\\"order_id\\\":123}'::text;
Kanalnamn fungerar bäst när de matchar hur folk tänker om systemet. Exempel: dashboard_updates, user_notifications, eller per-tenant som tenant_42_updates.
Håll payloaden liten. Lägg identifierare och en typ, inte fulla poster. En användbar standardform kan vara:
type (vad som hände)id (vad som ändrades)tenant_id eller user_idDet håller bandbredden nere och undviker att känsliga data läggs i notifikationsloggar.
Anslutningar faller bort. Planera för det.
Vid anslutning, kör LISTEN för alla behövda kanaler. Vid frånkoppling, återanslut med kort backoff. Vid återanslutning, kör LISTEN igen (prenumerationer kvarstår inte automatiskt). Efter återanslutning, gör en snabb omhämtning av "senaste ändringar" för att täcka event du kan ha missat.
För de flesta live-UI-uppdateringar är omhämtning det säkraste: klienten tar emot {type, id} och frågar sedan servern efter senaste tillstånd.
Inkremetell patchning kan vara snabbare, men är lättare att få fel med (events i fel ordning, delvisa fel). Ett bra mellanläge är: hämta små delar (en orderrad, ett ärendekort, ett badge-antal) och lämna tyngre aggregat på en kort timer.
När du går från en admin-dashboard till många användare som bevakar samma siffror, spelar goda vanor större roll än smart SQL. LISTEN/NOTIFY kan fortfarande fungera bra, men du behöver forma hur event flyter från databasen till webbläsare.
En vanlig baseline är: varje appinstans öppnar en långlivad anslutning som LISTENar, och pushar sedan uppdateringar till anslutna klienter. "En lyssnare per instans" är enkelt och ofta tillräckligt om du har få appservrar och tolererar tillfälliga återanslutningar.
Om du har många appinstanser (eller serverless-workers) kan en delad listener-tjänst vara enklare. En liten process lyssnar en gång och fanar sedan ut uppdateringar till resten av stacken. Den ger också en plats för batching, metrics och backpressure.
För webbläsare pushar du vanligtvis med WebSockets (tvåväg, bra för interaktiva UI) eller Server-Sent Events (SSE) (envägs, enklare för dashboards). Undvik att skicka "uppdatera allt." Skicka kompakta signaler som "order 123 changed" så UI kan hämta bara det som behövs.
För att undvika att UI thrashas, lägg till några skydd:
Kanaldesign är också viktig. Istället för en global kanal, partitionera per tenant, team eller funktion så klienter bara får relevanta event. Exempel: notify:tenant_42:billing och notify:tenant_42:ops.
LISTEN/NOTIFY känns enkelt, vilket är därför team levererar snabbt och sedan blir överraskade i produktion. De flesta problem kommer av att behandla det som en garanterad meddelandekö.
Om din app återansluter (deploy, nätverksblipp, DB-failover) försvinner alla NOTIFY som skickades medan du var frånkopplad. Lösningen är att göra notifikationen till en signal och sedan kontrollera databasen på nytt.
Ett praktiskt mönster: lagra det riktiga eventet i en tabell (med id och created_at), och vid återanslutning hämta allt nyare än din sista sedda id.
LISTEN/NOTIFY-payloads är inte avsedda för stora JSON-blobs. Stora payloads skapar mer jobb, mer parsing och större risk att träffa gränser.
Använd payloads för pyttesmå hints som "order:123". Låt appen läsa senaste tillstånd från databasen.
Ett vanligt misstag är att designa UI kring payload-innehållet som om det vore sanningskällan. Det gör schemaändringar och klientversioner besvärliga.
Håll en tydlig uppdelning: notify att något ändrades, och hämta sedan aktuell data med en vanlig fråga.
Triggers som NOTIFYar på varje radändring kan översvämma systemet, särskilt för aktiva tabeller.
Notifica endast vid meningsfulla övergångar (t.ex. statusändringar). Om du har väldigt brusiga uppdateringar, batcha changes (en notify per transaktion eller per tidsfönster) eller flytta de uppdateringarna utanför notify-vägen.
Även om databasen kan skicka notifikationer kan ditt UI fortfarande bli överbelastat. En dashboard som renderar om vid varje event kan frysa.
Debounca uppdateringar på klienten, slå ihop burst till en refresh och föredra "invalidera och hämta igen" framför att applicera varje delta. Exempel: en notifikationsikon kan uppdatera direkt, men dropdown-listan kan uppdateras högst en gång varannan sekund.
LISTEN/NOTIFY är utmärkt när du vill ha en liten "något ändrades"-signal så appen kan hämta färska data. Det är inte ett fullständigt meddelandesystem.
Innan du bygger UI:t runt det, svara på dessa frågor:
En praktisk regel: om du kan behandla NOTIFY som en knuff ("gå läs om raden") snarare än som själva payloaden, är du i säker zon.
Exempel: en admin-dashboard visar nya order. Om en notifikation missas visar nästa poll eller en siduppdatering ändå korrekt antal. Det är ett bra fall. Men om du skickar "dra kortet" eller "skicka paketet"-händelser, kan att missa ett event bli en incident.
Föreställ dig en liten säljapp: en dashboard visar dagens intäkter, totala order och en lista "senaste order". Samtidigt ska varje säljare få en snabb notis när en order de äger är betald eller skickad.
Ett enkelt tillvägagångssätt är att behandla PostgreSQL som sanningskälla och använda LISTEN/NOTIFY endast som knuffen att något ändrats.
När en order skapas eller ändrar status gör backend två saker i en request: den skriver raden (eller uppdaterar den) och skickar sedan en NOTIFY med en liten payload (ofta bara order-ID och eventtyp). UI litar inte på NOTIFY-payload för full data.
Ett praktiskt flöde kan se ut så här:
orders_events med {\\\"type\\\":\\\"status_changed\\\",\\\"order_id\\\":123}.Detta håller NOTIFY lättviktig och begränsar dyra frågor.
När trafiken växer syns sprickorna: en spik av event kan överväldiga en enda lyssnare, notifikationer kan missas vid återanslutning och du börjar behöva garanterad leverans och replay. Då lägger man ofta till ett mer pålitligt lager (en outbox-tabell plus en worker, och senare en broker) samtidigt som Postgres förblir sanningskällan.
LISTEN/NOTIFY är utmärkt för en snabb "något ändrades"-signal. Den är inte byggd för att vara ett komplett meddelandesystem. När du börjar förlita dig på events som sanningskälla är det dags att införa en broker.
Om något av detta dyker upp, sparar en broker er mycket huvudvärk:
LISTEN/NOTIFY lagrar inte meddelanden för senare. Det är en push-signal, inte en persisterad logg. Perfekt för "uppdatera den här widgeten", men riskabelt för "debitering" eller "skicka paket".
En broker ger ett verkligt meddelandeflöde: köer (arbete att göra), topics (sänd till många), retention (behåll meddelanden i minuter till dagar) och acknowledgments (en konsument bekräftar att den processat). Det låter dig separera "databasen ändrades" från "allt som ska hända eftersom det ändrades".
Du behöver inte välja den mest komplexa lösningen. Vanliga alternativ är Redis (pub/sub eller streams), NATS, RabbitMQ och Kafka. Rätt val beror på om du behöver enkla arbetsköer, fan-out till många tjänster eller möjlighet att spela upp historik.
Du kan flytta utan stor omskrivning. Ett praktiskt mönster är att behålla NOTIFY som wake-up medan brokern blir leveranskällan.
Börja med att skriva en "event-rad" i en tabell i samma transaktion som din affärsändring, och låt en worker publicera det eventet till brokern. Under övergången kan NOTIFY fortfarande säga åt UI-lagret att "kolla efter nya events", medan bakgrundsjobben konsumerar från brokern med retries och auditing.
På så vis förblir dashboards snabba, och kritiska arbetsflöden slutar inte bero på best-effort-notifieringar.
Välj en skärm (en dashboard-ruta, ett badge-antal eller en "ny notis"-toast) och koppla ihop den end-to-end. Med LISTEN/NOTIFY kan du få ett användbart resultat snabbt, så länge du håller scopet snävt och mäter vad som händer under verklig trafik.
Börja med det enklaste pålitliga mönstret: skriv raden, commit, och sänd sedan en liten signal att något ändrats. I UI reagerar du på signalen genom att hämta senaste tillstånd (eller den del du behöver). Det håller payloads små och undviker subtila buggar när meddelanden kommer i fel ordning.
Lägg till grundläggande observabilitet tidigt. Du behöver inte avancerade verktyg för att börja, men du behöver svar när systemet blir brusigt:
Håll kontrakten tråkiga och nedskrivna. Bestäm kanalnamn, eventnamn och formen på eventuell payload (även om den bara är ett ID). En kort "eventkatalog" i repo:t förhindrar att saker glider isär.
Om du bygger snabbt och vill hålla stacken enkel kan en plattform som Koder.ai hjälpa dig att leverera första versionen med en React-UI, en Go-backend och PostgreSQL, och sedan iterera när kraven klarnar.
Use LISTEN/NOTIFY when you only need a quick signal that something changed, like refreshing a badge count or a dashboard tile. Treat the notification as a nudge to refetch the real data from tables, not as the data itself.
Polling checks for changes on a schedule, so users often see updates late and your server does work even when nothing changed. LISTEN/NOTIFY pushes a small signal right when the change happens, which usually feels faster and avoids lots of empty requests.
No, it’s best-effort. If the listener is disconnected during a NOTIFY, it can miss the signal because notifications aren’t stored for later replay.
Keep it small and treat it as a hint. A practical default is a tiny JSON string with a type and an id, then have your app query Postgres for the current state.
A common pattern is to send the notification only after the write is committed. If you notify too early, a client can wake up and not find the new row yet.
Application code is usually easier to understand and test because it’s explicit. Triggers are useful when many different writers touch the same table and you want consistent behavior no matter who made the change.
Plan for reconnects as normal behavior. On reconnect, re-run LISTEN for the channels you need and do a quick refetch of recent state to cover anything you might have missed while offline.
Don’t have every browser connect to Postgres. A typical setup is one long-lived listener connection per backend instance, then your backend forwards events to browsers via WebSockets or SSE and the UI refetches what it needs.
Use narrower channels so only the right consumers wake up, and batch noisy bursts. Debouncing for a few hundred milliseconds and coalescing duplicate updates keeps your UI and backend from thrashing.
Graduate when you need durability, retries, consumer groups, ordering guarantees, or auditing/replay. If missing an event would cause a real incident (billing, shipping, irreversible workflows), use an outbox table plus a worker or a dedicated broker instead of relying on NOTIFY alone.