Linux-Kernel-Tuning & NVMe-Speicheroptimierung: High-Throughput Datenbank-Setups
Erstellt: 08.11.2026 · Zuletzt aktualisiert: 08.11.2026 · Nejat Hakan
Management Summary: Eine relationale Datenbank mit Standard-Kernel-Parametern der Linux-Distribution zu betreiben, garantiert Durchsatz-Engpässe und unvorhersehbare Latenzspitzen. Standardeinstellungen sind für Desktop-Systeme oder klassische Webserver optimiert – nicht für hochgradig nebenläufige Datenbankkerne. Unangepasste Systeme lagern Datenbank-RAM aggressiv in die Swap-Partition aus, frieren bei massiven Dirty-Page-Schreibvorgängen für Sekunden ein, erzeugen massiven CPU-TLB-Miss-Overhead bei 4-KiB-Speicherseiten und unterstellen dem Speichersystem fälschlicherweise die Mechanik alter rotierender Festplatten. Dieser Leitfaden beschreibt das Tuning des virtuellen Speichers, die Allokation von 2 MiB Huge Pages, die Konfiguration von NVMe-Multi-Queue-Schedulern und die Kalibrierung der Kostenkonstanten des Datenbank-Optimizers.
-
Virtueller Speicher & Swappiness
Auslagerung in den Swap-Speicher verhindern:
vm.swappiness = 1setzen undvm.dirty_background_ratio = 5etablieren, um I/O-Freezes zu eliminieren. -
2 MiB Huge Pages
CPU-TLB-Cache-Misses bei großen Pufferpools drastisch senken: Unberechenbare Transparent Huge Pages (THP) deaktivieren und statische HugePages zuweisen.
-
NVMe Multi-Queue Scheduler
Veraltete Single-Queue-Elevator-Scheduler umgehen: PCIe-NVMe-Laufwerke auf
noneodermq-deadlineschalten, um über 500.000 parallele IOPS freizugeben. -
Planner-Kostenkalibrierung
Veraltete HDD-Annahmen aus den 1990er Jahren korrigieren:
random_page_cost = 1.1undeffective_io_concurrency = 200für modernen Flash-Speicher setzen.
1. Virtuelle Speicherarchitektur & Datenbank-I/O
Schreibt eine Datenbank Daten auf den Datenträger, puffert der Linux-Kernel geänderte Seiten im RAM, bevor sie auf die NVMe synchronisiert werden. Das Standardverhalten des Kernels führt dabei häufig zu massiven Problemen:
%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
DB["PostgreSQL / MariaDB<br/>shared_buffers Flush"]
PAGES["2 MiB Huge Pages im RAM<br/>(Keine TLB-Verdrängung)"]
FLUSH{"Kernel Dirty Check<br/>dirty_background: 5%"}
BG_SYNC["Asynchroner Flusher<br/>Gleichmäßiger Stream"]
NVME["PCIe NVMe-Controller<br/>(Scheduler: none)"]
DB --> PAGES
PAGES --> FLUSH
FLUSH --> BG_SYNC
BG_SYNC --> NVME
Beseitigung der „Dirty-Page-Flush-Blockade“
Auf einem Server mit 128 GB RAM erlaubt die Linux-Standardeinstellung vm.dirty_ratio = 20, dass sich bis zu 25,6 GB ungeschriebene Dirty Pages im Speicher ansammeln, bevor Prozesse gedrosselt werden. Wird die Grenze erreicht, stoppt der Kernel alle schreibenden Prozesse und flutet den Speicherbus mit einem gigantischen synchronen Schreibstoß, der Latenzspitzen von 5 bis 30 Sekunden verursacht.
Die Lösung: Den Kernel anweisen, die Hintergrund-Flusher kontinuierlich bei sehr niedrigen Schwellenwerten aufzuwecken:
- vm.dirty_background_ratio = 5: Hintergrund-Threads beginnen bereits ab 5 % schmutzigen Daten im RAM mit dem Schreiben.
- vm.dirty_ratio = 10: Schreibende Anwendungen werden erst gedrosselt, wenn 10 % erreicht werden.
2. Kernel sysctl.conf Produktiv-Template
Hinterlege diese optimierten Parameter auf jedem dedizierten Debian 12 Datenbankserver:
# Datei: /etc/sysctl.d/99-database-kernel-tuning.conf
# ----------------------------------------------------------------------
# 1. Swapping- & Overcommit-Steuerung
# ----------------------------------------------------------------------
# Aggressives Auslagern von shared_buffers in den Swap-Space verhindern
vm.swappiness = 1
# Verhindern, dass der OOM-Killer bei Lastspitzen unüberlegt Prozesse beendet
vm.overcommit_memory = 2
vm.overcommit_ratio = 80
# ----------------------------------------------------------------------
# 2. Gleichmäßiger Dirty-Page-Hintergrund-Flush (Verhindert I/O-Freezes)
# ----------------------------------------------------------------------
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
# ----------------------------------------------------------------------
# 3. Inter-Process Communication (IPC) & Shared Memory Limits
# ----------------------------------------------------------------------
# Maximale Größe eines einzelnen Shared-Memory-Segments (bis zu 90 % des RAMs)
kernel.shmmax = 68719476736
# Gesamtzahl der Shared-Memory-Seiten im System (berechnet für 64GB+)
kernel.shmall = 16777216
# ----------------------------------------------------------------------
# 4. Netzwerk-Socket-Puffer für hohe parallele Verbindungszahlen
# ----------------------------------------------------------------------
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
Die Einstellungen ohne Neustart sofort aktivieren:
3. 2 MiB Huge Pages: TLB-Cache-Misses eliminieren
Standardmäßig verwaltet der x86-64-Linux-Kernel den Speicher in 4 KiB-Seiten. Allokiert PostgreSQL einen Pufferpool von shared_buffers = 32GB, muss der Translation Lookaside Buffer (TLB) des Prozessors folgende Menge verwalten:
(32 × 1024 × 1024 KiB) / 4 KiB = 8.388.608 Page-Table-Einträge
Über 8,3 Millionen Seiteneinträge führen zu permanenten Cache-Line-Verdrängungen im Prozessor. Durch den Wechsel auf 2 MiB Huge Pages schrumpft die Gesamtanzahl auf 16.384 Einträge (eine Reduktion um den Faktor 512), die vollständig in den L2/L3-Caches der CPU Platz finden.
Schritt 1: Benötigte Huge Pages berechnen
Für einen Pufferpool von 16 GB shared_buffers:
HugePages = (16 × 1024 MB) / 2 MB = 8192 Seiten
Mit einem Sicherheitspuffer von ca. 8 % konfigurieren (8.800 Seiten):
Schritt 2: Transparent Huge Pages (THP) deaktivieren
Transparent Huge Pages (THP) versuchen, 2-MiB-Seiten dynamisch zur Laufzeit zu erzeugen. Dies führt bei Datenbanken jedoch zu unberechenbaren Latenzspitzen durch dynamische Speicherdefragmentierung. Deaktiviere THP auf Datenbank-Servern immer:
# Aktuellen THP-Status prüfen:
cat /sys/kernel/mm/transparent_hugepage/enabled
# Steht dort [always], dauerhaft via systemd oder GRUB abschalten:
# grub: transparent_hugepage=never
Schritt 3: In postgresql.conf aktivieren
# Datei: /etc/postgresql/17/main/postgresql.conf
huge_pages = on # Bricht den Start ab, falls HugePages fehlen (verhindert stillen 4-KiB-Fallback)
4. NVMe Multi-Queue I/O Scheduler & Dateisystem
Moderne Enterprise-NVMe-SSDs nutzen den PCIe-Bus mit bis zu 64.000 parallelen Hardware-Queues. Ältere Linux-Elevator-Scheduler (wie cfq oder Single-Queue deadline) serialisieren Zugriffe künstlich.
# Aktuellen Scheduler für nvme0n1 prüfen:
cat /sys/block/nvme0n1/queue/scheduler
# Ausgabe: [none] mq-deadline
Für NVMe-Datenträger sollte der Scheduler auf none (reiner Hardware-Pass-Through) oder mq-deadline stehen:
# Datei: /etc/udev/rules.d/60-nvme-scheduler.rules
# 'none'-Scheduler für alle schnellen PCIe-NVMe-Laufwerke erzwingen
ACTION=="add|change", KERNEL=="nvme[0-9]*n[0-9]*", ATTR{queue/scheduler}="none"
Dateisystem-Mount-Optionen (/etc/fstab)
Deaktiviere für Datenbank-Verzeichnisse (/var/lib/postgresql oder /var/lib/mysql) das ständige Schreiben von Zugriffszeitstempeln:
# /etc/fstab
UUID=xxxx-xxxx-xxxx /var/lib/postgresql ext4 noatime,nodiratime,commit=60,errors=remount-ro 0 2
noatime,nodiratime: Eliminiert unnötige Schreib-I/O bei reinen Leseabfragen (SELECT).
- commit=60: Schreibt ext4-Metadaten nur alle 60 Sekunden statt alle 5 Sekunden (die Datenbank garantiert die Transaktionssicherheit bereits eigenständig über ihr WAL).
5. PostgreSQL-Planer-Kalibrierung für NVMe-Speicher
In der Standardkonfiguration geht PostgreSQL noch von alten mechanischen Festplatten aus, bei denen zufällige Zugriffe viermal teurer sind als sequenzielle Scans. Auf NVMe-SSDs erfolgen zufällige Zugriffe jedoch mit PCIe-Bus-Geschwindigkeit ohne jede mechanische Verzögerung.
Passe folgende Parameter in der postgresql.conf an:
# Datei: /etc/postgresql/17/main/postgresql.conf
# 1. Zufällige Zugriffe sind auf NVMe praktisch genauso schnell wie sequenzielle (Standard war 4.0!)
random_page_cost = 1.1
# 2. Kernel erlauben, bis zu 200 physische Seiten parallel über NVMe-Queues vorzuladen
effective_io_concurrency = 200
# 3. Dem Optimizer mitteilen, wie viel RAM für Caching bereitsteht (ca. 75 % des Gesamt-RAMs)
effective_cache_size = 48GB
# 4. Parallele Worker für analytische Scans konfigurieren
max_worker_processes = 8
max_parallel_workers_per_gather = 4
max_parallel_maintenance_workers = 4
PostgreSQL neu laden, um die Änderungen zu aktivieren: