Bevor wir uns mit SQL, Indexen und Tuning beschäftigen, lohnt sich eine grobe Landkarte: Was läuft eigentlich, wenn man Postgres startet? Wie kommt eine Query von der Anwendung bis ins Datenfile, und wie sorgt Postgres dafür, dass nichts verloren geht? Dieser Artikel ist bewusst überblicksartig — Details zu MVCC, WAL und Background-Prozessen folgen in eigenen Kapiteln.
Prozess-Modell: Postmaster + Backend pro Verbindung
PostgreSQL nutzt ein Multi-Process-Modell, kein Multi-Threading. Beim Start läuft ein zentraler Prozess, historisch Postmaster genannt, heute meist Postgres-Hauptprozess. Er hört auf dem konfigurierten Port (Standard 5432) und tut selbst keine Query-Arbeit.
Verbindet sich ein Client, fork’t der Hauptprozess einen eigenen Backend-Prozess für genau diese Verbindung. Die Query-Arbeit — Parser, Planner, Executor, Index-Zugriff, Tupel-Lesen — läuft komplett in diesem Backend.
ps -ef | grep postgres
# postgres 1234 ... /usr/lib/postgresql/18/bin/postgres -D /var/lib/postgresql/18/main
# postgres 1240 1234 postgres: checkpointer
# postgres 1241 1234 postgres: background writer
# postgres 1242 1234 postgres: walwriter
# postgres 1243 1234 postgres: autovacuum launcher
# postgres 1244 1234 postgres: logical replication launcher
# postgres 2050 1234 postgres: myuser mydb 127.0.0.1(54812) idleKonsequenzen aus diesem Modell:
- Eine Verbindung = ein Prozess = nicht ganz billig. Connections in dreistelliger Zahl bremsen den Server merklich. Lösung: Connection Pooling mit pgBouncer oder einem App-internen Pool.
- Backends sehen sich nicht direkt. Sie kommunizieren über
Shared Buffersund Locks im gemeinsamen Speicher. - Crasht ein Backend, überlebt der Server. Der Postmaster bemerkt den Crash und beendet andere Backends sicherheitshalber, startet aber sauber weiter.
Shared Buffers — der Postgres-eigene Cache
Postgres liest Datenseiten (8 KB pro Seite per Default) nicht direkt aus den Datenfiles. Stattdessen gibt es einen großen Shared Buffer Pool, dimensioniert über den Parameter shared_buffers. Eine Query sucht zuerst dort; nur bei einem Cache-Miss landet ein Read am OS-Cache und ggf. auf der Platte.
Wichtig: Postgres setzt bewusst auf den OS-Cache als zweite Ebene. Empfohlen sind typischerweise shared_buffers ≈ 25 % des verfügbaren RAMs — der Rest bleibt dem Page-Cache des Betriebssystems überlassen. Das unterscheidet Postgres von Datenbanken wie Oracle oder SQL Server, die den Großteil des Speichers selbst verwalten.
Write-Ahead Log (WAL) — die Crash-Versicherung
Wenn eine Transaktion ein Tupel ändert, schreibt Postgres die Änderung zuerst in das Write-Ahead Log und erst danach (oft viel später) in die eigentlichen Daten-Dateien. Das WAL liegt im Verzeichnis pg_wal/ (früher pg_xlog/).
Damit das Verfahren funktioniert, muss bei COMMIT der entsprechende WAL-Eintrag persistent auf der Platte sein — die Daten-Datei selbst aber nicht. Das ist der Kern des D in ACID:
- Bei einem Crash kann Postgres aus dem WAL alle bestätigten Transaktionen wiederherstellen.
- Verloren gehen nur Transaktionen, die noch nicht committet waren.
Periodisch macht Postgres einen Checkpoint: alle dreckigen Buffer werden zurück in die Daten-Dateien geschrieben, der WAL-Punkt davor wird gemerkt. Beim Crash-Recovery muss nur das WAL ab dem letzten Checkpoint abgespielt werden — das hält die Recovery-Zeit handhabbar.
Das WAL ist auch die Grundlage für:
- Replikation (Streaming Replication = WAL an Replikas streamen),
- Point-in-Time-Recovery (WAL archivieren, später bis zu beliebigem Zeitpunkt zurückspielen),
- Logische Replikation (PUBLICATION/SUBSCRIPTION decodieren das WAL).
MVCC in einem Satz
PostgreSQL nutzt Multi-Version Concurrency Control. Statt UPDATE in-place zu machen, schreibt Postgres bei jeder Änderung eine neue Tupel-Version und lässt die alte stehen, bis sie niemand mehr sehen kann. Jeder Snapshot einer Transaktion sieht eine konsistente Sicht — Reader blockieren keine Writer und umgekehrt.
Der Preis dafür: alte Tupel-Versionen werden mit der Zeit zu Bloat. Postgres räumt sie über VACUUM (meist automatisch via autovacuum) wieder weg. MVCC bekommt ein eigenes Kapitel — hier reicht: Lesen blockt nicht, aber regelmäßiges Aufräumen ist kein Luxus, sondern Pflicht.
Hintergrundprozesse auf einen Blick
Zusätzlich zum Hauptprozess und den Backends laufen mehrere Helfer ständig im Hintergrund:
| Prozess | Aufgabe |
|---|---|
| checkpointer | Schreibt regelmäßig dreckige Buffer in die Daten-Dateien (Checkpoint). |
| background writer | Schreibt zwischen Checkpoints proaktiv dreckige Buffer raus, damit Backends nicht selbst auf I/O warten müssen. |
| walwriter | Flusht WAL-Records aus dem Shared Buffer in die WAL-Dateien. |
| autovacuum launcher + autovacuum worker | Räumt tote Tupel weg, aktualisiert Statistiken (ANALYZE), verhindert Transaction-ID-Wraparound. |
| logical replication launcher | Startet Worker für logische Replikation, falls Subscriptions konfiguriert sind. |
| stats collector / cumulative stats | Sammelt Laufzeit-Statistiken (Tabellenzugriffe, Funktionsaufrufe, …) für pg_stat_*-Views. |
| archiver (optional) | Kopiert geschriebene WAL-Segmente in ein Archiv (für PITR oder Backup). |
In ps-Ausgabe oder htop siehst du diese Prozesse mit Namen wie postgres: checkpointer — Postgres setzt den Prozess-Titel selbst, damit klar ist, was wer tut.
Datenverzeichnis-Layout (PGDATA)
Alles, was Postgres persistent hält, liegt unter dem Daten-Verzeichnis (PGDATA). Auf Ubuntu z. B. /var/lib/postgresql/18/main/. Wichtige Unterordner:
| Pfad | Inhalt |
|---|---|
base/ | Eigentliche Daten-Dateien, sortiert nach Datenbank-OID und Relation-OID. |
global/ | Cluster-weite Tabellen (Rollen, Datenbanken, Tablespaces). |
pg_wal/ | Write-Ahead-Log-Segmente (jeweils 16 MB per Default). |
pg_xact/ | Commit-Status pro Transaction-ID. |
pg_stat/ | Persistente Statistik-Snapshots. |
postgresql.conf | Hauptkonfiguration. |
pg_hba.conf | Authentifizierungs-Regeln. |
postmaster.pid | PID-Datei des laufenden Hauptprozesses. |
Wichtig: das Daten-Verzeichnis selbst ist kein geeignetes Backup-Ziel und sollte nie händisch kopiert werden, während Postgres läuft. Für konsistente Backups gibt es pg_dump (logisch) und pg_basebackup (physisch) — siehe Kapitel Backup & Restore.
Interessantes
Multi-Process statt Multi-Threading ist Absicht.
Postgres bekam in seiner Geschichte mehrfach Anläufe, auf Threading umzustellen — und hat sich jedes Mal dagegen entschieden. Prozesse haben klar getrennte Adressräume, was Bugs in Extensions weniger gefährlich macht. Der Preis: Connections sind teurer als bei Thread-basierten DBs wie MySQL.
WAL-Segmente sind 16 MB groß — und das passt fast immer.
Du kannst die Größe beim Initdb mit --wal-segsize ändern, aber im Alltag ist die Default-Größe sehr selten der Engpass. Wer das WAL-Volumen reduzieren will, fängt eher bei wal_compression = on an als bei der Segmentgröße.
shared_buffers ist nicht alles.
Anders als bei vielen kommerziellen DBs ist shared_buffers bewusst klein dimensioniert — typischerweise 25 % des RAMs, selten mehr. Der OS-Cache übernimmt die zweite Stufe. Wer shared_buffers auf 80 % stellt, macht es meist langsamer, nicht schneller.
Autovacuum ist nicht optional.
In jedem Tutorial steht „MVCC ist toll, kein Locking auf Reads“ — selten dabei: ohne autovacuum füllt sich die Tabelle mit toten Tupeln, Indexe blähen auf, und irgendwann droht Transaction-ID-Wraparound mit Read-Only-Modus. Autovacuum sollte aktiv bleiben; das Default-Tuning reicht für die meisten Workloads.
Postgres skaliert vertikal sehr gut, horizontal nur mit Hilfe.
Auf einer großen Maschine kommt Postgres weit — viele Produkte fahren bis weit über eine Milliarde Zeilen pro Tabelle gut. Echtes Sharding ist hingegen kein Kern-Feature; dafür gibt es Erweiterungen (Citus) oder Forks (Greenplum, CockroachDB). Streaming Replication löst Read-Skalierung, aber kein Write-Sharding.
Weiterführende Ressourcen
Externe Quellen
- Server Programming – Architecture – PostgreSQL Documentation
- WAL Internals – PostgreSQL Documentation
- Concurrency Control (MVCC) – PostgreSQL Documentation
- Background Processes – PostgreSQL Documentation
- Database File Layout – PostgreSQL Documentation