Fernwartung Download starten

ZFS Performance-Tuning: Recordsize, atime, Komprimierung

ZFSTrueNASStoragePerformance
ZFS Performance-Tuning: Recordsize, atime, Komprimierung

ZFS ist out-of-the-box ein guter Generalist. Das heißt: für jeden Workload ordentlich, für keinen optimal. Wer einen Pool für eine konkrete Anwendung baut — VM-Storage, Backup-Target, File-Server, Datenbank — kann mit ein paar wenigen Dataset-Properties spürbar mehr Performance, weniger Speicherverbrauch und längere SSD-Lebensdauer erreichen.

Dieser Artikel zeigt die wichtigsten Stellschrauben in der richtigen Reihenfolge. Keine erfundenen Benchmarks — wo es Tradeoffs gibt, beschreiben wir sie qualitativ. Eigene Tests im Lab oder in einem Staging-Pool sind durch nichts zu ersetzen.

Was beim Pool-Setup nicht mehr änderbar ist

Bevor wir zu tunbaren Properties kommen, eine Erinnerung an die unwiderruflichen Entscheidungen beim Pool-Create:

  • ashift — sector-size-alignment, gesetzt beim VDEV-Create. Empfehlung: ashift=12 (4K-Sectors) für moderne SSDs und Enterprise-HDDs. Bei NVMe und manchen Enterprise-SSDs ist ashift=13 (8K) sinnvoll. Falscher ashift = Write-Amplification, nicht nachträglich änderbar.
  • VDEV-Topologie — Mirror, RAIDZ1/2/3, dRAID. Eine RAIDZ-Konfiguration lässt sich nicht in einen Mirror umwandeln. RAIDZ-Expansion (ab OpenZFS 2.3) erweitert nur, ändert nicht die Topologie. Siehe ZFS RAID-Level erklärt.
  • Encryption-Properties auf Pool-Ebene — nachträglich nicht änderbar.

Wer hier falsch dimensioniert, kann später nichts mehr durch Properties retten. Der Rest dieses Artikels behandelt das, was sich online und ohne Datenverlust ändern lässt.

recordsize — die wichtigste Stellschraube

recordsize ist die maximale logische Blockgröße, mit der ZFS Daten in ein Dataset schreibt. Default: 128K. Das ist für File-Server mit gemischten Dateigrößen okay, für VMs oder Datenbanken aber meist suboptimal.

Faustregel: recordsize sollte zur typischen IO-Größe des Workloads passen.

WorkloadEmpfehlung recordsizeBegründung
File-Server (gemischte Dateien)128K (default)Generalist, gute Balance
Große Mediendateien (Video, Backups, Images)1MWeniger Metadaten-Overhead, höhere Streaming-Performance
VM-Images (Proxmox, qcow2, raw)64K oder 128KAnwendungs-IO-Größe meist 4–16K, aber Multi-Block-Operationen profitieren
Datenbanken (PostgreSQL, MySQL InnoDB)16KDatenbank-Page-Size matchen — typisch 8K (Postgres) oder 16K (InnoDB)
Microsoft SQL Server (Datenfiles)64KSQL Server schreibt in 64K-Extents
Backup-Targets (Veeam, PBS-Chunks, Restic)1MLange sequenzielle Writes, dedup-orientierte Speicherung

Wichtig: recordsize gilt ab dem Setzen für neu geschriebene Blöcke. Bestehende Daten behalten ihre alte Blockgröße bis zum nächsten Rewrite. Wer ein Dataset umstellt, sollte die Daten neu kopieren (zfs send | zfs receive in ein neues Dataset) oder einfach abwarten, bis sich die Dataset-Inhalte durch normale Schreibvorgänge erneuert haben.

# recordsize für ein VM-Dataset
zfs set recordsize=64K tank/vms

# recordsize für ein Datenbank-Dataset (Postgres)
zfs set recordsize=16K tank/postgres

# recordsize für ein Backup-Target
zfs set recordsize=1M tank/backups

atime=off — leiser Killer entfernt

atime (Access Time) ist die Property, die bei jedem Read den Lesezeitstempel der Datei aktualisiert — also einen Write pro Read. Das war in 1990er-Unix-Workloads sinnvoll. Heute ist es fast immer schädlicher Overhead:

  • Für jeden Read fällt ein Metadaten-Write an
  • SSDs werden unnötig belastet (Write-Amplification)
  • File-Server, Backup-Targets, VM-Storage profitieren nicht von atime
zfs set atime=off tank

Auf Pool-Root setzen, vererbt sich auf alle Datasets. Ausnahme: Wenn Anwendungen wirklich auf atime angewiesen sind (Mail-Server in einigen Konfigurationen, manche Quota-Tools). In dem Fall selektiv auf atime=off auf den Datasets verzichten, die diese Anwendungen halten.

Modernere Alternative: relatime=on aktualisiert atime nur, wenn die letzte Update-Zeit älter als 24h oder vor der mtime/ctime liegt. Reduziert die meisten unnötigen Writes, ohne atime völlig zu verlieren.

# Wenn atime wirklich gebraucht wird, aber Schonung gewünscht
zfs set atime=on tank/mailserver
zfs set relatime=on tank/mailserver

Komprimierung — lz4, zstd-3, zstd-19

Komprimierung ist in fast allen Szenarien ein klarer Gewinn — weniger Platzverbrauch und oft sogar schnellere IO, weil weniger Daten von/zur Disk fließen müssen. Die Frage ist nur: welcher Algorithmus.

AlgorithmusCPU-LastKomprimierungsrate (typisch)LatencyAnwendungsfall
lz4Sehr niedrig1,2:1 – 2,0:1Sehr niedrigDefault für VMs, Datenbanken, alles latency-sensitiv
zstd-3Niedrig–mittel1,5:1 – 2,5:1NiedrigGeneralist mit besserer Rate als lz4, kaum CPU-Mehrkosten auf modernen EPYC/Xeon
zstd-9Mittel–hoch1,8:1 – 3,0:1Spürbar höherFile-Server mit überwiegend Read-Workload
zstd-19Sehr hoch2,0:1 – 4,0:1HochArchiv- und Cold-Storage, wenn Schreiben selten und CPU verfügbar
gzip-6Hochähnlich zstd-9HochLegacy — heute selten Vorteil gegenüber zstd
offKeine1,0:1NiedrigsteNur bei bereits komprimierten Daten (z.B. Veeam-Repository mit eigener Komprimierung)

Tradeoffs in der Praxis:

  • VMs und Datenbanken: zstd-3 ist oft die bessere Wahl als lz4 — kaum messbare Latency-Erhöhung auf modernen CPUs, deutlich bessere Komprimierung.
  • All-Flash-Pools mit hoher IO-Last: lz4 bleibt sicher; zstd nur nach Lab-Test.
  • Backup-Targets: Wenn die Daten bereits dedupliziert + komprimiert ankommen (PBS, Veeam Hardened Repo): compression=off oder maximal lz4. Mehr bringt nichts und kostet CPU.
  • Cold-Storage / Archive: zstd-19 lohnt sich. Geschrieben wird selten, gelesen wird selten, jeder Prozent gesparter Platz zählt.
# Default für meiste Datasets
zfs set compression=zstd-3 tank

# All-Flash-VM-Pool mit Latency-Priorität
zfs set compression=lz4 tank/vms

# Archive
zfs set compression=zstd-19 tank/archive

# Backup-Target mit eingehend bereits komprimierten Daten
zfs set compression=off tank/veeam

Wer den Effekt prüfen will: zfs get compressratio dataset zeigt die tatsächlich erreichte Rate auf vorhandenen Daten.

Mehr dazu im Detail unter ZFS-Komprimierung in der Praxis.

special_small_blocks — Metadaten und kleine Files auf NVMe

Wenn der Pool ein Special VDEV (separates Mirror-VDEV aus NVMe) hat, kann special_small_blocks dafür sorgen, dass nicht nur Metadaten, sondern auch kleine Datenblöcke dort landen. Das beschleunigt File-Server-Workloads mit vielen kleinen Files dramatisch — die Hot-Files liegen auf NVMe, nur große Files auf den langsameren HDDs.

# Special-VDEV muss bereits angehängt sein (zpool add tank special mirror ...)
zfs set special_small_blocks=64K tank/fileserver

Werte zwischen 32K und 128K sind in der Praxis sinnvoll. Wichtig: Das Special-VDEV muss redundant ausgelegt sein (mindestens Mirror) — fällt es aus, ist der gesamte Pool tot. Mehr im Proxmox-ZFS-Tuning-Artikel.

primarycache / secondarycache

Diese beiden Properties steuern, was im ARC (RAM) und L2ARC (NVMe-Cache) gehalten wird:

WertBedeutung
all (default)Daten + Metadaten cachen
metadataNur Metadaten cachen
noneNichts cachen

Sinnvolle Anwendungen:

  • Sequenzielle Backup-Reads: primarycache=metadata — Backup-Daten werden einmal gelesen, kein Wert im ARC. RAM bleibt für andere Datasets frei.
  • Datenbanken mit eigenem Cache (z.B. PostgreSQL shared_buffers): primarycache=metadata — die DB cached selbst effektiver, ARC nur für Metadaten.
  • Hot-Read-Workloads (File-Server-Profile): primarycache=all belassen.
# Backup-Dataset: ARC nur für Metadaten
zfs set primarycache=metadata tank/backups
zfs set secondarycache=metadata tank/backups

sync — vorsichtig nutzen

sync steuert, wie ZFS mit synchronen Schreibvorgängen umgeht:

  • standard (default): Schreibvorgang wartet auf SLOG/ZIL-Bestätigung — sicher, konsistent
  • always: Erzwingt sync auch für asynchrone Writes — sehr sicher, sehr langsam
  • disabled: Ignoriert sync-Anforderungen — schnellster Write, aber Daten der letzten Sekunden gehen bei Stromausfall verloren

sync=disabled ist eine Falle. Es ist verlockend für Benchmark-Werte, aber riskant in produktiven Setups:

  • Sicher für: temporäre Scratch-Datasets, Build-Artefakte, Caches, die jederzeit neu erzeugt werden können
  • Riskant für: Datenbanken, VM-Images, alles, was Konsistenz braucht
# Nur für nachweislich unkritische Scratch-Daten
zfs set sync=disabled tank/scratch

Wer sync-Performance braucht, sollte stattdessen ein dediziertes SLOG-Device (Intel Optane, hochwertige NVMe mit Power-Loss-Protection) hinzufügen — das beschleunigt sync ohne Konsistenzverlust.

logbias — throughput vs. latency

logbias=throughput ändert das Verhalten des SLOG: sync-Writes gehen direkt in den Pool statt über das SLOG. Macht für datenbankartige Workloads mit sehr großen sync-Writes Sinn, wo das SLOG sonst zum Engpass würde.

zfs set logbias=throughput tank/large-db

Default logbias=latency ist für die meisten Anwendungen richtig.

redundant_metadata

redundant_metadata=most reduziert die Anzahl der Metadaten-Kopien von 2 auf 1 für die meisten Blöcke (kritische Metadaten bleiben doppelt). Spart Platz, reduziert aber die Wiederherstellbarkeit bei korrupten Metadaten-Blöcken. Selten zu empfehlen, außer bei wirklich knappem Speicher in sicheren Umgebungen.

Einmal-Setup-Checkliste für neue Pools

Ein pragmatischer Default-Setup-Block für einen frischen Pool:

# Pool-weit
zfs set atime=off tank
zfs set compression=zstd-3 tank
zfs set xattr=sa tank          # Erweiterte Attribute inline, weniger Overhead
zfs set acltype=posixacl tank  # Falls POSIX-ACLs gebraucht werden

# VM-Dataset
zfs create -o recordsize=64K -o compression=lz4 tank/vms

# Datenbank-Dataset
zfs create -o recordsize=16K -o compression=lz4 -o logbias=latency tank/postgres

# Backup-Target
zfs create -o recordsize=1M -o compression=off -o primarycache=metadata tank/backups

# Generelles File-Dataset
zfs create -o recordsize=128K tank/files

Das sind keine universellen Wahrheiten, sondern erfahrungsbasierte Startwerte — bei produktivem Einsatz mit echten Workloads validieren und nachjustieren.

Was die Tuning-Wirkung nicht ersetzt

Properties tunen, was vorhanden ist. Sie reparieren nicht:

  • Zu wenig RAM für ARC. Faustregel: 1 GB RAM pro 1 TB Pool, mehr bei dedup oder vielen kleinen Files. Wer hier knausert, sieht das in jedem Workload.
  • Falsche VDEV-Topologie. Ein einzelnes RAIDZ2 aus 12 großen HDDs für VM-Workload wird auch mit perfektem Tuning enttäuschen — VMs brauchen Mirror-VDEVs oder All-Flash.
  • Fragmentierten Pool > 80% Belegung. ZFS verlangsamt sich ab ca. 80% Fülle deutlich. Mehr Platz lassen oder vergrößern.
  • Falscher Storage-Stack. Spinning-HDDs für High-IOPS-Workloads bleiben auch mit perfektem Tuning langsam.

DATAZONE-Empfehlung

Tuning ist kein Selbstzweck — es ist die letzte Schicht über sauber dimensionierter Hardware und richtiger Pool-Topologie. Reihenfolge:

  1. Hardware passend zur Anwendung (TrueNAS-Modellwahl oder Proxmox-Architektur-Planung)
  2. VDEV-Topologie richtig beim Pool-Create (ZFS RAID-Level)
  3. Default-Properties (atime=off, compression=zstd-3, xattr=sa)
  4. Workload-spezifische Datasets (recordsize, compression, sync, primarycache pro Dataset)
  5. Messen. zpool iostat, arcstat, Anwendungs-Metriken (siehe Grafana-Stack, TrueNAS-SMART-Monitoring)

Wir helfen bei der Pool-Auslegung und beim Tuning bestehender Setups. Erfahrungsgemäß lassen sich bei ungetunten KMU-Pools 20–50 % Performance ohne neue Hardware herausholen — wenn die Dimensionierung grundsätzlich stimmt.

Quellen und weiterführende Artikel

Mehr zu diesen Themen:

IT-Beratung gewünscht?

Kontaktieren Sie uns für eine unverbindliche Beratung zu Proxmox, OPNsense, TrueNAS und mehr.

Jetzt Kontakt aufnehmen