POSETTE Talk Recap - Postgres is niet traag, je storage wel
De presentatie begint met verschillende symptomen die teams die Postgres op schaal draaien herkennen: de ingestie vertraagt, de P95 read latency wordt onvoorspelbaar, autovacuum loopt achter, checkpoints concurreren met de eigenlijke workload om resources, of logische replicatie loopt op in lag. Hoewel deze problemen niet altijd dezelfde oorzaak hebben, is er vaak één gemeenschappelijke factor die gemakkelijk over het hoofd wordt gezien: de prestaties van de storage.
Om dit te begrijpen, voerde Sai een benchmark uit waarbij dezelfde PostgreSQL-workload van 3,3 miljard rijen werd vergeleken op baseline gp3 EBS en lokale NVMe.
Vijf symptomen van schaalproblemen
Er zijn vijf veelvoorkomende problemen die vaak optreden zodra een PostgreSQL-workload honderden gigabytes of terabytes bereikt en draait met reële concurrency en throughput:
- Trage ingestie: Ingestie-pipelines die zwaar leunen op
UPDATEs enUPSERTs. Taken die voorheen in seconden klaar waren, kunnen uitlopen tot minuten naarmate het datavolume groeit. - Inconsistente read latency: In workloads waarbij de meeste queries snel blijven, maar een enkel aantal dramatisch vertraagt. De P95-latency verschuift van milliseconden naar seconden, waardoor dashboards timen en de oorzaak lastiger te isoleren is.
- VACUUM loopt achter: Autovacuum kan achterop raken naarmate de
UPDATEenUPSERTthroughput toeneemt, waardoor table bloat oploopt van gigabytes naar terabytes. - Tragere checkpoints: Checkpoints kunnen langer duren en concurreren met de client-workload om I/O en CPU, waardoor een operatie die voorheen onzichtbaar was, verandert in een terugkerende latency-spike.
- Logische replicatie lag: Dit treedt op wanneer PostgreSQL-data wordt gestreamd naar systemen zoals Kafka of ClickHouse. WAL-decoding kan traag zijn en een groeiende replicatie-slot kan het probleem verergeren.
Hoe deze symptomen verbonden zijn met storage
Wanneer de working set groter wordt dan het geheugen dat beschikbaar is voor PostgreSQL en de page cache van het besturingssysteem, is er meer fysieke I/O nodig voor het lezen van data. Op tragere storage kunnen de hogere kosten en het wachten in de wachtrij (queuing) van deze reads zowel de gemiddelde als de tail-latency verhogen.
Client-queries en onderhoudstaken op de achtergrond maken gebruik van hetzelfde storage-budget. Heap- en index-writes, WAL-synchronisatie, checkpoints, VACUUM en logical-decoding spills concurreren allemaal om bandbreedte, IOPS en wachtrijcapaciteit. Zodra de storage een van deze limieten bereikt, beginnen operaties elkaar te vertragen. Dit is hoe trage storage de bronoorzaak kan zijn van verschillende schijnbaar ongerelateerde PostgreSQL-problemen.
Wat verandert er met lokale NVMe?
Het argument voor lokale NVMe is niet dat het I/O elimineert, maar dat het de kosten daarvan drastisch kan verlagen. Het verschil zit in operaties op milliseconde-schaal bij network-attached storage, tegenover operaties die in tientallen microseconden kunnen worden voltooid op lokale NVMe. Cache misses, WAL flushes en onderhouds-I/O vinden nog steeds plaats, maar ze hebben simpelweg veel minder impact op het totale systeem.
De benchmark: Lokale NVMe versus EBS
Om dit beter te begrijpen, zette Sai een experiment op om een vergelijkbare workload te testen op PostgreSQL draaiend op EBS en lokale NVMe.
Setup
Om storage als enige variabele te isoleren, werden acht identieke PostgreSQL-clusters gebruikt: vier met instance-store NVMe en vier met baseline gp3 EBS, geconfigureerd op 3.000 IOPS. Elk cluster draaide op een m6id.4xlarge instance met 16 vCPUs, 64 GiB RAM en 16 GiB shared_buffers.
De setup gebruikte een pgbench_accounts heap van 482 GiB met 3,3 miljard rijen. Elk cluster voerde gedurende vijf minuten dezelfde highly concurrent random-UPDATE workload uit, met 64 clients en 16 worker threads:
\set aid random(1, 3300000000)
UPDATE pgbench_accounts SET abalance = abalance + 1 WHERE aid = :aid;
Het enige verschil tussen de twee groepen was de storage-klasse.
Resultaten van de benchmark
De benchmark produceerde een mediaan van 16.030 TPS voor de NVMe-clusters, vergeleken met 1.734 TPS op EBS—een verschil van 9,24×. De mediane transactie-latency daalde van 36,9 ms naar 4,0 ms.
Voor andere operaties voltooide NVMe een VACUUM over 10 GB aan bloat in 366 seconden, tegenover 964 seconden op EBS. Een backlog van 10 GB aan logische replicatie werd in 139 seconden weggewerkt op NVMe, tegenover 238 seconden op EBS.
Opmerking: deze resultaten zijn specifiek voor deze workload en configuraties. Een andere EBS-setup zou waarschijnlijk andere resultaten opleveren.
De kloof verklaren
Het verschil in throughput is makkelijker te begrijpen als we kijken naar waar de 37 ms aan transactie-latency bij EBS naartoe ging:
- Page reads: Ongeveer 19 ms op EBS, tegenover 0,3 ms op NVMe.
- WAL fsync bij commit: 11 ms op EBS versus 1,5 ms op NVMe.
- Lock en scheduler overhead: Ongeveer 5 ms versus 0,2 ms.
- CPU-werk: In beide gevallen ongeveer 2 ms.
Het grootste deel van de extra EBS-latency kwam dus voort uit het wachten op storage, niet uit extra CPU-werk door PostgreSQL.
Een snapshot van pgstatactivity liet een vergelijkbaar patroon zien: van de 64 backends wachtten er op EBS 29 op IO:DataFileRead, tegenover negen op NVMe (circa 45% versus 14%).
CPU-profiling voegde een contra-intuïtieve observatie toe. Over een window van 240 seconden accumuleerde de NVMe-host 2.253 CPU-seconden (ongeveer 9,4 cores bezet), terwijl de EBS-host slechts 251 CPU-seconden accumuleerde (ongeveer één core bezet). Dit toont aan dat EBS-processen veel meer tijd off-CPU doorbrachten in afwachting van storage. De NVMe-host hield zijn CPU-capaciteit actiever bezig met nuttig werk, wat de veel hogere throughput verklaart.
De vraag over duurzaamheid (durability)
Het belangrijkste bezwaar tegen lokale NVMe is dat instance-store NVMe gekoppeld is aan de levensduur van een machine; de lokale data verdwijnt als de node uitvalt. De vraag is of PostgreSQL NVMe-latentie kan combineren met productie-waardige beschikbaarheid en recovery.
Het korte antwoord is ja, mits de duurzaamheid wordt gegarandeerd op clusterniveau in plaats van door een individuele Postgres-node.
Postgres op lokale NVMe in productie draaien
Het draaien van PostgreSQL op lokale NVMe betekent dat je moet ontwerpen rondom storage die snel is, maar vluchtig. Het verlies van een node betekent het verlies van de lokale kopie van de data; instance-store volumes kunnen niet worden gesnapshot via de EBS API; en de beschikbare capaciteit hangt af van het gekozen instance-type.
De voorgestelde productie-architectuur hanteert drie hoofdeisen:
1. Hoge beschikbaarheid met twee standbys
Er wordt gebruikgemaakt van quorum-gebaseerde synchrone replicatie met twee standby-kandidaten. Met een configuratie zoals ANY 1 (standby1, standby2) kan een transactie commiten zodra één van de standbys de WAL duurzaam heeft bevestigd. Door de nodes over verschillende availability zones (AZ's) te plaatsen, wordt bescherming geboden tegen het uitvallen van een AZ.
2. Continue backups naar Object Storage
Voor duurzaamheid buiten de database-nodes kunnen open-source tools zoals WAL-G fysieke base backups en continue WAL-archivering verzorgen. Met low-latency WAL shipping kan een RPO (Recovery Point Objective) van enkele seconden worden behaald en is point-in-time recovery mogelijk binnen het retentievenster. Backups en WAL moeten in een apart failure-domein worden bewaard, bij voorkeur met een regionale kopie voor rampenbestrijding.
3. Backups als fundament voor recovery
Base backups en gearchiveerde WAL zijn meer dan een noodmechanisme. Ze ondersteunen point-in-time restores, geïsoleerde branches, het aanpassen van de deployment-omvang, het seeden van read-replica's en disaster recovery. Replicatie en backups lossen verschillende problemen op: standbys zorgen voor snelle failover, terwijl backups beschermen tegen corruptie, menselijke fouten en cluster-brede storingen.
Voorbeelden uit de praktijk
Dit patroon is niet alleen theoretisch. In de presentatie worden voorbeelden genoemd van Instacart, die PostgreSQL op NVMe gebruikt voor latency-gevoelige search-workloads, en Datadog, die lokale NVMe-instances gebruikt voor specifieke workloads. Het terugkerende patroon is: lokale storage voor performance, synchrone replicatie voor beschikbaarheid, en onafhankelijk opgeslagen base backups en WAL-archieven voor recovery.
De belangrijkste conclusie
Wanneer PostgreSQL stopt met schalen, is het raadzaam om de database en de storage samen te inspecteren. Trage ingestie, onvoorspelbare read latency, druk op de vacuum, disruptieve checkpoints en replicatie-lag kunnen signalen zijn dat de storage-laag zijn limiet heeft bereikt.
Lokale NVMe kan de I/O-boete voor storage-bound workloads drastisch verminderen, maar dit vereist een ander operationeel model: quorum-gebaseerde replicatie voor beschikbaarheid en onafhankelijke backups plus gearchiveerde WAL voor recovery.
Groetjes,