PostgreSQL HugePages aktivieren — Leitfaden
Wie du PostgreSQL mit HugePages 5-15 % schneller machst: shared_buffers, vm.nr_hugepages, THP — komplett mit Rollback-Plan.
Status des Servers (Stand 2026-04-14): Linux mit 125 GB RAM,
Postgres 18 mitshared_buffers = 256 MB,effective_cache_size = 32 GB,
huge_pages = try. HugePages sind praktisch nicht konfiguriert
(HugePages_Total = 4→ 8 MB). Transparent HugePages (THP) aktiv mit
4,5 GB allokiert für anonyme Pages.
TL;DR
PostgreSQL profitiert deutlich von Explicit HugePages — typisch
5-15 % Speedup bei Queries, die viel shared memory berühren. Bei deiner
aktuellen Konfiguration (256 MB shared_buffers auf einem 125-GB-Server) ist
der Hebel sogar doppelt: **erst shared_buffers erhöhen, dann HugePages
aktivieren**. Ohne den ersten Schritt bringen HugePages kaum etwas.
1. Was sind HugePages?
Dein Linux-Kernel organisiert RAM in kleinen Einheiten — den sogenannten
Pages. Standard-Größe ist 4 KB. Jede einzelne Page wird über eine
Adress-Übersetzungstabelle (den "TLB" = Translation Lookaside Buffer) auf
physischen Speicher gemappt. Der TLB hat aber nur begrenzten Platz (typisch
64-1024 Einträge je CPU-Core).
HugePages sind größere Pages (2 MB oder 1 GB statt 4 KB). Der Effekt:
- Weniger TLB-Einträge für die gleiche Menge RAM
- Weniger TLB-Misses
- Schnellere Adress-Übersetzung bei großen Memory-Regionen
Postgres' shared_buffers ist exakt so eine **große, konstant genutzte
Memory-Region** — perfekter Kandidat für HugePages.
Grafik: 4 KB Pages vs. 2 MB HugePages
2. Warum PostgreSQL so stark profitiert
Postgres legt shared_buffers (den DB-internen Cache für Pages) in
shared memory ab — eine große, konstant genutzte Region. Jede SELECT,
INSERT oder UPDATE-Operation greift auf diese Region zu. Ohne HugePages
muss der Kernel pro Zugriff TLB-Einträge laden, was CPU-Zyklen kostet.
Messbarer Effekt in der Praxis:
| Szenario | Ohne HugePages | Mit HugePages |
|---|---|---|
| Random-OLTP (viele kleine Queries) | Baseline | +5 % |
| Analytische Joins (große CTEs) | Baseline | +10 bis 15 % |
| Bulk-Insert | Baseline | +3 bis 5 % |
Besonders CompositeOptimizationV2 profitiert: Die Queries scannen
Millionen Zeilen in recommendation_history und stock_prices — genau
das Szenario mit großen CTEs und vielen shared_buffer-Hits.
3. Aktueller Status prüfen
3.1 Kernel-Seite
grep -i huge /proc/meminfo
Deutung:
HugePages_Total→ Anzahl Explicit HugePages (konfigurierbar viavm.nr_hugepages)HugePages_Free→ davon freiHugePages_Rsvd→ reserviert (z. B. von Postgres)AnonHugePages→ Transparent HugePages (automatisch vom Kernel)Hugepagesize→ üblicherweise 2048 kB (2 MB)
Wenn HugePages_Total = 0 oder sehr klein: Postgres nutzt keine Explicit HugePages.
3.2 Postgres-Seite
SHOW huge_pages; -- 'try' | 'on' | 'off'
SHOW shared_buffers; -- typisch 25 % RAM bei dedizierten DB-Servern
SHOW effective_cache_size;
SHOW max_connections;
huge_pages = try (Default) bedeutet: **PG versucht HugePages zu nutzen,
fällt aber lautlos auf 4 KB Pages zurück wenn keine da sind**. Perfekter
Modus für "probieren schadet nicht" — aber man merkt dann nicht dass der
Effekt fehlt.
3.3 THP-Status
cat /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/defrag
Output typisch: always never. Die Klammern zeigen den aktiven
Modus. Für Datenbank-Server ist never empfohlen — siehe Abschnitt 8.
4. Entscheidung: Was einstellen?
Schritt A — shared_buffers erhöhen (erst das!)
Deine aktuellen 256 MB sind für einen 125-GB-Server deutlich zu niedrig.
Postgres-Faustregeln:
| Server-Rolle | shared_buffers |
|---|---|
| Dedizierter DB-Server | 25 % RAM |
| Server mit anderen großen Services (wie dein Setup mit mariadb, amavi, …) | 10-15 % RAM |
| Shared Hosting | 5-10 % RAM |
Bei deinem Setup (125 GB RAM, aber auch mariadb + Mail-Stack) empfehle ich
shared_buffers = 16 GB als vernünftigen Mittelweg:
- Genug für großzügigen Cache der
recommendation_history+stock_prices - Lässt 100+ GB für OS-Filesystem-Cache und andere Services
Wichtig: shared_buffers darf nicht mehr als dein verfügbares RAM sein
— und 125 GB sind komfortabel. Aber nie höher als effective_cache_size.
Schritt B — HugePages passend berechnen
Formel:
HugePages_nötig = (shared_buffers + Overhead) / 2 MB
Overhead berücksichtigt PG-Metadaten (WAL-Buffer, shared_preload_libraries,
etc.). Faustregel: +10 %.
Rechnung für shared_buffers = 16 GB:
16 GB / 2 MB = 8192 Pages
+ 10 % Puffer = 9011 → aufgerundet 9100
Für andere Werte:
shared_buffers |
vm.nr_hugepages (mit Puffer) |
|---|---|
| 4 GB | 2250 |
| 8 GB | 4500 |
| 16 GB | 9100 |
| 32 GB | 18000 |
Schritt C — huge_pages = on statt try
Empfehlung: nach erfolgreichem Testlauf von try auf on wechseln.
Vorteil: PG startet nicht mehr, wenn HugePages fehlen — du bemerkst
Konfigurations-Regressionen sofort statt stiller Degradation.
5. Durchführung (Schritt für Schritt)
⚠️ Wartungsfenster nötig — PG muss neu gestartet werden. Alle Queries
brechen ab, laufende Backtests/Batch-Jobs ebenfalls. Plane ein Zeitfenster
von 10-15 Min.
5.1 Vorbereitung
Baseline-Messung — vergleich später:
SELECT pid, query_start, state, substring(query, 1, 80) AS q
FROM pg_stat_activity
WHERE state = 'active' AND backend_type = 'client backend'
ORDER BY query_start;
Vorhandene Swap-/Memory-Auslastung notieren:
free -h
grep -i huge /proc/meminfo
5.2 Postgres-Konfiguration
In /etc/postgresql/18/main/postgresql.conf:
# Memory
# Vorher: shared_buffers = 256MB
shared_buffers = 16GB
# Vorher: huge_pages = try
huge_pages = on
# Optional: Effective-Planner-Settings anpassen
# effective_cache_size = 64GB (25-50 % RAM, wenn Server stark Postgres-zentriert)
# Parallel-Worker — auf 16 CPU-Cores massiv unterkonfiguriert per Default!
# Vorher: max_worker_processes = 8
max_worker_processes = 32
# Vorher: max_parallel_workers = 12
max_parallel_workers = 16
# max_parallel_workers_per_gather = 4 (bleibt)
Hintergrund zu den Parallel-Workern: max_worker_processes ist der harte
Cap fuer ALLE Background-Worker (parallel queries, autovacuum, logical
replication, etc.). PG-Default ist 8 — auf einem 16-Core-Server viel zu
niedrig. Wenn 8 parallele Sessions jeweils 4 parallel workers wollen,
braucht der Pool 32 Slots + ~3 fuer Autovacuum = 35. Ohne die Erhoehung
laufen V2-Queries effektiv seriell statt mit 4x Parallelitaet.
Gewinn-Schaetzung: Query-Zeit pro CIK faellt von ~1.5 min auf ~20-40 Sek
(Faktor 2-3 Speedup).
Noch nicht neustarten — erst Kernel vorbereiten.
5.3 Kernel-Seite: HugePages allokieren
# Aktuell allokieren (läuft sofort, nicht persistent)
sudo sysctl vm.nr_hugepages=9100
# Verifizieren
grep -i huge /proc/meminfo
# HugePages_Total: 9100
# HugePages_Free: 9100
# HugePages_Rsvd: 0
Wichtig: Wenn das System gerade stark Memory-Fragmentierung hat, kann
der Kernel weniger als die angeforderte Anzahl allokieren. Dann:
# RAM entfragmentieren via Drop-Caches
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches
sudo sysctl vm.nr_hugepages=9100 # erneut versuchen
Wenn immer noch nicht genug → System erstmalig neu starten (sauberer RAM)
und dann erneut allokieren.
5.4 Transparent HugePages (THP) deaktivieren
THP und Explicit HugePages können nebeneinander laufen, aber für
DB-Server wird THP abgeraten:
# Live deaktivieren
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag
Persistent via GRUB (Debian/Ubuntu):
sudo nano /etc/default/grub
# Ergänze transparent_hugepage=never in GRUB_CMDLINE_LINUX_DEFAULT:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash transparent_hugepage=never"
sudo update-grub
# Reboot erst beim nächsten Maintenance-Fenster
Alternativ via systemd-Unit (wirkt beim nächsten Reboot):
sudo tee /etc/systemd/system/disable-thp.service > /dev/null <<'EOF'
Description=Disable Transparent Huge Pages
After=sysinit.target local-fs.target
Type=oneshot
ExecStart=/bin/sh -c "echo never > /sys/kernel/mm/transparent_hugepage/enabled"
ExecStart=/bin/sh -c "echo never > /sys/kernel/mm/transparent_hugepage/defrag"
WantedBy=basic.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable disable-thp.service
sudo systemctl start disable-thp.service
5.5 Persistent machen (sysctl)
echo 'vm.nr_hugepages = 9100' | sudo tee -a /etc/sysctl.d/99-postgres-hugepages.conf
echo 'vm.swappiness = 10' | sudo tee -a /etc/sysctl.d/99-postgres-hugepages.conf
# Aktivieren ohne Reboot
sudo sysctl --system
5.6 Postgres neu starten
sudo systemctl restart postgresql@18-main
# Status prüfen
sudo systemctl status postgresql@18-main
sudo tail -50 /var/log/postgresql/postgresql-18-main.log
Achte im Log auf Zeilen wie:
LOG: database system is ready to accept connections
Bei Fehlern → siehe Abschnitt 9 (Troubleshooting).
6. Verifikation
6.1 HugePages sind reserviert
grep -i huge /proc/meminfo
Erwartung:
HugePages_Total: 9100
HugePages_Free: 900 ← <<< 9100, d.h. PG hat sie reserviert
HugePages_Rsvd: 8200 ← PG's Reservierung
AnonHugePages: 150000 kB ← THP (sollte niedrig sein nach Deaktivierung)
Wenn HugePages_Rsvd deutlich > 0 → Postgres nutzt HugePages erfolgreich.
6.2 Postgres-Log
sudo grep -i huge /var/log/postgresql/postgresql-18-main.log | tail -5
Erwartung: Keine Warnung à la "could not map anonymous shared memory:
Cannot allocate memory". Wenn PG mit huge_pages = on nicht starten kann,
steht das hier.
6.3 Performance-Smoketest
Gleiche Query vor/nach messen (z. B. aus deinem V2-Backtest-Kontext):
EXPLAIN (ANALYZE, BUFFERS)
SELECT COUNT(*) FROM market.recommendation_history
WHERE calculated_date >= CURRENT_DATE - 30;
Interessant: shared hit=xxx bleibt hoch (gut!), Execution Time sollte
spürbar niedriger sein.
7. Rollback
Wenn etwas schiefgeht, zurück zum Ursprungszustand:
# 1. postgresql.conf zurück auf huge_pages = try, shared_buffers = 256MB
sudo nano /etc/postgresql/18/main/postgresql.conf
# 2. Kernel-HugePages freigeben
sudo sysctl vm.nr_hugepages=0
# 3. sysctl-Custom-File entfernen
sudo rm /etc/sysctl.d/99-postgres-hugepages.conf
sudo sysctl --system
# 4. THP wieder aktivieren (falls gewünscht)
echo always | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
# 5. Postgres neu starten
sudo systemctl restart postgresql@18-main
8. Transparent HugePages (THP) — warum ausschalten?
THP ist nicht das Gleiche wie Explicit HugePages:
| Feature | Explicit HugePages | Transparent HugePages (THP) |
|---|---|---|
| Allokation | Vom Admin fest reserviert | Automatisch vom Kernel |
| Für shared memory (PG) | ✅ Ja | ❌ Nein (nur anonyme Pages) |
| Vorhersehbar | ✅ Ja | ❌ Kann plötzlich zurückfallen |
| Stall-Risiko | Keine | Gelegentliche mehrhundert-ms-Stalls beim Defrag |
| Empfehlung für DB | ✅ Aktivieren | ⚠️ Deaktivieren |
Der "Stall"-Effekt von THP: Wenn der Kernel beschließt, anonyme Pages in
2-MB-Blöcke zu konsolidieren (khugepaged-Daemon), kann das kurz zu
CPU-Spitzen und Latenz-Peaks führen. Für eine OLTP-DB mit garantierter
Response-Zeit ist das schlecht.
Dass khugepaged läuft erkennst du an CPU-Spitzen des gleichnamigen
Prozesses in top. Nach Deaktivierung verschwindet er.
9. Troubleshooting
9.1 Postgres startet nicht mit huge_pages = on
Fehler: FATAL: could not map anonymous shared memory: Cannot allocate memory
Ursache: Zu wenig HugePages allokiert.
Fix:
grep -i hugepages_total /proc/meminfo
# Wert mit shared_buffers / 2048 kB vergleichen
# Bei zu wenig: vm.nr_hugepages erhöhen oder huge_pages = try zurück
9.2 HugePages_Total lässt sich nicht setzen
Symptom: sudo sysctl vm.nr_hugepages=9100 → nur 4000 werden angezeigt
Ursache: Memory-Fragmentierung. Kernel findet keine zusammenhängenden
2-MB-Blöcke.
Fix (in dieser Reihenfolge):
# 1. Caches dropen
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches
# 2. Erneut probieren
sudo sysctl vm.nr_hugepages=9100
# 3. Wenn immer noch nicht genug: Neustart einplanen
# (HugePages am besten früh im Bootup allokieren → sysctl.d reicht aus)
9.3 Speicher-Druck durch zu viele HugePages
Symptom: Andere Services (mariadb etc.) bekommen plötzlich OOM-Kills.
Ursache: vm.nr_hugepages × 2 MB ist fest reserviert und **nicht
austauschbar**. Wenn das zu viel des RAMs bindet, bleibt für Nicht-PG-Prozesse
zu wenig.
Fix: HugePages-Zahl reduzieren (z. B. von 9100 auf 5000) → entsprechend
shared_buffers kleiner.
9.4 huge_pages = on erzwingt, aber Performance ist schlechter
Unwahrscheinlich, aber möglich bei ungeeignetem Workload (sehr kleine
shared_buffers, dominant OS-Cache-Zugriffe). Dann zurück auf try und
vm.nr_hugepages = 0. HugePages ist kein Silver Bullet — bei richtig
großen shared_buffers (≥ 4 GB) zahlen sie sich aber fast immer aus.
10. Zusammenfassung & Empfehlung für diesen Server
Wartungsfenster (15 Min) — alle Aenderungen zusammen:
shared_buffersvon 256 MB → 16 GBhuge_pagesvontry→ onmax_worker_processesvon 8 → 32 (wichtiger Parallel-Hebel bei 16 Cores)max_parallel_workersvon 12 → 16vm.nr_hugepages = 9100- THP →
never vm.swappiness = 10- Postgres restart
Erwarteter Effekt beim naechsten V2-Backtest-Lauf:
| Hebel | Gewinn |
|---|---|
| HugePages | TLB-Misses reduziert → ~5-10% weniger CPU-Overhead |
shared_buffers 64× größer |
Buffer-Hit-Rate steigt dramatisch, weniger Disk-Reads |
max_worker_processes 32 |
Parallel-Queries bekommen ihre 4× Parallelitaet voll → Faktor 2-3 speedup |
Kombinierter Speedup: Faktor 3-5.
Bei heutiger V2-Laufzeit von 40-50h (CPU-bound, parallel workers verhungern):
kuenftig 10-15 h realistisch. Bei aktiv laufendem V2 mit Resume-Feature
kann man das Maintenance-Fenster sogar mittendrin ziehen — die Queue
behaelt 'done'-Eintraege, 'processing' wird per Stale-Releaser wieder auf
'pending' gesetzt.
Referenzen
- PostgreSQL Docs: Huge Pages
- Linux Kernel: HugeTLB Pages
- Red Hat Performance Tuning: THP Disable