Reaaliaikajärjestelmien rakentaminen PostgreSQL:llä: ORM:ää pidemmälle

Atlas Verifiedin tiimi
·
13. helmikuuta 2026
·
8 min lukuaikaa
Reaaliaikajärjestelmien rakentaminen PostgreSQL:llä: ORM:ää pidemmälle

Kun sovellus tarvitsee reaaliaikaisia päivityksiä, vuoden 2025 oletusvastaus on hallinnoitu palvelu: Firebase Realtime Database, Supabase Realtime, Pusher, Ably tai isännöity WebSocket-kerros. Nämä palvelut piilottavat päivitysten lähettämisen monimutkaisuuden yhdistetyille asiakkaille. Ne toimivat hyvin — kunnes eivät.

Atlas Verifiedissä aloitimme hallinnoidulla tietokanta-alustalla, jossa oli reaaliaikaiset tilaukset. Järjestelmän kasvaessa saavutimme abstraktion rajat. Yhteyspoolien rajoitteet, reaaliajan luotettavuusongelmat, PostGIS-paikkatietokyselyjen tarve ja räätälöidyn triggerilogiikan tavoite veivät samaan suuntaan: tietokantakerros omaan hallintaan.

Siirryimme itse ylläpidettyyn PostgreSQL:ään ja rakensimme reaaliaikajärjestelmän sen natiivilla LISTEN/NOTIFY-mekanismilla ja Server-Sent Events -tekniikalla. Kirjoitus käsittelee päätöksen syyt, arkkitehtuurin toiminnan ja opit.

Miksi jätimme hallinnoidun alustan

Hallinnoidut tietokanta-alustat tarjoavat houkuttelevan arvolupauksen: tunnistautuminen, rivikohtainen turvallisuus, reaaliaikaiset tilaukset, automaattiset API:t ja koontinäyttö — kaikki valmiina. Varhaisen vaiheen tuotteille tämä on todella arvokasta. Voit rakentaa toimivan sovelluksen kirjoittamatta yhtäkään taustakoodiriviä.

Ongelmat syntyvät sovelluksen kypsyessä ja vaatimusten erkaantuessa alustan oletuksista.

Yhteysrajat. Hallinnoidut alustat ohjaavat tietokantayhteydet usein oman rajansa asettavan yhteyspoolin kautta. Kun useat taustapalvelut tarvitsevat omat poolinsa — API-palvelin, taustatyöjärjestelmä, reaaliaikakuuntelija — saatavilla olevat yhteydet loppuvat nopeasti. Huomasimme suunnittelevamme poolin rajoitteiden kiertoa todellisten tuoteongelmien ratkaisemisen sijaan.

Reaaliajan luotettavuus. Hallinnoidun alustan reaaliaikajärjestelmä käytti Change Data Capturea PostgreSQL:n Write-Ahead Logista. Arkkitehtuuri on elegantti, mutta se toi vikatapoja, joita emme voineet hallita. WAL-pohjaisessa replikoinnissa on luontainen viive. Reaaliaikapalvelu jäi toisinaan jälkeen ja toimitti vanhentuneita päivityksiä. Uudelleenyhdistäminen verkkokatkojen jälkeen oli epäluotettavaa. Näiden ongelmien vianmääritys vaati myös pääsyä infrastruktuuriin, jota emme hallinneet.

Laajennusvaatimukset. Sovelluksemme käsittelee kauppa-asiakirjoja, joissa on maantieteellisiä tietoja: satamien sijainnit, kuljetusreitit ja alusten sijainnit. PostGIS — PostgreSQL:n paikkatietolaajennus — on välttämätön näille kyselyille. Hallinnoitu alusta tuki PostGIS:iä, mutta tarvitsimme tiettyjä versioita ja määrityksiä, joita ei aina ollut saatavilla.

Räätälöity triggerilogiikka. Tarvitsimme tietokantatriggereitä, jotka käynnistävät sovelluskohtaisia tapahtumia — eivät vain yleisiä ”rivi muuttui” -ilmoituksia. Asiakirjan käsittelytilan muuttuessa meidän on ilmoitettava juuri sille yhdistetylle asiakkaalle, joka latasi sen. Hallinnoidun alustan reaaliaika oli taulutasoinen; tarvitsimme toimijatason.

Siirtopäätös ei koskenut kustannusta tai periaatetta. Se oli käytännöllinen: käytimme enemmän aikaa alustan rajoitteiden kiertämiseen kuin ominaisuuksien rakentamiseen itse olisi kulunut.

LISTEN/NOTIFY: PostgreSQL:n piilotettu julkaisu- ja tilausmekanismi

PostgreSQL on sisältänyt sisäänrakennetun julkaisu- ja tilausmekanismin vuonna 2010 julkaistusta versiosta 9.0 alkaen. Yli 15 vuoden iästään huolimatta LISTEN/NOTIFY on huomattavan vähän käytetty. Useimmat kehittäjät eivät tiedä sen olemassaolosta.

Mekanismi on yksinkertainen. Mikä tahansa tietokantayhteys voi suorittaa LISTENin nimetyllä kanavalla tilatakseen sen. Mikä tahansa yhteys voi suorittaa NOTIFYn lähettääkseen viestin kaikille kanavan kuuntelijoille. Hyötykuorma on merkkijono, jonka raja on 8,000 tavua.

Mikä tekee LISTEN/NOTIFYstä tehokkaan reaaliaikasovelluksissa:

Ei ylimääräistä infrastruktuuria. Ei erillistä viestinvälittäjää käyttöönotettavaksi, seurattavaksi tai skaalattavaksi. Jos sinulla on PostgreSQL, sinulla on julkaisu ja tilaus. Ei Redisiä, RabbitMQ:ta tai Kafkaa — tässä käyttötapauksessa.

Transaktiotakuut. NOTIFY-viestit toimitetaan vasta ympäröivän transaktion vahvistuttua. Jos transaktio lisää rivin ja lähettää ilmoituksen mutta peruuntuu, ilmoitusta ei koskaan toimiteta. Tätä ominaisuutta ulkoiset viestinvälittäjät eivät tarjoa ilman huolellista koordinointia.

Triggeri-integraatio. PostgreSQL-triggerit voivat kutsua pg_notifya suorituksensa osana. Tietokanta voi siis ilmoittaa kuuntelijoille automaattisesti tiettyjen tietojen muuttuessa ilman sovelluskoodia kirjoituspolussa.

Rajoitukset ovat todellisia ja ne kannattaa ymmärtää:

Ei pysyvyyttä. Jos kukaan ei kuuntele NOTIFYn käynnistyessä, viesti katoaa. Ei uudelleentoistoa, viestijonoa tai toimitustakuuta irti oleville asiakkaille. Kuuntelijoiden on hoidettava uudelleenyhdistäminen ja tilan palautus.

Hyötykuorman koko. Ilmoituksen 8,000 tavun hyötykuormaraja estää suurten objektien lähettämisen NOTIFYllä. Lähetämme pienet hyötykuormat — taulun nimi, operaatiotyyppi ja pääavain — ja annamme asiakkaan hakea täydet tiedot tarvittaessa.

Yhden tietokannan rajaus. LISTEN/NOTIFY toimii yhden PostgreSQL-instanssin sisällä. Se ei ulotu replikoihin tai klustereihin. Arkkitehtuureissa, joissa kaikki kirjoitukset menevät yhteen ensisijaiseen instanssiin, tämä ei ole rajoitus.

Arkkitehtuurimme: triggerit, SSE ja kanavien multipleksointi

Näin osat liittyvät yhteen.

Tietokantatriggerit asennetaan tauluihin, joissa reaaliaikapäivitykset merkitsevät: asiakirjat, viestit, keskustelut, ilmoitukset, toimitusvarmennukset ja muut. Rivin lisäys, päivitys tai poisto käynnistää triggerin, joka kutsuu pg_notifya JSON-hyötykuormalla, jossa ovat taulun nimi, operaatiotyyppi, pääavain ja mahdolliset suodatussarakkeet.

Erillinen kuunteluyhteys API-palvelimessamme suorittaa LISTENin ilmoituskanavalla ja vastaanottaa kaikki triggeritapahtumat. Se on pitkäikäinen erillinen PostgreSQL-yhteys — kysely-yhteyspoolista erillään — joka vain kuuntelee ilmoituksia.

Tapahtumaväylä vastaanottaa kuuntelijan ilmoitukset ja ohjaa ne yhdistetyille asiakkaille tilausten perusteella. Tietyn toimitustunnuksen asiakirjoja tilaava asiakas saa vain kyseisen toimituksen asiakirjailmoitukset.

Palvelimen lähettämät tapahtumat toimittaa ilmoitukset selaimeen. Jokainen yhdistetty asiakas avaa yhden SSE-yhteyden API-palvelimeen ja määrittää haluamansa kanavat ja suodattimet. Tapahtumaväylä täsmäyttää saapuvat ilmoitukset asiakastilauksiin ja lähettää vastaavat tapahtumat sopivien SSE-yhteyksien kautta.

Miksi SSE WebSocketsin sijaan

Olemme olleet tähän päätökseen johdonmukaisesti tyytyväisiä. Server-Sent Events on WebSocketsia yksinkertaisempi kaikissa käyttötapauksellemme olennaisissa ulottuvuuksissa.

Suunniteltu yksisuuntaiseksi. Reaaliaikatarpeemme on palvelimelta asiakkaalle: palvelin kertoo tietojen muuttumisesta. Asiakkaan ei tarvitse lähettää reaaliaikaviestejä palvelimelle — se käyttää tavallisia HTTP-pyyntöjä. SSE:n yksisuuntainen malli vastaa todellista vaatimustamme.

HTTP-natiivi. SSE-yhteydet ovat tavallisia HTTP-pyyntöjä tietyllä sisältötyypillä. Ne toimivat kaikkien HTTP-välityspalvelimien, kuormantasaajien ja CDN:ien läpi ilman erityismäärityksiä. WebSockets vaatii HTTP Upgraden, jota osa välityspalvelimista käsittelee väärin ja osa yrityspalomuureista estää kokonaan.

Automaattinen uudelleenyhdistäminen. Selaimen EventSource API hoitaa uudelleenyhdistämisen natiivisti. Yhteyden katketessa selain yhdistää automaattisesti uudelleen eksponentiaalisesti kasvavalla odotusajalla. WebSocketsilla toteutat logiikan itse — ja teet virheen vähintään kerran.

Ainoa WebSocketsin voittama alue — kaksisuuntainen viestintä — on meille tarpeeton. Reaaliaikaista asiakas-palvelinviestintää vaativille sovelluksille WebSockets on oikea valinta. Palvelimelta asiakkaalle ilmoituksia lähettäville sovelluksille SSE on oikea valinta.

Kyselykerroksen rakentaminen ilman ORM:ää

Hallinnoidun alustan jättäessämme jätimme myös sen automaattisen kysely-API:n. Tarvitsimme API-palvelimellemme tietokannan käyttökerroksen.

Tavanomaiset vaihtoehdot ovat ORM:t kuten Prisma, TypeORM tai Drizzle — tai suora SQL ajurilla. Valitsimme kolmannen tien: kevyen kyselyrakentajan, joka tarjoaa ketjutettavan API:n ja tuottaa sekä suorittaa taustalla suoraa SQL:ää.

Tuttu API. Nykyinen koodikantamme oli kirjoitettu hallinnoidun alustan asiakaskirjastolle. Yhteensopiva API tarkoitti vähäisiä koodimuutoksia siirrossa. Aiemmin alustan asiakkaalla suoritetut kyselyt voitiin suorittaa kyselyrakentajallamme samalla syntaksilla.

Ei taikuutta. ORM:t tuottavat SQL:ää, jota et näe etkä aina hallitse. Hitaan kyselyn kohdalla on luettava tuotettu SQL, joka usein muistuttaa vain vähän sitä tuottanutta ORM-kutsua. Kyselyrakentajamme tuottaa ennakoitavaa SQL:ää — jokainen ketjutettu metodi vastaa tiettyä SQL-lausetta, ja tuotetun kyselyn voi aina tarkastaa.

Upotetut liitokset. Kauppatiedot ovat relaatiomuotoisia. Toimituksella on asiakirjoja, niillä solmuja ja niillä varmennustarkistuksia. Sisäkkäisten valintojen ilmaiseminen ja sisäkkäisen tuloksen saaminen — ilman monimutkaisen JOIN-kyselyn käsikirjoittamista joka kerta — parantaa kehittäjäkokemusta huomattavasti.

Tuloksena on muutaman sadan koodirivin kyselykerros, joka hoitaa valtaosan tietokantavuorovaikutuksestamme ja tuottaa aina luettavaa ja optimoitavaa SQL:ää.

Yhteyspoolin budjetointi

Kun ylläpidät PostgreSQL:ää itse, vastaat yhteysbudjetista. PostgreSQL:n max_connections-asetus määrittää ehdottoman katon, ja jokainen yhteys kuluttaa muistia.

Huomasimme, että yhteyksien ajatteleminen budjettina — palvelujen jakamana rajallisena resurssina — estää ongelmatyypin, joka vaivaa sitä suunnittelemattomia tiimejä.

Jokainen palvelu ilmoittaa yhteyksiensä enimmäismäärän. Enimmäisten summa on pidettävä selvästi PostgreSQL:n rajan alla, ja varaa on jätettävä tilapäisille yhteyksille — psql-istunnot, siirtoskriptit ja seurantatyökalut. Tämä kuulostaa ilmeiseltä, mutta useimmat tiimit tekevät sen vasta yhteyksien loputtua tuotannossa. Ennakkobudjetoinnilla vältimme juuri sen kello 3:n yöhälytyksen.

Turvalliset suuren mittakaavan siirrot

Siirtyminen hallinnoidusta alustasta itse ylläpidettyyn PostgreSQL:ään tarkoitti satojen taulujen, funktioiden, triggerien ja indeksien siirtämistä. Suurempi haaste oli kuitenkin rakentaa siirtojärjestelmä, joka toimisi sovelluksen kehittyessä.

Automaattinen siirtojärjestelmämme toimii näin: siirtotiedostot nimetään aikaleimoilla ja tallennetaan omaan hakemistoon. Siirtosuoritin lukee hakemiston, vertaa sitä sovelletut siirrot kirjaavaan seurantatauluun ja suorittaa odottavat siirrot järjestyksessä. Jokainen suoritetaan transaktiossa — epäonnistuessaan se peruuntuu siististi ja estää käyttöönoton, kunnes ongelma korjataan.

Siirtosuoritin toimii automaattisesti jokaisessa käyttöönotossa ennen sovelluksen käynnistymistä. Tietomallimuutokset ovat siten osa käyttöönottoa ilman käsivaiheita. Kehittäjä luo aikaleimatun SQL-tiedoston, lähettää sen koodivarastoon ja seuraava käyttöönotto soveltaa sitä.

Turvallisuuden keskeiset suunnittelupäätökset: siirrot käyttävät idempotentteja suojauksia, jotta osittainen soveltaminen ei aiheuta virhettä. Jokaisen siirron on oltava itsenäinen SQL-skripti ilman ihmisen syötettä. Siirron epäonnistuessa koko transaktio peruuntuu — sovellus ei käynnisty osittain sovelletulla tietomallimuutoksella.

Mitä saimme

Siirtyminen itse ylläpidettyyn PostgreSQL:ään räätälöidyllä reaaliajalla oli huomattava tekninen investointi. Hyödyt ovat kuitenkin olleet suuria:

Luotettavuus. Reaaliaikailmoitukset saapuvat millisekunneissa taustatiedon muutoksesta. Ei WAL-replikointiviivettä eikä kolmannen osapuolen palvelukatkoja kriittisellä polulla.

Vianmääritys. Kun jokin menee pieleen, voimme jäljittää koko polun: triggeri käynnistyy, ilmoitus lähetetään, tapahtumaväylä reitittää ja SSE toimittaa. Jokainen vaihe on koodissamme, lokeissamme ja infrastruktuurissamme.

Joustavuus. Reaaliajan lisääminen uuteen tauluun on yksi triggerin asentava SQL-siirto. Ei alustamäärityksiä, kolmannen osapuolen koontinäyttöä tai tukipyyntöä.

Suorituskyky. Suorat PostgreSQL-yhteydet säädetyllä poolilla päihittävät hallinnoidun alustan infrastruktuurin kautta reititetyt yhteydet. Kyselyviive pieneni merkittävästi siirron jälkeen.

Vastapainona on operatiivinen vastuu. Hallitsemme tietokantaa, varmuuskopioimme, seuraamme replikointia ja suunnittelemme kapasiteetin. PostgreSQL-ylläpidon tuntevalle tiimille tämä on hallittavaa. Muulle tiimille hallinnoidun alustan abstraktiot voivat olla rajoitustensa arvoisia.


Atlas Verified toimii itse ylläpidetyllä PostgreSQL:llä ja räätälöidyllä reaaliaikainfrastruktuurilla, käsitellen päivittäin tuhansia kaupan vaatimustenmukaisuusvarmennuksia maailmanlaajuisessa toimitusketjussa.

Piditkö artikkelista?

Jaa se verkostollesi