PostgreSQL Performance-Settings — Referenz-Artikel
Ziel: Überblick aller performance-relevanten Settings. Für jedes: Was es macht, Default, wann anfassen, typische Werte. Getrennt für Linux und Windows wo es unterschiedlich ist.
Bezugsversion: PostgreSQL 18. Vorgabewerte ändern sich zwischen den Hauptversionen — lies deinen Ist-Wert mit
SHOW <parameter>;ab, bevor du etwas setzt.
Vorab. Alle Werte hier sind Ausgangspunkte, keine Vorgaben. Was zu deinem Server passt, hängt von Arbeitslast, Speicherausstattung und Datenträger ab. Einige Empfehlungen greifen über die Datenbank hinaus — ein Windows-Dienstrecht, der I/O-Scheduler, die Zurückschreib-Schwellen des Kernels. Probiere sie zuerst auf einem Testsystem und nur mit einer aktuellen, wiederherstellbar geprüften Sicherung. Ändere eine Sache pro Schritt, miss davor und danach, und sorge dafür, dass du jeden Eingriff zurücknehmen kannst. Für den Betrieb deiner Systeme bist du verantwortlich.
1. Grundprinzipien
PostgreSQL teilt seinen Speicher in zwei Klassen, die du getrennt konfigurierst:
Faustregel für RAM-Aufteilung
Bei einem dedizierten PG-Server (nichts anderes läuft groß):
| Region | Anteil RAM | Beispiel (32 GB RAM) |
|---|---|---|
shared_buffers |
25% | 8 GB |
| OS-Filesystem-Cache | 50-65% | 16-20 GB |
work_mem × Connections |
10-15% | 3-5 GB |
| Reserve | 5-10% | 2-3 GB |
Bei shared Servern (andere Services parallel): shared_buffers konservativer auf 10-15 % RAM.
2. Memory-Settings
shared_buffers
Was: Interner Buffer-Cache für Table-/Index-Pages. Jeder Read/Write geht erst durch diesen Cache.
Default: 128 MB (viel zu niedrig für moderne Server — immer anpassen)
Faustregel:
- Dedicated DB-Server: 25 % RAM
- Shared-Server: 10-15 % RAM
- Obere Grenze: ~40 % RAM (darüber bringt's nichts, OS-Cache ist effizienter)
Wann anfassen: Immer beim Setup.
Wichtig: shared_buffers ist fest reserviert beim Serverstart. Braucht Restart bei Änderung.
work_mem
Was: Memory-Budget pro Sort/Hash-Operation innerhalb einer Query. Eine komplexe Query kann 5-10 Sort/Hash-Ops haben → jede bekommt eigenes work_mem.
Default: 4 MB (absurd niedrig)
Faustregel:
- Konservativ: 16-32 MB
- Offensiv (wenig Connections): 64-128 MB
- Per-Session overrideable:
SET work_mem = '256MB'für spezifische Jobs
Wann anfassen: Immer. Aber Vorsicht:
- Worst-Case-RAM =
max_connections × work_mem × ~3 (Ops) × multi - Bei 100 Connections × 32 MB × 3 = 9,6 GB potenziell
Signal dass zu klein: EXPLAIN zeigt Sort Method: external merge oder Hash Batches: N (N>1) → Spill-to-Disk.
maintenance_work_mem
Was: Memory für VACUUM, CREATE INDEX, ALTER TABLE. Pro parallel maintenance worker.
Default: 64 MB (oft zu klein)
Empfehlung: 256 MB - 2 GB, je nach RAM. Große maintenance_work_mem macht CREATE INDEX und VACUUM deutlich schneller.
Wann anfassen: Sofort beim Setup. Kann per Session override'd werden wenn gebraucht.
hash_mem_multiplier
Was: Multiplikator für work_mem nur bei Hash-Ops (Hash Join, Hash Aggregate). Hash-Spills sind teurer als Sort-Spills, darum separater Budget-Boost.
Default: 2.0 (seit PG 15), vorher 1.0
Faustregel: 2.0 passt meistens. Auf 3.0-4.0 hochdrehen wenn EXPLAIN viele Hash-Spills zeigt.
Wann anfassen: Selten. Bei stark analytischem Workload mit großen Aggregaten sinnvoll.
temp_buffers
Was: Memory für temporäre Tabellen pro Session.
Default: 8 MB (OK für die meisten)
Wann anfassen: Nur wenn massiv temporäre Tables genutzt werden (ETL-Workloads). Dann auf 64-256 MB.
effective_cache_size
Was: Schätzung wie viel RAM insgesamt für Disk-Caching zur Verfügung steht (inkl. OS-Cache). Der Query-Planner nutzt das um Index-Scans vs. Seq-Scans zu kalkulieren. Keine tatsächliche Reservierung — nur ein Hinweis an den Planner.
Default: 4 GB
Faustregel: 50-75 % des RAMs.
Wann anfassen: Immer. Wirkt nur auf Query-Pläne, nicht auf Memory-Verbrauch.
wal_buffers
Was: Shared-Memory-Puffer für WAL (Write-Ahead Log) bevor er auf Disk geht.
Default: -1 = 1/32 von shared_buffers, min 64 kB, max 16 MB (wird automatisch berechnet)
Wann anfassen: Selten. Bei schreibintensiven Workloads auf 64 MB fix setzen. Sonst Default lassen.
huge_pages (Linux und Windows)
Was: Aktiviert große Speicherseiten für shared_buffers. Das verkleinert die Adressumsetzungstabellen und senkt den Verwaltungsaufwand. Der Parameter heißt auf beiden Plattformen huge_pages; unter Windows heißt das Konzept Large Pages (siehe Abschnitt 11).
Default: try (versucht große Seiten, fällt still zurück)
Werte:
off— deaktivierttry— nutzt große Seiten wenn verfügbar (Standard)on— erzwingt, PG startet nicht ohne (Fehlerdetection)
Wann anfassen: Bei shared_buffers ≥ 4 GB lohnt es sich klar. Linux-Setup siehe separater Artikel zu HugePages.
3. Parallel Query
max_worker_processes
Was: Harter Cap für die Background-Worker (parallele Abfragen, logische Replikation, Extensions wie pg_cron…). Autovacuum zählt nicht hinein — dessen Worker haben über autovacuum_max_workers eigene Slots.
Default: 8 (viel zu niedrig für moderne Server)
Faustregel: CPU-Cores × 2 oder mindestens CPU-Cores, minimum 16.
Wann anfassen: Immer. Braucht Restart.
Symptom wenn zu klein: Queries laufen effektiv seriell trotz max_parallel_workers_per_gather > 0. Du siehst in pg_stat_activity keine parallel worker-Backends trotz Plan-Aussicht.
max_parallel_workers
Was: Wie viele von max_worker_processes dürfen parallel queries fahren (Rest bleibt für autovacuum etc.).
Default: 8
Faustregel: ≤ CPU-Cores. Auf 16-Core-System: 12-16.
Braucht Restart.
max_parallel_workers_per_gather
Was: Maximum worker pro einzelner Query.
Default: 2
Faustregel:
- OLTP (viele Queries, klein): 2
- Analytisch (wenige, große): 4-8
- Reporting-Server: bis 16 möglich
Zur Laufzeit änderbar per Session: SET max_parallel_workers_per_gather = 8;
max_parallel_maintenance_workers
Was: Parallelität für CREATE INDEX, VACUUM.
Default: 2
Faustregel: 4 bei ≥ 8 Cores. Macht CREATE INDEX auf großen Tabellen signifikant schneller.
parallel_tuple_cost / parallel_setup_cost
Was: Kosten, die der Planner für Parallelisierung einrechnet. Hoch = weniger parallel.
Default: 0.1 / 1000
Wann anfassen: Wenn Planner zu selten parallel plant trotz config. Dann parallel_setup_cost = 100 (runter von 1000).
Effekt: Kleinere Queries dürfen parallel werden.
min_parallel_table_scan_size / min_parallel_index_scan_size
Was: Min-Größe einer Tabelle/Index damit Planner überhaupt parallel in Betracht zieht.
Default: 8 MB / 512 kB
Anfassen: Kleiner (z.B. 1MB) macht parallel für kleinere Tables verfügbar. Selten nötig.
4. WAL & Checkpoint
wal_level
Was: Wie viel Information in WAL geschrieben wird.
Werte:
minimal— nur Recovery, kein Streaming Replicationreplica(Default seit PG 10) — unterstützt Standby-Replikationlogical— plus Logical Replication
Anfassen: Nur bei aktiver Replikation.
checkpoint_timeout
Was: Max Zeit zwischen Checkpoints (dirty Pages werden auf Disk synced).
Default: 5 Min (zu oft bei schreibintensiven Workloads)
Faustregel: 15-30 Minuten. Mehr = weniger IO-Spikes, aber mehr WAL zu recovern nach Crash.
max_wal_size / min_wal_size
Was: WAL-Verzeichnis-Größenlimit. Wenn erreicht → Checkpoint-Trigger.
Default: 1 GB / 80 MB
Faustregel:
- Kleiner Server: 2-4 GB
- Schreibintensiv: 8-32 GB
min_wal_size: 1-4 GB
Höher = weniger Checkpoint-Overhead, mehr Disk-Space.
checkpoint_completion_target
Was: Spread des Checkpoint-Writes (0.0 = sofort alles, 1.0 = bis zum nächsten Checkpoint).
Default: 0.9 (modern)
Wann anfassen: Selten. Default 0.9 ist gut.
wal_compression
Was: Komprimiert WAL-Einträge für full-page-writes.
Default: off (historisch)
Empfehlung: on oder lz4 (PG 15+) — reduziert WAL-Volumen deutlich, kostet minimale CPU.
wal_writer_delay / wal_writer_flush_after
Was: Timing des WAL-Writer-Prozesses.
Default: 200 ms / 1 MB
Selten anfassen. Defaults sind vernünftig.
5. Autovacuum
autovacuum
Was: Automatisches VACUUM und ANALYZE im Hintergrund.
Default: on
NIE ausschalten. Außer in speziellen Batch-Szenarien.
autovacuum_max_workers
Was: Wie viele autovacuum-Prozesse parallel laufen dürfen.
Default: 3
Faustregel: 3-6. Abhängig von DB-Größe und Tabellen-Anzahl.
autovacuum_vacuum_scale_factor / autovacuum_analyze_scale_factor
Was: Anteil veränderter Tabelle, bei dem autovacuum getriggert wird.
Default: 0.2 / 0.1 (20 % / 10 %)
Faustregel:
- Sehr große Tabellen: 0.05 / 0.025 (triggert früher — wichtig für Query-Pläne!)
- Kleine Tabellen: Default OK
Szenario: Eine 100-Mio-Row-Tabelle sollte nicht auf 20 Mio Änderungen warten bis ANALYZE läuft — das kostet Stunden an schlechten Query-Plänen.
Per Tabelle overridable:
ALTER TABLE big_table SET (autovacuum_analyze_scale_factor = 0.025);
autovacuum_vacuum_cost_delay
Was: Pause des autovacuum-Workers zwischen Operationen (drosselt IO-Impact).
Default: 2 ms
Faustregel: Bei stark IO-sensiblen Workloads auf 5-10 ms hoch. Sonst Default.
vacuum_cost_limit / autovacuum_vacuum_cost_limit
Was: Bevor autovacuum pausiert.
Default: 200 / -1 (inherit)
Faustregel: Bei schnellen SSDs auf 2000 hochsetzen → VACUUM macht mehr pro Runde → fertig schneller.
6. IO & Planner-Costs
random_page_cost / seq_page_cost
Was: Planner-Kosten für Random- bzw. Sequential-Disk-Access.
Default: 4.0 / 1.0 (basiert auf HDDs)
Faustregel:
- SSDs:
random_page_cost = 1.1(Random ist fast gleich schnell) - NVMe:
random_page_cost = 1.0 - HDDs: Default lassen
Effekt: Planner wählt Index-Scans aggressiver wenn random_page_cost niedriger ist.
effective_io_concurrency
Was: Wie viele parallele IO-Requests der Planner annimmt (für Bitmap-Heap-Scans etc.).
Default: 1 (HDD)
Faustregel:
- SSD: 200
- NVMe: 300-500
- RAID mit vielen Spindeln: N × Spindeln
maintenance_io_concurrency
Was: Gleicher Wert für VACUUM & ANALYZE.
Default: 10
Empfehlung: Doppelt so hoch wie effective_io_concurrency (für schnelleres VACUUM).
default_statistics_target
Was: Wie viele Statistik-Samples ANALYZE pro Spalte anlegt. Mehr = genauere Planner-Entscheidungen, aber längere ANALYZE-Zeiten.
Default: 100
Faustregel: 200-500 bei analytischem Workload, 100 bei OLTP.
Per Spalte overridable:
ALTER TABLE big_table ALTER COLUMN important_col SET STATISTICS 500;
7. Connections & Timeouts
max_connections
Was: Maximum Client-Connections.
Default: 100
Faustregel: So niedrig wie möglich. Jede Connection reserviert Memory (ca. 10 MB) und belegt work_mem × Ops. Besser Connection-Pooler (pgbouncer) davor.
Szenario-Werte:
- Webanwendung mit pgbouncer: 100-200
- Direkt-Connects Admin/Batch: 300-500
- Reporting-Server: 50-100
idle_in_transaction_session_timeout
Was: Killt Sessions die mit offener Transaktion idle sind.
Default: 0 (unlimited — gefährlich)
Empfehlung: 5-15 min. Verhindert „Zombie-Transaktionen“ die autovacuum blockieren.
statement_timeout
Was: Killt Queries die länger laufen als X.
Default: 0 (unlimited)
Empfehlung: Pro Session/Rolle setzen — nicht global:
ALTER ROLE webapp SET statement_timeout = '30s';
ALTER ROLE batch SET statement_timeout = '1h';
lock_timeout
Was: Max Wartezeit auf Lock.
Default: 0 (unlimited)
Empfehlung: In kritischen Sessions 5-30s setzen. Verhindert dass DDL-Operationen ewig blockieren.
8. Shared Memory (Low-Level)
shared_memory_type
Was: Implementierung des shared-memory-Bereichs.
Werte: mmap (Linux-Default), sysv, windows
Anfassen: Fast nie. Nur bei Container-Constraints wo /dev/shm zu klein.
dynamic_shared_memory_type
Was: Implementierung für parallel-worker-Kommunikation.
Werte: posix (Linux-Default), sysv, mmap, windows, none
Anfassen: Fast nie. none deaktiviert parallel queries → nicht anfassen.
min_dynamic_shared_memory
Was: Prä-allokiert dynamic-shared-memory beim Serverstart.
Default: 0 (on-demand)
Anfassen: Bei sehr häufigen parallel queries vielleicht 64-256 MB. Effekt < 1%.
9. Logging (für Analysen)
log_min_duration_statement
Was: Loggt alle Queries, die länger als X laufen.
Default: -1 (aus)
Empfehlung: 1000 (1 Sek) auf Produktion. Zeigt dir schleichende langsame Queries.
log_checkpoints
Was: Loggt jeden Checkpoint.
Default: on (seit PG 15)
Anfassen: Lass on — wichtig für Performance-Analyse.
log_autovacuum_min_duration
Was: Loggt autovacuum-Läufe die länger dauern.
Default: 10 Min (PG 15+), vorher -1 (aus)
Empfehlung: 1000 (1s) — du siehst welche Tabellen autovacuum-Probleme haben.
log_lock_waits
Was: Loggt wenn Sessions > deadlock_timeout auf Locks warten.
Default: off
Empfehlung: on auf Produktion.
log_temp_files
Was: Loggt Queries die temporäre Dateien anlegen (= work_mem gespillt).
Default: -1 (aus)
Empfehlung: 0 (alle) oder 1024 (ab 1 MB). Zeigt dir wo work_mem zu klein ist.
10. Linux-spezifisch
/etc/sysctl.conf (Kernel-Seite)
# Swappen so lange wie möglich vermeiden (DB hat eigenen Cache)
vm.swappiness = 10
# Dirty-Pages früher flushen → weniger IO-Bursts
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
# HugePages für Postgres (siehe postgres-hugepages.md)
vm.nr_hugepages = 9100 # abhängig von shared_buffers
# IPv6 abschalten ist eine Betriebs-/Angriffsflaechen-Entscheidung,
# KEINE Performance-Massnahme - es spart keinen messbaren Overhead
# net.ipv6.conf.all.disable_ipv6 = 1
# SHM-Limits hochdrehen (falls shared_memory_type=sysv)
# kernel.shmmax = 8589934592
# kernel.shmall = 2097152
Transparent HugePages (THP)
Für DB-Server deaktivieren — kann zu sporadischen Stalls führen. Warum und wie, steht in unserer HugePages-Anleitung, Abschnitt 8.
IO-Scheduler (SSD/NVMe)
# Prüfen
cat /sys/block/sda/queue/scheduler
# Auf NVMe optimal
echo none | sudo tee /sys/block/nvme0n1/queue/scheduler
# Auf SSD optimal
echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler
Persistent via udev-Regeln oder GRUB-Parameter.
Filesystem für PG-Datadir
Empfehlung: ext4 oder xfs mit noatime,nodiratime gemountet. Vermeide nobarrier (Datensicherheit vor Perf).
Die Kernel-Seite ist hier bewusst nur ein Merkzettel. Warum die Werte wirken, was sie kosten und wie du den Effekt misst, steht ausführlich in unserem Beitrag zum Betriebssystem unter PostgreSQL.
11. Windows-spezifisch
huge_pages heißt auch auf Windows so
Was: Große Speicherseiten für shared_buffers. Windows nennt sie Large Pages — der Konfigurationsparameter heißt trotzdem auf allen Plattformen huge_pages. Einen Parameter large_pages gibt es in PostgreSQL nicht; wer ihn einträgt, bekommt beim Start unrecognized configuration parameter.
Werte: off, try (Default), on
Vorher: Security-Policy setzen:
secpol.mscöffnen- Local Policies → User Rights Assignment → Lock pages in memory
- Den PG-Service-User hinzufügen — das ist das Konto, unter dem der Dienst läuft, nicht das eigene Anmeldekonto
- Dienst neu starten
⚠ Das Recht wirkt über PostgreSQL hinaus: wer es hat, kann Speicher festnageln, den Windows anschließend nicht mehr auslagern kann. Genau deshalb ist es nicht ab Werk vergeben. Gib es dem Dienstkonto der Datenbank, nicht einem Sammelkonto, und auf einem Server, der noch anderes trägt, mit Blick auf den Gesamtspeicher.
Dann huge_pages = try in postgresql.conf.
⚠ try verschweigt den Fehlschlag. Fehlt das Recht, fällt PostgreSQL still auf normale Seiten zurück — der Dienst läuft, die Einstellung steht da, wirksam ist sie nicht. Mit huge_pages = on verweigert der Dienst stattdessen den Start. Das ist die ehrlichere Einstellung, sobald das Recht sitzt.
Kein nr_hugepages auf Windows
Windows hat keine nr_hugepages-Äquivalenz — die Large Pages werden on-demand über VirtualAlloc(MEM_LARGE_PAGES) angefordert. Wenn Windows nicht genug zusammenhängenden Speicher hat, schlägt's fehl. Vorab reservieren wie unter Linux lässt sich also nicht.
Was unter Windows sonst noch unter der Datenbank mitentscheidet — Dienstrechte, Virenschutz-Filtertreiber, Energieplan und die Unterschiede zu SQL Server — steht in unserem Beitrag zu Windows unter dem Datenbankserver.
update_process_title
Nachsehen, nicht setzen. PostgreSQL steht auf Windows bereits ab Werk auf off — genau mit der Begründung, die man sonst als Tuning-Tipp liest: das Aktualisieren des Prozesstitels ist dort teurer als auf anderen Plattformen. Auf allen übrigen Plattformen ist der Vorgabewert on. Es gibt hier also nichts zu tun; wer den Wert trotzdem explizit setzt, dokumentiert nur die Vorgabe.
Monitoring unter Windows
PostgreSQL bringt für Windows keine eigenen Leistungsindikatoren mit — anders als etwa SQL Server registriert es keine Perfmon-Objekte. Beobachtet wird deshalb über die Standard-Indikatoren des Betriebssystems (Prozessor, Arbeitsspeicher, Datenträger, Prozessanzahl) plus die Bordmittel der Datenbank: pg_stat_activity, pg_stat_statements und das Fehlerprotokoll.
⚠ In älteren Anleitungen steht an dieser Stelle gern lodctr /R. Der Befehl baut die Leistungsindikatoren der gesamten Maschine aus dem Sicherungsstand neu auf und kann dabei Registrierungen anderer Produkte beschädigen. Mit PostgreSQL hat er nichts zu tun — es gibt keine PostgreSQL-Counter, die er registrieren könnte.
Shared Memory Implementation
Auf Windows hast du keine Wahl — shared_memory_type ist fest windows, dynamic_shared_memory_type fest windows.
12. Empfohlene Anfangswerte
Kleiner Server (4 GB RAM, 2-4 Cores)
shared_buffers = 1GB
work_mem = 16MB
maintenance_work_mem = 128MB
effective_cache_size = 3GB
max_connections = 50
max_worker_processes = 4
max_parallel_workers = 4
max_parallel_workers_per_gather = 2
random_page_cost = 1.1 # SSD
effective_io_concurrency = 200
wal_compression = on
checkpoint_timeout = 15min
max_wal_size = 2GB
log_min_duration_statement = 1000
Mittlerer Server (32 GB RAM, 8 Cores, SSD)
shared_buffers = 8GB
work_mem = 32MB # bei max_connections = 100 → max 9-12 GB
maintenance_work_mem = 1GB
effective_cache_size = 20GB
huge_pages = try
max_connections = 100
max_worker_processes = 16
max_parallel_workers = 8
max_parallel_workers_per_gather = 4
max_parallel_maintenance_workers = 4
random_page_cost = 1.1
effective_io_concurrency = 200
maintenance_io_concurrency = 400
wal_compression = lz4
checkpoint_timeout = 30min
max_wal_size = 8GB
log_min_duration_statement = 1000
log_autovacuum_min_duration = 1000
log_temp_files = 1024
autovacuum_vacuum_scale_factor = 0.1
autovacuum_analyze_scale_factor = 0.05
Großer Server (128 GB RAM, 16+ Cores, NVMe)
shared_buffers = 16GB # oder 32GB wenn dedicated
work_mem = 64MB
maintenance_work_mem = 2GB
effective_cache_size = 96GB
huge_pages = on # Kernel: vm.nr_hugepages = 9100
max_connections = 200
max_worker_processes = 32
max_parallel_workers = 16
max_parallel_workers_per_gather = 4
max_parallel_maintenance_workers = 4
random_page_cost = 1.0
effective_io_concurrency = 300
maintenance_io_concurrency = 500
wal_compression = lz4
checkpoint_timeout = 30min
max_wal_size = 32GB
log_min_duration_statement = 500
log_autovacuum_min_duration = 1000
log_temp_files = 1024
autovacuum_max_workers = 6
autovacuum_vacuum_scale_factor = 0.05
autovacuum_analyze_scale_factor = 0.02
13. Diagnose-Queries
Non-Default-Settings anzeigen
SELECT name, setting, unit, short_desc
FROM pg_settings
WHERE source != 'default'
ORDER BY name;
Buffer-Hit-Rate (Ziel: > 99 %)
SELECT
round(100.0 * sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read), 0), 2) AS cache_hit_ratio
FROM pg_statio_user_tables;
Unter 95 % ist ein Indiz, kein Beweis — bei scan-lastiger Arbeit ist eine niedrige Rate normal, weil der Cache des Betriebssystems die Blöcke liefert. Entscheide über shared_buffers erst zusammen mit log_temp_files und pg_stat_statements.
Queries mit Disk-Spill (work_mem zu klein)
SELECT query, calls, total_exec_time, temp_blks_written
FROM pg_stat_statements
WHERE temp_blks_written > 0
ORDER BY temp_blks_written DESC
LIMIT 20;
(Braucht Extension pg_stat_statements.)
Lange laufende Queries jetzt
SELECT pid, state, wait_event_type, wait_event,
NOW() - query_start AS duration,
substring(query, 1, 100) AS q
FROM pg_stat_activity
WHERE state = 'active' AND backend_type = 'client backend'
ORDER BY query_start;
Tabellen die autovacuum brauchen
SELECT schemaname, relname, n_live_tup, n_dead_tup, last_vacuum, last_autovacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 10000
ORDER BY n_dead_tup DESC
LIMIT 20;
Welche Queries sind teuer? (pg_stat_statements)
SELECT query, calls, total_exec_time, mean_exec_time, rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
Index-Nutzung
SELECT schemaname, relname, indexrelname, idx_scan, idx_tup_read
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC
LIMIT 20;
Zeigt ungenutzte Indizes — sie kosten bei jedem Schreibvorgang, ohne beim Lesen zu helfen.
⚠ Nicht blind löschen. Zwei Fallen: Indizes, die einen PRIMARY KEY oder eine UNIQUE-Bedingung tragen, dürfen nicht weg — ein DROP INDEX nimmt dort die Bedingung mit. Filtere sie über indisunique/indisprimary aus pg_index heraus. Und idx_scan zählt erst seit dem letzten Zurücksetzen der Statistik: prüfe stats_reset in pg_stat_database, bevor du aus einer 0 auf „wird nie benutzt" schließt. Ein Index für den Quartalsabschluss steht im März zu Recht auf 0.
Checkpoint-Verhalten
SELECT num_timed, num_requested, write_time, sync_time, buffers_written
FROM pg_stat_checkpointer; -- bis PostgreSQL 16: pg_stat_bgwriter mit
-- checkpoints_timed / checkpoints_req / checkpoint_write_time
num_requested ≫ num_timed → max_wal_size zu klein.
Abschluss
Die 5 wichtigsten Settings die du fast immer anfassen musst:
shared_buffers(25% RAM dedicated, 10-15% shared)work_mem(32-64 MB)effective_cache_size(50-75% RAM)max_worker_processes(≥ CPU-Cores × 2)random_page_cost = 1.1(SSD-Anpassung)
Der Rest ist Feintuning — nur dann sinnvoll wenn du vorher gemessen hast (EXPLAIN ANALYZE, pg_stat_statements, pg_stat_bgwriter).
Goldene Regel: Defaults sind meistens OK. Jede Änderung mit Messung vorher/nachher validieren. Nicht blind aus Blogposts übernehmen.
Weiterführend
- PostgreSQL Tuning Guide
- pgtune.leopard.in.ua — Online-Konfigurator mit Ausgangswerten
pg_stat_statementsaktivieren für Query-Statistikpg_stat_activityfür Live-Analyse