Das Blatt zum Seminar PostgreSQL Performance und Sicherheit in der Praxis — Parameter, Entscheidungsraster und Kurzformen zum Nachschlagen, wenn man vor einer Instanz sitzt und den Namen gerade nicht parat hat. Es bringt das Vorgehen nicht bei; wie man misst, deutet und daraus eine Maßnahme ableitet, ist der Inhalt der Module. Maßgeblich bleibt in jedem Zweifelsfall die PostgreSQL-Dokumentation zur eingesetzten Version.
Speicherparameter
| Parameter | Wirkung |
|---|---|
shared_buffers | Gemeinsamer Cache für Tabellen- und Indexseiten |
work_mem | Pro Operation für Sortierung, Hash und Aggregation |
effective_cache_size | Belegt nichts, schätzt für den Planner den Cache |
maintenance_work_mem | Für VACUUM, CREATE INDEX und REINDEX |
Nur effective_cache_size reserviert keinen Speicher. work_mem ist der
gefährlichste Wert: Er gilt je Sortierung, Hash und Aggregation — eine Abfrage
mit mehreren solchen Schritten belegt ihn mehrfach, mal der Zahl aktiver
Sitzungen.
Modul: Serverkonfiguration und Umgebung
Eine Einstellung ändern und nachweisen
ALTER SYSTEM SET work_mem = '32MB';
SELECT pg_reload_conf();
SHOW work_mem; -- neuer Wert steht
Steht nach dem Reload noch der alte Wert, verlangt der Parameter einen Neustart.
Vorab sagt das die Spalte context in pg_settings. SHOW config_file zeigt,
welche Datei die Instanz wirklich gelesen hat — postgresql.auto.conf überstimmt
die von Hand gepflegte postgresql.conf. Zurückgenommen wird mit
ALTER SYSTEM RESET.
Modul: Serverkonfiguration und Umgebung
Checkpoints und Write-Ahead-Logging
| Parameter | Wirkung |
|---|---|
checkpoint_timeout | Zeitabstand, nach dem ein Checkpoint erzwungen wird |
max_wal_size | WAL-Menge, ab der ein Checkpoint ausgelöst wird |
min_wal_size | Untergrenze, hält Dateien für den Dauerbetrieb vor |
checkpoint_completion_target | Über welchen Anteil des Intervalls geschrieben wird |
wal_level | Wie viel Information ins Log geschrieben wird |
wal_buffers | Pufferspeicher für WAL-Schreibzugriffe |
wal_level bestimmt zugleich, ob Replikation und Wiederherstellung auf einen
Zeitpunkt möglich sind. Wer checkpoint_timeout erhöht, muss max_wal_size
mitziehen, sonst greift weiter die WAL-Grenze.
SELECT num_timed, num_requested, write_time, sync_time
FROM pg_stat_checkpointer; -- ab Version 17
SELECT wal_records, wal_bytes FROM pg_stat_wal;
Viele num_requested gegenüber num_timed heißt: max_wal_size ist für diese
Last zu knapp bemessen. Vor Version 17 standen die Checkpoint-Zähler in
pg_stat_bgwriter.
Modul: Serverkonfiguration und Umgebung
Ausführungspläne lesen
| Angabe | Bedeutung |
|---|---|
cost | Geschätzter Aufwand, einheitenlos, nur zum Vergleich |
rows und actual rows | Schätzung gegen tatsächliche Zeilenzahl |
Rows Removed by Filter | Gelesen und wieder verworfen — der klassische Hebel |
Buffers | Wie viele Seiten aus Cache und Platte gelesen wurden |
Workers launched | Wie viele Prozesse parallel gearbeitet haben |
Heap Fetches | Über null heißt: kein reiner Index-Only-Zugriff |
Weichen Schätzung und Wirklichkeit stark ab, ist meist die Statistik das Thema, nicht der Index. Gemessen wird immer mit Puffern:
EXPLAIN (ANALYZE, BUFFERS) SELECT …;
ANALYZE führt die Abfrage wirklich aus — bei schreibenden Anweisungen also nur
in einer Transaktion, die zurückgerollt wird.
Modul: Abfrage-Performance und Indizes
Welcher Index für welches Muster
| Form | Wofür |
|---|---|
| B-Tree auf einer Spalte | Gleichheit, Bereiche und Sortierung — der Regelfall |
Zusammengesetzt (a, b) | Beide Spalten in wechselnden Kombinationen im Filter |
Partiell WHERE status = … | Auswertungen betreffen fast immer denselben Wert |
INCLUDE (…) (abdeckend) | Index Only Scan, Heap Fetches auf null |
GIN jsonb_ops | Viele Operatoren auf JSONB, größerer Index |
GIN jsonb_path_ops | Nur Enthaltensein @>, deutlich kleiner und schneller |
CREATE INDEX buchung_bezahlt_idx ON buchung (gebucht_am)
WHERE status = 'bezahlt';
CREATE INDEX buchung_uebersicht_idx ON buchung (gebucht_am DESC, status)
INCLUDE (id, kunde_id);
CREATE INDEX veranstaltung_details_gin ON veranstaltung USING gin (details);
Die zuerst genannte Spalte eines zusammengesetzten Index muss in der Bedingung
stehen. INCLUDE-Spalten sind nicht Teil des Suchschlüssels — wonach auch
gefiltert wird, gehört in den Schlüssel. Nach dem Anlegen ANALYZE, im
Produktivsystem CREATE INDEX CONCURRENTLY. Der Index-Only-Zugriff hängt an
VACUUM: nur als sichtbar markierte Seiten erlauben ihn.
Modul: Abfrage-Performance und Indizes
Statistik und Umformulierung
SELECT relname, last_analyze, last_autoanalyze, n_mod_since_analyze
FROM pg_stat_user_tables WHERE relname = 'buchung';
ANALYZE buchung;
ANALYZE ändert keine Daten, es aktualisiert nur das Bild, das der Planner von
ihnen hat — nötig nach Massenimport, nach einem UPDATE über einen großen Teil der
Tabelle und direkt nach einer Migration. Eine korrelierte Unterabfrage
(SubPlan, eine Ausführung je äußerer Zeile) wird zur Verbund-Formulierung mit
GROUP BY; das ist meist der größere Hebel als ein weiterer Index. Vorsicht bei
der Semantik: Ein JOIN verwirft Zeilen ohne Posten.
Modul: Abfrage-Performance und Indizes
Autovacuum, MVCC und Wartungsmittel
| Parameter | Bedeutung |
|---|---|
autovacuum_vacuum_scale_factor | Anteil geänderter Zeilen als Auslöser |
autovacuum_vacuum_threshold | Mindestzahl geänderter Zeilen als Auslöser |
autovacuum_vacuum_cost_limit | Wie viel Arbeit am Stück, bevor pausiert wird |
autovacuum_max_workers | Wie viele Tabellen gleichzeitig bearbeitet werden |
Bei vielen großen Tabellen bremsen oft die Worker, nicht der Schwellwert. Für einzelne Vieländerungstabellen tabellenbezogen statt global stellen:
ALTER TABLE buchung SET (autovacuum_vacuum_scale_factor = 0.02,
autovacuum_vacuum_threshold = 500);
| Mittel | Wirkung |
|---|---|
ANALYZE | Frischt nur die Statistik für den Planner auf |
VACUUM | Gibt Platz zur Wiederverwendung frei, verkleinert die Datei nicht |
REINDEX | Baut aufgeblähte Indizes neu auf |
VACUUM FULL | Schreibt die Tabelle neu, gibt Platz ans System zurück, sperrt |
Rückstand erkennt man daran, dass der Anteil toter Zeilen über Tage steigt, statt nach jedem Lauf zu fallen, und dass das Transaktionsalter stetig wächst:
SELECT relname, n_live_tup, n_dead_tup, last_autovacuum
FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;
SELECT datname, age(datfrozenxid) FROM pg_database ORDER BY 2 DESC;
Module: Serverkonfiguration und Umgebung · Überwachung, Sperrverhalten und Wartung
Sperren und blockierende Sitzungen
SELECT a.pid, a.state, a.wait_event_type,
pg_blocking_pids(a.pid) AS blockiert_von,
now() - a.xact_start AS transaktionsalter
FROM pg_stat_activity a
WHERE cardinality(pg_blocking_pids(a.pid)) > 0;
SELECT pg_cancel_backend(4711); -- sanft: Anweisung abbrechen
SELECT pg_terminate_backend(4711); -- hart: Sitzung beenden
Steht der Blockierer auf idle in transaction, fehlt nicht Rechenleistung,
sondern ein COMMIT. Beenden rollt die offene Transaktion zurück und lässt die
Anwendung mit einem Verbindungsabbruch zurück — deshalb das letzte Mittel. Ein
Anteil idle bei wenigen active in pg_stat_activity ist das klassische Bild
für einen fehlenden Verbindungspool.
Module: Abfrage-Performance und Indizes · Überwachung, Sperrverhalten und Wartung
Überwachung: welches Werkzeug beantwortet was
| Werkzeug | Beantwortet |
|---|---|
pg_stat_statements | Welche Anweisungen die meiste Zeit verbrauchen |
| Serverlog | Was passiert ist, als niemand hingesehen hat |
pg_stat_activity und pg_locks | Wer gerade wartet und worauf |
pg_stat_user_tables | Ob die Wartung mit der Last Schritt hält |
Die teuersten Anweisungen sortiert man nach total_exec_time, nicht nach
mean_exec_time — sonst verschwinden die häufig laufenden aus dem Bild.
pg_stat_statements_reset() startet einen neuen Messzeitraum.
| Protokolleinstellung | Zweck |
|---|---|
log_min_duration_statement | Anweisungen ab einer Laufzeitschwelle festhalten |
log_checkpoints | Checkpoint-Tätigkeit mitschreiben |
log_lock_waits | Wartezeiten auf Sperren ab deadlock_timeout melden |
logging_collector | Ausgabe in verwaltete Dateien schreiben (Neustart) |
log_line_prefix | Benutzer, Datenbank, Herkunft je Zeile |
log_connections / log_disconnections | Verbindungsaufnahme und Sitzungsende |
log_statement ist nicht log_min_duration_statement: Das eine schreibt alles
mit, samt schützenswerter Werte.
Modul: Überwachung, Sperrverhalten und Wartung
Sicherheitsschichten
| Schicht | Mittel in PostgreSQL |
|---|---|
| Netzzugang | listen_addresses und pg_hba.conf |
| Authentifizierung | SCRAM-SHA-256, ab Version 18 auch OAuth |
| Übertragung | TLS, in den Einstellungen weiterhin SSL genannt |
| Autorisierung | Rollen, Rechte, Standardrechte, Row Level Security |
| Nachweis | Protokollierung und Statistiksichten |
Die Reihenfolge ist zugleich der Weg einer Verbindung — jede Schicht kann sie beenden. Zum Nachweis, dass die Schichten wirklich greifen:
SHOW password_encryption; -- soll scram-sha-256 sein
SELECT line_number, type, database, user_name, address, auth_method, error
FROM pg_hba_file_rules ORDER BY line_number;
SELECT a.usename, a.client_addr, s.ssl, s.version
FROM pg_stat_activity a JOIN pg_stat_ssl s ON s.pid = a.pid;
Die Spalte error in pg_hba_file_rules meldet Zeilen, die der Server nicht
übernehmen konnte — sie wirken dann gar nicht. Der Server nimmt die erste
passende Regel; eine großzügige Regel oben macht jede strengere darunter
wirkungslos. Bestehende Kennwörter wandeln sich nicht von selbst auf SCRAM um,
sie müssen einmal neu gesetzt werden.
Modul: Sicherheit im Produktivbetrieb
Rollen, Rechte und Row Level Security
Eigentum, Rechte und Anmeldung getrennt halten — die Anmelderolle nutzt die Objekte, besitzt sie aber nicht:
CREATE ROLE tonspur_owner NOLOGIN;
CREATE ROLE tonspur_readwrite NOLOGIN;
CREATE ROLE tonspur_app LOGIN PASSWORD '…';
GRANT tonspur_readwrite TO tonspur_app;
GRANT USAGE ON SCHEMA verkauf TO tonspur_readwrite;
GRANT SELECT, INSERT, UPDATE ON verkauf.buchung TO tonspur_readwrite;
ALTER DEFAULT PRIVILEGES FOR ROLE tonspur_owner IN SCHEMA verkauf
GRANT SELECT, INSERT, UPDATE ON TABLES TO tonspur_readwrite;
Standardrechte wirken nur für künftige Objekte dieser Rolle in diesem Schema,
nie rückwirkend. \dp verkauf.buchung zeigt den Ist-Stand; nachsehen sollte man
zuerst bei Rechten, die zur Fehlersuche vergeben wurden, bei der Rolle public
und beim search_path.
ALTER TABLE verkauf.veranstaltung ENABLE ROW LEVEL SECURITY;
CREATE POLICY nur_eigene ON verkauf.veranstaltung
FOR SELECT USING (veranstalter = current_user);
| Rolle | Verhalten gegenüber RLS |
|---|---|
| Superuser | Umgeht sämtliche Regeln |
| Eigentümer der Tabelle | Umgeht sie standardmäßig |
Rolle mit BYPASSRLS | Ausdrücklich von der Prüfung ausgenommen |
FORCE ROW LEVEL SECURITY erzwingt die Regeln auch für den Eigentümer. Eine
Regel nur für SELECT lässt INSERT und UPDATE offen.
Modul: Sicherheit im Produktivbetrieb
Typische Fallen
- Einheiten übersehen —
16384beishared_bufferssind Blöcke zu 8 kB, nicht Bytes. - Reload verbucht, Wert nicht geprüft — manche Parameter greifen erst nach einem Neustart, und der alte Wert bleibt kommentarlos stehen.
- Kosten für Millisekunden halten —
costist einheitenlos, undEXPLAINohneANALYZEliefert Schätzungen, keine Messwerte. - Nach dem Indexanlegen kein
ANALYZEund sich über den unveränderten Plan wundern. - Für jede Spalte ein eigener Index, statt das Abfragemuster abzubilden — jeder Index kostet bei jeder Änderung, nicht nur beim Massenimport.
- Am Sortierknoten optimieren statt am teuren Durchlauf darunter.
- Autovacuum bei Rückstand abschalten — der Preis kommt später. Lange offene Transaktionen halten jedes Aufräumen auf, gleich wie scharf gestellt.
VACUUM FULLim laufenden Betrieb — es sperrt die Tabelle vollständig; ein normalesVACUUMverkleinert die Datei ohnehin nicht.- Die wartende Sitzung abbrechen statt die blockierende, oder
pg_terminate_backendeinsetzen, wo einCOMMITgenügt hätte. trustaus der Entwicklung stehen lassen, diepg_hba.confändern und den Reload vergessen — oder sich ohne zweiten Zugangsweg selbst aussperren.- Verschlüsselung annehmen, weil der Server sie anbietet — nachweisbar ist sie
nur je Sitzung über
pg_stat_ssl. - Rechte direkt an Anmeldungen vergeben statt an eine Rechte-Rolle, oder die Anwendung mit der Eigentümerrolle verbinden lassen.
- RLS nur mit der administrativen Anmeldung testen und nichts bemerken — Eigentümer und Superuser gehen an jeder Regel vorbei.
Zum Seminar PostgreSQL Performance und Sicherheit in der Praxis