ZFS · 7 min · 2026-04-13

Ist deine ZFS Recordsize falsch? Ein Performance Tuning Guide

ZFS recordsize ist ein Maximum, keine feste Blockgröße. Lerne wie du sie pro Workload korrekt setzt, Write Amplification vermeidest und warum du sie VOR dem Schreiben setzen musst.

TL;DR

ZFS recordsize ist ein Maximum, keine feste Blockgröße. Kleine Dateien verschwenden keinen Platz. Aber die Default-Einstellung 128K verursacht massive Write Amplification bei Datenbanken und VMs. Setze recordsize pro Dataset bevor du Daten schreibst — die Änderung wirkt nicht rückwirkend.

Die meisten ZFS-Admins erstellen ihren Pool, legen Datasets an und fassen recordsize nie an. Der Default ist 128K. Es funktioniert. Nichts bricht. Warum sich also kümmern?

Weil “funktioniert” und “performt gut” zwei sehr verschiedene Dinge sind. Eine PostgreSQL-Datenbank auf einem 128K-recordsize Dataset kann bei jedem zufälligen Update eine 16-fache Write Amplification erleiden. Ein Medien-Archiv auf dem gleichen 128K verschwendet I/O-Bandbreite, weil es viermal mehr Daten pro sequenziellem Lesevorgang liest als nötig. Beides sind stille Performance-Killer. Keine Fehler, keine Warnungen — nur langsam.

Was recordsize tatsächlich macht

Das erste Missverständnis zum Aufräumen: recordsize ist ein Maximum, keine feste Allokationseinheit.

Wenn ZFS eine Datei schreibt, teilt es sie in Records (Blöcke) auf. Jeder Record kann bis zu recordsize Bytes groß sein. Aber wenn eine Datei kleiner als die recordsize ist, ist der Record nur so groß wie die Datei. Eine 4K-Datei auf einem Dataset mit recordsize=1M belegt einen 4K-Record auf der Disk, nicht 1M.

Dataset: recordsize=1M

Datei: config.yaml (2K)    → 1 Record, 2K auf Disk
Datei: foto.jpg (3.2M)     → 4 Records, 3.2M auf Disk
Datei: database.db (800K)  → 1 Record, 800K auf Disk
Datei: video.mp4 (2.1GB)   → ~2.150 Records, 2.1GB auf Disk

Du verschwendest also keinen Speicherplatz durch eine große recordsize. Das Platzverschwendungs-Argument kommt aus einer ganz anderen Richtung: Performance bei zufälligem I/O.

Warum 128K für die meisten Workloads falsch ist

Der Default 128K ist ein Kompromiss. Er funktioniert akzeptabel für allgemeines File-Serving mit einer Mischung aus kleinen und großen Dateien bei überwiegend sequenziellem Zugriff. Aber sobald dein Workload ein dominantes Muster hat, ist 128K fast sicher nicht optimal.

Das Problem ist Write Amplification bei zufälligem I/O.

Wenn eine Anwendung 8K Daten in die Mitte einer Datei schreibt, muss ZFS auf einen ganzen Record operieren. Bei recordsize=128K bedeutet das:

  1. Den vollen 128K-Record von der Disk lesen
  2. Die 8K ändern
  3. Den gesamten 128K-Record zurückschreiben (ZFS ist Copy-on-Write, schreibt an eine neue Stelle)

Aus einem 8K-Anwendungsschreibvorgang wurde ein 128K-Read + 128K-Write. Das ist 16-fache Write Amplification. Bei einer geschäftigen Datenbank mit tausenden zufälligen 8K-Schreibvorgängen pro Sekunde ist das verheerend.

Write Amplification ist unsichtbar

ZFS loggt Write Amplification nicht. Deine Anwendung meldet keine Fehler. Du siehst nur hohe Disk-Auslastung, unerklärliche Latenz und Throughput weit unter dem, was deine Hardware liefern sollte.

Empfohlene recordsize pro Workload

WorkloadrecordsizeWarum
PostgreSQL8KEntspricht PostgreSQL’s 8K Page-Größe
MySQL/InnoDB16KInnoDB Default Page-Größe ist 16K
VMs (QCOW2, raw)16K-64KGast-Dateisysteme machen eigenes Block-Management
Proxmox VM-Storage (zvols)16KProxmox Default zvol blocksize
Allgemeiner Fileserver128KDefault; guter Kompromiss für gemischte Workloads
Medien-Archiv (Fotos, Video)1M-16MGroße sequenzielle Reads profitieren von weniger, größeren Records
Backup-Ziel1MSequenzielles Write/Read dominant

Proxmox-Nutzer

Wenn du Proxmox mit ZFS-Storage betreibst, nutzen deine VM-zvols bereits eine 16K blocksize (volblocksize). Aber wenn du VM-Backups oder ISO-Images auf einem ZFS-Dataset speicherst, setze recordsize auf 1M. Backup-Dateien sind groß und sequenziell — 128K lässt Performance auf dem Tisch.

Wie recordsize mit Compression interagiert

ZFS komprimiert Daten pro Record. Eine größere recordsize gibt dem Kompressionsalgorithmus mehr Daten pro Einheit, was generell eine leicht bessere Kompressionsrate produziert. Der Unterschied ist nicht dramatisch — vielleicht 2-5% bessere Ratios von 8K zu 128K — aber er existiert.

Für Datenbanken entsteht ein echtes Trade-off: Kleinere recordsize (8K) reduziert Write Amplification aber komprimiert leicht schlechter. Für Medien- und Backup-Datasets mit sequenziellem Zugriff gibt eine große recordsize sowohl bessere Kompression als auch besseren Throughput.

Wie recordsize mit Dedup interagiert

ZFS-Deduplizierung arbeitet auf Blöcken (Records). Bei recordsize=128K müssen zwei Dateien identische 128K-aligned Chunks haben um zu deduplizieren. Je größer der Record, desto unwahrscheinlicher sind byte-genaue Übereinstimmungen.

In der Praxis: ZFS-Dedup lohnt sich selten. Die DDT (Dedup Table) Memory-Anforderungen sind extrem. Für die meisten Workloads ist Compression (lz4 oder zstd) die bessere Wahl — ohne globalen Metadaten-Overhead und gut skalierbar.

recordsize-Änderungen wirken NICHT rückwirkend

Das ist das Wichtigste zum Verständnis:

zfs set recordsize=8K tank/postgres

Dieser Befehl ändert die recordsize nur für neue Schreibvorgänge. Jeder Record der bereits auf Disk liegt, behält seine alte recordsize. Um bestehende Daten zu konvertieren, musst du sie neu schreiben — per Dump/Restore oder zfs send | zfs receive in ein neues Dataset.

recordsize VOR dem Schreiben setzen

Setze recordsize immer auf einem Dataset bevor du es mit Daten befüllst. Nachträgliches Ändern erfordert ein komplettes Neuschreiben. Das gilt für neue Proxmox ZFS-Pools, neue Datenbank-Datasets und jede Migration.

Praktische Beispiele

Dataset für PostgreSQL:

zfs create -o recordsize=8K -o compression=lz4 \
    -o atime=off -o primarycache=metadata \
    tank/postgres

Dataset für Medien-Archiv:

zfs create -o recordsize=16M -o compression=zstd \
    -o atime=off \
    tank/media

Dataset für Proxmox VM-Backups:

zfs create -o recordsize=1M -o compression=zstd \
    tank/backups

Aktuelle recordsize aller Datasets prüfen:

zfs get recordsize -r tank

Quick Reference

Vor dem Tuning zwei Fragen stellen:

  1. Was ist das dominante I/O-Muster? Zufällig (Datenbanken, VMs) oder sequenziell (Medien, Backups)?
  2. Was ist die native Block-Größe der Anwendung? PostgreSQL nutzt 8K Pages. InnoDB nutzt 16K.

Wenn sequenziell und groß → nimm 1M oder 16M. Wenn zufällig und klein → matche die Anwendungs-Blockgröße. Wenn echt gemischt → 128K Default ist ein vernünftiger Kompromiss.

Und immer vor dem Schreiben setzen.