PostgreSQL‑partitionering voor gebeurtenis‑ en audittabellen
PostgreSQL‑partitionering voor eventtabellen: leer wanneer het rendeert, hoe je partition keys kiest en wat het verandert voor adminpaneel‑filters en retentie.

Waarom gebeurtenis‑ en audittabellen een probleem worden
Event‑ en audittabellen lijken op elkaar, maar bestaan om verschillende redenen.
Een eventtabel legt vast wat er gebeurt: paginaweergaven, verzonden e‑mails, webhook‑calls, job‑runs. Een audittabel registreert wie wat en wanneer heeft gewijzigd: een statuswijziging, een permissieaanpassing, een uitbetaling‑goedkeuring, vaak met “voor” en “na” details.
Beide groeien snel omdat ze append‑only zijn. Je verwijdert zelden individuele rijen en er komen voortdurend nieuwe rijen bij. Zelfs een klein product kan binnen weken miljoenen logrijen produceren zodra achtergrondtaken en integraties meerekenen.
De pijn verschijnt in het dagelijks werk. Adminpanelen hebben snelle filters nodig zoals “fouten van gisteren” of “acties door deze gebruiker”. Naarmate de tabel groeit, beginnen die schermen te haperen.
Je zult meestal eerst een paar signalen zien:
- Filters nemen seconden in beslag (of timen uit) zelfs met een smal datumbereik.
- Indexen worden zo groot dat inserts vertragen en opslagkosten stijgen.
- VACUUM en autovacuum duren langer en je ziet meer onderhoudsactiviteit.
- Retentie wordt riskant: oude rijen verwijderen is langzaam en veroorzaakt bloat.
Partitionering is één manier om dit aan te pakken. Simpel gezegd splitst het één grote tabel op in veel kleinere tabellen (partities) die één logische naam delen. PostgreSQL routeert elke nieuwe rij naar de juiste partitie op basis van een regel, meestal tijd.
Daarom kijkt men naar PostgreSQL‑partitionering voor eventtabellen: het kan recente data in kleinere stukken houden, zodat PostgreSQL hele partities kan overslaan wanneer een query alleen een tijdvenster nodig heeft.
Partitionering is geen magische snelheidsknop. Het helpt vaak flink voor queries zoals “laatste 7 dagen” en maakt retentie eenvoudiger (oude partities droppen is snel). Maar het kan ook nieuwe problemen geven:
- Queries die de partition key niet gebruiken moeten mogelijk veel partities checken.
- Meer partities betekent meer objecten om te beheren en meer manieren om dingen verkeerd te configureren.
- Sommige unieke constraints en indexen worden lastiger af te dwingen over alle data heen.
Als je adminpaneel sterk leunt op datumsfilters en voorspelbare retentierichtlijnen, kan partitionering echt winst opleveren. Als de meeste queries “vind alle acties van gebruiker X over de hele geschiedenis” zijn, kan het problemen veroorzaken tenzij je UI en indexen zorgvuldig ontwerpt.
Typische toegangspatronen voor logs en audits
Event‑ en audittabellen groeien één kant op: omhoog. Ze krijgen een constante stroom inserts en vrijwel geen updates. De meeste rijen worden één keer weggeschreven en later gelezen tijdens supportwerk, incidentreviews of compliance‑checks.
Die append‑only vorm is belangrijk. Schrijfprestaties blijven een constante zorg omdat inserts de hele dag doorgaan, terwijl leesprestaties er toe doen in pieken (wanneer support of ops snel antwoorden nodig heeft).
De meeste reads zijn filters, geen willekeurige lookups. In een adminpaneel begint iemand meestal breed (laatste 24 uur) en versmalt dan naar een gebruiker, een entiteit of een actie.
Veelvoorkomende filters zijn:
- Een tijdsvenster
- Een actor (user ID, service account, IP‑adres)
- Een target (entiteitstype + entiteit‑ID, zoals Order #1234)
- Een actietype (created, updated, deleted, login failed)
- Een status of severity (success/error)
Tijdsvenster is de natuurlijke “eerste knip” omdat het vrijwel altijd aanwezig is. Dat is het belangrijke inzicht achter PostgreSQL‑partitionering voor eventtabellen: veel queries willen een tijdssegment, en alles anders is een tweede filter binnen dat segment.
Retentie is de andere constante. Logs blijven zelden voor altijd. Teams bewaren vaak gedetailleerde events 30 of 90 dagen en verwijderen of archiveren daarna. Auditlogs kunnen langere eisen hebben (365 dagen of meer), maar zelfs dan wil je een voorspelbare manier om oude data te verwijderen zonder de database te blokkeren.
Auditlogging heeft ook extra verwachtingen. Je wilt doorgaans dat geschiedenis onveranderlijk is, dat elk record traceerbaar is (wie/wat/wanneer plus request‑ of sessiecontext) en dat toegang gecontroleerd is (niet iedereen mag beveiligings‑events zien).
Deze patronen verschijnen direct in UI‑ontwerp. De filters die men standaard verwacht — datumkeuzers, gebruikerselecties, entiteitszoekopdrachten, actiedropdowns — zijn dezelfde filters die je tabel en indexen moeten ondersteunen als je de adminervaring snel wilt houden naarmate het volume groeit.
Hoe te bepalen of partitionering de moeite waard is
Partitionering is geen standaardbest practice voor auditlogs. Het rendeert wanneer één tabel zo groot wordt dat dagelijkse queries en routinematig onderhoud elkaar in de weg gaan zitten.
Een eenvoudige vuistregel: zodra een eventtabel tientallen miljoenen rijen bereikt, is het de moeite waard om te meten. Als de tabel en zijn indexen tientallen gigabytes bereiken, kunnen zelfs simpele datum‑zoeken traag of onvoorspelbaar worden omdat er veel datapagina's van schijf gelezen worden en indexen duur zijn om te onderhouden.
Het duidelijkste query‑signaal is wanneer je regelmatig om een klein tijdsvenster vraagt (laatste dag, week), maar PostgreSQL toch een groot deel van de tabel moet aanraken. Je ziet dat terug in trage “recente activiteit” schermen, of audits gefilterd op datum plus gebruiker, actietype of entiteit‑ID. Als queryplannen grote scans tonen of buffer reads consistent hoog zijn, betaal je voor data die je niet wilde lezen.
Operationele signalen zijn even belangrijk:
- VACUUM en autovacuum duren veel langer dan vroeger.
- Autovacuum raakt achter en dode tuples (bloat) bouwen zich op.
- Indexen groeien sneller dan verwacht, vooral multicolumn indexen.
- Lock‑contention wordt merkbaarder als onderhoud samenvalt met productieverkeer.
Operationele kosten zijn de langzame druppel die teams naar partitionering duwt. Backups en restores worden trager, opslag sluipt omhoog en retentie‑jobs worden duur omdat grote DELETEs bloat en extra vacuumwerk veroorzaken.
Als je hoofddoelen een nette retentie en snellere “recente periode” queries zijn, is partitionering meestal de moeite waard om serieus te onderzoeken. Als de tabel bescheiden is en queries al snel zijn met goede indexen, voegt partitionering complexiteit toe zonder duidelijk voordeel.
Partitioneringsopties die passen bij event‑ en audittabellen
Voor de meeste audit‑ en eventdata is de eenvoudigste keuze range‑partitionering op tijd. Logs komen in tijdsvolgorde binnen, queries richten zich vaak op “laatste 24 uur” of “laatste 30 dagen” en retentie is doorgaans tijdgebaseerd. Met tijdpartities kan het verwijderen van oude data zo simpel zijn als het verwijderen van een oude partitie in plaats van een grote DELETE die de tabel bloat geeft.
Tijdrange‑partitionering houdt ook indexen kleiner en gerichter. Elke partitie heeft zijn eigen indexen, dus een query voor de laatste week hoeft niet één gigantische index te doorlopen die jaren historie bestrijkt.
Andere partitioneringsstijlen bestaan, maar passen minder bij logs en audits:
- List (tenant of klant) kan werken als je een klein aantal zeer grote tenants hebt en queries meestal binnen één tenant blijven. Het wordt pijnlijk bij honderden of duizenden tenants.
- Hash (gelijke schrijfverdeling) kan helpen als je geen tijdvensterqueries hebt en schrijfactiviteit gelijkmatig wilt spreiden. Voor auditlogs is het minder gebruikelijk omdat het retentie en tijdgebaseerd browsen bemoeilijkt.
- Subpartitionering (tijd plus tenant) kan krachtig zijn, maar complexiteit groeit snel. Het is vooral voor zeer high‑volume systemen met strikte tenantisolatiebehoeften.
Als je voor tijdrange kiest, pick een partitiegrootte die past bij hoe je bladert en bewaart. Dagelijkse partities zijn logisch voor zeer hoge volumes of strikte retentie. Maandelijkse partities zijn makkelijker te beheren bij een matig volume.
Een praktisch voorbeeld: als een adminteam elke ochtend foutieve loginpogingen checkt en filtert op de laatste 7 dagen, dan zorgen dagelijkse of wekelijkse partities ervoor dat de query slechts de meest recente partities hoeft te lezen. PostgreSQL kan de rest negeren.
Welke aanpak je ook kiest, plan de saaie onderdelen: maak toekomstige partities aan, handel laat binnenkomende events af en definieer wat er bij elke grens gebeurt (einde dag, einde maand). Partitionering betaalt zich terug wanneer die routines eenvoudig blijven.
Hoe kies je de juiste partition key
Een goede partition key past bij hoe je de tabel leest, niet hoe de data in een diagram oogt.
Voor event‑ en auditlogs begin je bij je adminpaneel: welk filter gebruikt men als eerste, bijna altijd? Voor de meeste teams is dat een tijdsvenster (laatste 24 uur, laatste 7 dagen, custom datums). Als dat voor jou geldt, geeft tijdgebaseerde partitionering meestal de grootste en meest voorspelbare winst omdat PostgreSQL hele partities buiten het geselecteerde bereik kan overslaan.
Behandel de key als een langetermijnbelofte. Je optimaliseert voor de queries die je jaren lang blijft draaien.
Start met de “eerste filter” die mensen gebruiken
De meeste adminschermen volgen een patroon: tijdsvenster plus optioneel gebruiker, actie, status of resource. Partitioneer op datgene dat vroeg en consistent de resultaten verkleint.
Een korte realiteitscheck:
- Als de default view “recente events” is, partitioneer op timestamp.
- Als de default view “events voor één tenant/account” is, kan
tenant_idzinvol zijn, maar alleen als tenants groot genoeg zijn om het te rechtvaardigen. - Als de eerste stap altijd “kies een gebruiker” is, klinkt
user_idverleidelijk, maar meestal resulteert dat in te veel partities om te beheren.
Vermijd hoge‑cardinaliteit keys
Partitionering werkt het beste wanneer elke partitie een betekenisvolle datablok is. Sleutels zoals user_id, session_id, request_id of device_id kunnen leiden tot duizenden of miljoenen partities. Dat verhoogt metadata‑overhead, bemoeilijkt onderhoud en vertraagt vaak de planner.
Tijdgebaseerde partities houden het aantal partities voorspelbaar. Je kiest dagelijks, wekelijks of maandelijks op basis van volume. Te weinig partities (één per jaar) helpt niet veel. Te veel (één per uur) voegt snel overhead toe.
Kies de juiste timestamp: created_at versus occurred_at
Wees expliciet over wat tijd betekent:
occurred_at: wanneer het event in het product gebeurde.created_at: wanneer de database het registreerde.
Voor audits is “occurred” vaak wat admins interesseert. Maar vertraagde levering (offline mobiele clients, retries, queues) betekent dat occurred_at soms laat binnenkomt. Als late aankomsten vaak voorkomen, kan partitioneren op created_at en indexeren van occurred_at voor filtering operationeel stabieler zijn. De andere optie is een duidelijke backfill‑policy en accepteren dat oude partities af en toe late events ontvangen.
Bepaal ook hoe je tijd opslaat. Gebruik een consistent type (vaak timestamptz) en hanteer UTC als bron van waarheid. Format voor de kijker in de UI naar tijdzone. Dat houdt partitiongrenzen stabiel en voorkomt onverwachte issues bij zomertijd.
Stapsgewijs: plan en rol partitionering uit
Partitionering is het makkelijkst als je het als een klein migratieproject behandelt, niet als een snelle tweak. Het doel is eenvoudige writes, voorspelbare reads en retentie die een routine‑handeling wordt.
Een praktisch uitrolplan
-
Kies een partitiegrootte die bij je volume past. Maandelijkse partities zijn meestal prima bij enkele honderdduizenden rijen per maand. Als je tientallen miljoenen per maand insert, houden wekelijkse of dagelijkse partities indexen kleiner en vacuumwerk beter afgebakend.
-
Ontwerp keys en constraints voor gepartitioneerde tabellen. In PostgreSQL moet een unique constraint de partition key bevatten (of op een andere manier afgedwongen worden). Een gangbaar patroon is
(created_at, id), waarbijidwordt gegenereerd encreated_atde partition key is. Dit voorkomt verrassingen later wanneer blijkt dat een verwachte constraint niet toegestaan is. -
Maak toekomstige partities aan voordat je ze nodig hebt. Wacht niet tot inserts falen omdat er geen matchende partitie is. Bepaal hoe ver vooruit je ze aanmaakt (bijvoorbeeld 2–3 maanden) en maak er een routinejob van.
-
Houd per‑partitie indexen klein en doelgericht. Partitionering maakt indexen niet gratis. De meeste eventtabellen hebben de partition key plus één of twee indexen nodig die echte adminfilters dekken, zoals
actor_id,entity_idofevent_type. Sla “voor het geval” indexen over. Je kunt ze later aan nieuwe partities toevoegen en oudere terugvullen als dat nodig is. -
Plan retentie rond het droppen van partities, niet rond het verwijderen van rijen. Als je 180 dagen logs bewaart, is het droppen van een oude partitie snel en vermijd je langdurige deletes en bloat. Leg de retentieregel vast: wie voert het uit en hoe verifieer je dat het werkte.
Een klein voorbeeld
Als je audittabel 5 miljoen rijen per week krijgt, zijn wekelijkse partities op created_at een redelijke start. Maak partities 8 weken vooruit en houd twee indexen per partitie: één voor zoekopdrachten op actor_id en één voor entity_id. Wanneer de retentieperiode verloopt, drop je de oudste wekelijkse partitie in plaats van miljoenen rijen te verwijderen.
Als je interne tools bouwt met AppMaster, helpt het om de partition key en constraints vroeg te bepalen zodat het datamodel en de gegenereerde code dezelfde aannames volgen vanaf dag één.
Wat partitionering verandert voor adminpaneel‑filters
Zodra je een logtabel partitioneert, worden adminpaneelfilters geen louter UI‑kwestie meer. Ze bepalen in de praktijk of een query een paar partities raakt of maanden aan data moet scannen.
De grootste praktische verandering: tijd kan niet langer optioneel zijn. Als gebruikers een onbeperkte zoekopdracht kunnen doen (geen datumbereik, alleen “toon alles voor gebruiker X”), moet PostgreSQL mogelijk elke partitie doorlopen. Zelfs als elke check snel is, voegt het openen van veel partities overhead toe en voelt de pagina traag.
Een rule‑of‑thumb die goed werkt: verplicht een tijdvenster voor log‑ en auditzoekopdrachten en zet een verstandige default (zoals laatste 24 uur). Als iemand echt “alle tijd” nodig heeft, maak dat dan een bewuste keuze met een waarschuwing dat het traag kan zijn.
Zorg dat filters passen bij partition pruning
Partition pruning helpt alleen als de WHERE‑clausule de partition key in een vorm bevat die PostgreSQL kan gebruiken. Filters zoals created_at BETWEEN X AND Y prunen netjes. Patronen die pruning vaak breken zijn het casten van timestamps naar dates, het omwikkelen van de kolom in functies of filteren op een ander tijdveld dan de partition key.
Binnen elke partitie moeten indexen overeenkomen met hoe mensen daadwerkelijk filteren. In de praktijk zijn de combinaties die vaak van belang zijn: tijd plus één andere conditie: tenant/workspace, gebruiker, actie/type, entiteit‑ID of status.
Sorteren en pagineren: houd het ondiep
Partitionering lost trage paginering niet vanzelf op. Als het adminpaneel sorteert op nieuwste eerst en gebruikers naar pagina 5000 springen, dwingt diepe OFFSET‑paginering PostgreSQL nog steeds veel rijen te overslaan.
Cursor‑stijl paginering gedraagt zich beter voor logs: “laad events vóór deze timestamp/id.” Het houdt de database bij indexen in plaats van grote offsets te skippen.
Presets helpen ook. Een paar opties volstaan meestal: laatste 24 uur, laatste 7 dagen, vandaag, gisteren, aangepast bereik. Presets verminderen toevallige “scan alles” zoekopdrachten en maken de adminervaring voorspelbaarder.
Veelvoorkomende fouten en valkuilen
De meeste partitioneringsprojecten mislukken om simpele redenen: partitionering op zich werkt, maar queries en de admin‑UI sluiten er niet op aan. Als je wilt dat partitionering rendeert, ontwerp het rond echte filters en echte retentie.
- Partitioneren op de verkeerde tijdkolom
Pruning werkt alleen als de WHERE‑clausule overeenkomt met de partition key. Een veelgemaakte fout is partitioneren op created_at terwijl het adminpaneel filtert op event_time (of andersom). Als je supportteam altijd vraagt “wat gebeurde tussen 10:00 en 10:15”, maar de tabel is partitioneerd op ingestietijd, raak je alsnog meer data dan verwacht.
- Te veel kleine partities maken
Uur‑ (of kleinere) partities zien er netjes uit, maar ze voegen overhead toe: meer objecten, meer plannerwerk en meer kans op ontbrekende indexen of mismatched permissies.
Tenzij je extreem hoge schrijflast en strikte retentie hebt, zijn dagelijkse of maandelijkse partities meestal eenvoudiger te beheren.
- Aannemen dat globale uniekheid nog werkt
Gepartitioneerde tabellen hebben beperkingen: sommige unieke indexen moeten de partition key bevatten, anders kan PostgreSQL ze niet over alle partities afdwingen.
Dit verrast teams die verwachten dat event_id altijd uniek is. Als je een uniek identifier nodig hebt, gebruik een UUID en maak het uniek samen met de tijdsleutel, of handhaaf uniekheid in de applicatielaag.
- De admin‑UI vrije zoekopdrachten laten uitvoeren
Adminpanelen leveren vaak een vriendelijke zoekbox die zonder filters draait. Op een gepartitioneerde logtabel kan dat betekenen dat elke partitie gescand wordt.
Full‑text search over payloads is bijzonder riskant. Bouw guardrails: verplicht een tijdsvenster, begrens de standaards en maak “alle tijd” een bewuste keuze.
- Geen retentieplan (en geen plan voor partities)
Partitionering lost retentie niet automatisch op. Zonder beleid stapel je oude partities op, krijg je rommelige opslag en vertraagt onderhoud.
Een eenvoudige set operationele regels voorkomt dit: definieer hoelang raw events blijven, automatiseer het aanmaken van toekomstige partities en het droppen van oude, pas indexen consistent toe, monitor partitieaantallen en grensdata, en test de traagste adminfilters met realistische datahoeveelheden.
Korte checklist voordat je beslist
Partitionering kan veel voordeel bieden voor auditlogs, maar het voegt routinewerk toe. Controleer voordat je het schema verandert hoe mensen de tabel daadwerkelijk gebruiken.
Als je grootste pijn is dat adminpagina's timen‑out bij “Laatste 24 uur” of “Deze week”, dan ben je dichtbij een goede match. Als de meeste queries “user ID over alle geschiedenis” zijn, helpt partitionering minder tenzij je ook de UI aanpast om zoeken te sturen.
Een korte checklist:
- Tijdsvenster is de standaardfilter. De meeste adminqueries bevatten een duidelijk window (van/tot). Als open‑ended zoekopdrachten veel voorkomen, helpt partition pruning minder.
- Retentie wordt afgedwongen door partities te droppen, niet door rijen te verwijderen. Je bent comfortabel met het droppen van oude partities en hebt een duidelijke regel voor hoe lang data bewaard moet blijven.
- Partitietelling blijft redelijk. Reken uit hoeveel partities per jaar (dagelijks, wekelijks, maandelijks). Te veel kleine partities verhogen overhead; te weinig grote partities verkleinen het voordeel.
- Indexen komen overeen met de filters die echt gebruikt worden. Naast de partition key heb je nog steeds de juiste per‑partitie indexen nodig voor veelgebruikte filters en sorteervolgorde.
- Partities worden automatisch aangemaakt en gemonitord. Een routinejob maakt toekomstpartities aan en je weet wanneer die faalt.
Een praktische test: kijk naar de drie filters die je support of ops team het meest gebruikt. Als twee daarvan meestal worden afgedekt door “tijdsvenster + één extra conditie”, dan is PostgreSQL‑partitionering voor eventtabellen vaak de moeite waard om serieus te overwegen.
Een realistisch voorbeeld en praktische vervolgstappen
Een supportteam heeft de hele dag twee schermen open: “Login events” (succesvolle en mislukte aanmeldingen) en “Security audits” (wachtwoordresets, rolwijzigingen, API‑sleutelupdates). Wanneer een klant verdachte activiteit meldt, filtert het team op gebruiker, kijkt de laatste uren en exporteert een kort rapport.
Voor partitionering staat alles in één grote events‑tabel. Die groeit snel en zelfs simpele zoekopdrachten beginnen te hangen omdat de database door veel oude rijen werkt. Retentie is ook lastig: een nachtjob verwijdert oude rijen, maar grote deletes duren lang, veroorzaken bloat en concurreren met normaal verkeer.
Na partitionering per maand (op event‑timestamp) verbetert de workflow. Het adminpaneel verplicht een tijdfilter, dus de meeste queries raken slechts één of twee partities. Pagina's laden sneller omdat PostgreSQL partities buiten het geselecteerde bereik kan negeren. Retentie wordt routine: in plaats van miljoenen rijen te verwijderen, dropt je oude partities.
Iets blijft moeilijk: full‑text zoeken over “alle tijd”. Als iemand een IP‑adres zoekt of een vage frase zonder datumlimiet, kan partitionering dat niet goedkoop maken. De oplossing zit meestal in productgedrag: zet zoekopdrachten standaard naar een tijdvenster en maak “laatste 24 uur / 7 dagen / 30 dagen” de voor de hand liggende opties.
Praktische vervolgstappen die vaak werken:
- Breng eerst je adminpaneelfilters in kaart. Schrijf op welke velden mensen gebruiken en welke verplicht zijn.
- Kies partities die aansluiten bij hoe je bladert. Maandelijkse partities zijn vaak een goede start; ga pas naar wekelijks als het volume dat vereist.
- Maak tijdsvensters een kernonderdeel van de UX. Als de UI “geen datum” toestaat, verwacht trage pagina's.
- Stem indexen af op echte filters. Wanneer tijd altijd aanwezig is, is een time‑first indexstrategie vaak de juiste basis.
- Stel retentieregels in die bij partitiongrenzen passen (bijv. 13 maanden behouden en alles ouder verwijderen).
Als je een intern adminpaneel bouwt met AppMaster (appmaster.io), is het de moeite waard deze aannames vroeg in het model op te nemen: behandel tijdsgebonden filters als onderdeel van het datamodel, niet alleen als een UI‑keuze. Die kleine beslissing beschermt queryprestaties naarmate het logvolume groeit.
FAQ
Partitionering helpt vooral wanneer je veel queries hebt die tijdgebonden zijn (bijvoorbeeld “laatste 24 uur” of “laatste 7 dagen”) en de tabel zo groot is geworden dat indexen en onderhoud zwaar beginnen te wegen. Als je hoofdzakelijk zoekt op “alle geschiedenis voor gebruiker X”, kan partitionering extra overhead veroorzaken tenzij je tijdfilters afdwingt in de UI en de juiste per‑partition indexen toevoegt.
Range‑partitionering op tijd is meestal het beste uitgangspunt voor logs en audits: writes komen in tijdsvolgorde binnen, queries beginnen vaak met een tijdvenster en retentie is tijdgebonden. List‑ of hash‑partitionering werkt in speciale gevallen, maar maakt vaak retentie en browsen lastiger voor audit‑workflows.
Kies het veld dat gebruikers als eerste en vrijwel altijd gebruiken om te filteren. In de meeste adminpanelen is dat een tijdsinterval, dus tijdgebaseerde partitionering is de meest voorspelbare keuze. Zie het als een langetermijnkeuze: het veranderen van de partition key later is een echte migratieklus.
Gebruik sleutelvelden zoals een timestamp of tenant‑identifier alleen wanneer ze een beheersbaar aantal partities opleveren. Vermijd hoge‑cardinaliteit sleutels zoals user_id, session_id of request_id, omdat die duizenden partities kunnen maken, de planner en operatie bemoeilijken en meestal geen consistente snelheidswinst geven.
Partitioneer op created_at wanneer je operationele stabiliteit nodig hebt en je late aankomsten (queues, retries, offline clients) niet vertrouwt. Partitioneer op occurred_at wanneer je primaire behoefte is “wat gebeurde er tijdens dit venster” en de eventtijd betrouwbaar is. Een veelgebruikte tussenoplossing is partitioneren op created_at en occurred_at indexeren voor filtering.
Ja. Zodra een tabel gepartitioneerd is, moet de admin‑UI meestal een tijdfilter verplichten. Zonder tijdfilter moet PostgreSQL mogelijk veel of alle partities doorzoeken, waardoor pagina's traag worden. Een goede standaard is “laatste 24 uur”, met “alle tijd” als bewuste optie.
Ja. Het omwikkelen van de partition key in functies (bijvoorbeeld casten naar date) kan pruning blokkeren, en filteren op een ander tijdveld dan de partition key kan extra partities doen scannen. Gebruik eenvoudige vormen zoals created_at BETWEEN X AND Y om pruning betrouwbaar te houden.
Vermijd diepe OFFSET‑paginering voor logweergaven, want die dwingt de database veel rijen over te slaan. Gebruik cursor‑stijl paginering, bijvoorbeeld “laad events vóór deze (timestamp, id)”, dat indexvriendelijk blijft en de prestaties stabiel houdt naarmate de tabel groeit.
In PostgreSQL moeten sommige unieke constraints de partition key bevatten; een globaal unieke id werkt daarom mogelijk niet zoals verwacht. Een praktisch patroon is een samengestelde unique zoals (created_at, id) wanneer created_at de partition key is. Als je een uniek extern ID nodig hebt, gebruik een UUID en behandel globale uniekheid zorgvuldig.
Partities droppen is snel en voorkomt bloat en extra vacuumwerk dat grote DELETEs veroorzaken. Zorg dat retentierichtlijnen overeenkomen met partition boundaries en automatiseer het: maak toekomstpartities op tijd aan en verwijder verlopen partities volgens schema. Zonder automatisering wordt partitionering handwerk.


