Wir setzen bei unseren Kunden bereits seit Jahren auf Proxmox VE als Hypervisor-System, nicht erst seit der Übernahme von VMWare durch Broadcom. Im Laufe der Zeit stolpert man über einige Fallstricke, die man vermeiden sollte. Dazu gehört u.a. das ZFS-System, soz. ein „all-in-one“-Mix aus RAID und Dateisystem. Wer mehr zu ZFS erfahren möchte, seien die Links bei Storage Insider und Thomas Krenn zu empfehlen.
ZFS und consumer drives (SSD/NVME) - bitte nicht
Ein häufiger Fehler bei der Verwendung von ZFS ist der Einsatz von Consumer SSD/NVME und/oder „rotierendem Blech“ (konventionelle Festplatten) für virtuelle Maschinen. Die möglichen Probleme, welche daraus entstehen können, sind vielfältig. Von höherem Verschleiß über I/O delays usw.
Warum ist das so?
-
Keine Power Loss Protection (PLP)
ZFS vertraut darauf, dass sync=always-Writes tatsächlich persistent sind. Ohne PLP drohen Datenverluste bei Stromausfall
-
Schlechte IOPS unter ZFS
ZFS erzeugt hohes IOPS-Aufkommen bei Metadaten und Checksums. Viele Consumer-SSDs brechen dabei ein
-
Zu kleiner SLC-/DRAM-Cache
Consumer-SSDs drosseln stark, wenn der Cache voll ist → massiver Performanceeinbruch bei ZFS-Workloads
-
Falsches Write Behavior/FTL
ZFS schreibt viele kleine, nicht sequentielle Blöcke → Consumer-SSDs optimieren auf lineare Schreiblasten
-
Geringere Langlebigkeit
Consumer-SSDs nutzen aggressives Write-Combining, das ZFS unterwandert – Folge: frühzeitiger Verschleiß
Empfehlungen
Für eine bessere Performance sowie Langlebigkeit empfehlen sich beim Einsatz von ZFS im produktiven Betrieb immer Enterprise-SSDs mit PLP. Bspw.:
- Samsung PM-Serie
- Kingston DC-Serie
- Intel D-Serie
- Kioxia CM/PM-Serie
- Micron 74XX
📊 Vergleich: wear-out von Consumer VS Enterprise SSDs
Stand: 2025
Merkmal | Consumer SSDs | Enterprise SSDs | TBW (total bytes written) | 100 – 1.200 TB (typisch) | 2.000 – 100.000+ TB |
---|---|---|
DWPD (Drive Writes per Day) | 0.2 – 0.5 DWPD | 1 – 10+ DWPD (teilweise bis 50 DWPD) |
Überprovisionierung (OP) | 7 – 12 % | 28 – 40 % (oft konfigurierbar) |
Garantiedauer | 3 – 5 Jahre | 5 – 7 Jahre |
NAND-Typ | TLC, QLC | SLC, eMLC, pTLC |
Power Loss Protection (PLP) | ❌ i. d. R. nicht vorhanden | ✅ Kondensatorgestützt |
Write Amplification | Hoch (durch geringeren OP, QLC) | Gering (durch Controller + OP + Wear-Leveling) |
SMART-Wear-Level-Indikator | Oft unzuverlässig (teils kosmetisch) | Präzise, mit exakter Host-Written vs. NAND-Written-Stats |
Garbage Collection / Trim | Hintergrund-Trim, meist ineffizient | Hardwarebasiert, effizient unter Dauerlast |
MTBF / AFR* | MTBF: 1–1.5 Mio h / AFR: 1–2 % | MTBF: 2–3 Mio h / AFR: <0.44 % |
ZFS-Eignung | ❌ Hoher Verschleiß durch sync writes, kein PLP | ✅ Speziell für ZFS geeignet mit sync/async Mix |
* AFR = annualized failure rate
📊 ZFS-RAID Typen & Unterschiede
Wie „konventionelles“ RAID erlaubt auch ZFS verschiedene Level/Modi. Dabei sollte man beachten, für was der Storage später verwendet werden soll – vor dem Erstellen des RAID.
RAID-Level | Eigenschaften | IOPS | VM-Storage geeignet |
---|---|---|---|
Mirror | 2x identische Disks | Hoch (1x Disk) | ✅ Ja |
Striped Mirror | RAID1+0 Equivalent | Sehr hoch (nx Disk) | ✅ ✅ Ja, optimal |
RAIDZ1 | Parität über n-1 Disks | Niedrig | ❌ Nein |
RAIDZ2 | Doppelte Parität | Sehr niedrig | ❌ Nein |
RAIDZ3 | Dreifache Parität | Extrem niedrig | ❌ Nein |
Mirror 🚀
- 2x identische Disks
- IOPS-Leistung: Hoch (nx Disk)
- VM-Storage geeignet: ✅ Ja
Striped Mirror 🚀🚀
- RAID1+0 Equivalent
- IOPS-Leistung: Sehr hoch (nx Disk)
- VM-Storage geeignet: ✅ ✅ Ja, optimal
RAIDZ1 🐌
- Parität über n-1 Disks
- IOPS-Leistung: niedrig
- VM-Storage geeignet: ❌ Nein
RAIDZ2 🐌🐌
- Doppelte Parität
- IOPS-Leistung: sehr niedrig
- VM-Storage geeignet: ❌ Nein
RAIDZ3 🐌🐌🐌
- Dreifache Parität
- IOPS-Leistung: extrem niedrig
- VM-Storage geeignet: ❌ Nein
Warum RAIDZ für VM-Workloads nicht geeignet ist
- Parität muss bei jeder kleinen Änderung neu berechnet und geschrieben werden
- Sync-Write-lastige Anwendungen (z. B. Datenbanken) leiden massiv unter RAIDZ
- Random I/O (wie bei VMs) performt schlecht auf Paritäts-RAID
📊 Vergleich IOPS bei ZFS-RAID Typen (Random Write)
- Höher: bessere Random Write Performance
- Mirror-Layouts skalieren mit Disks
- RAIDZ leider stark bei kleinen sync writes
RAIDZ eignet sich primär für Archivdaten, große sequentielle reads oder Backups, jedoch nicht für VM-Betriebssysteme oder Datenbanken.
Die volblocksize in ZFS
Zunächst eine kurze Erläuterung, worum es sich dabei überhaupt handelt. Die volblocksize ist die Blockgröße, mit der ZFS Daten in einem sog. ZVOL (virtuelles Blockgerät) speichert. Sie entspricht der kleinsten Einheit, die ZFS für Lese- und Schreiboperationen verwendet.
Sie ist nur für ZVOLs relevant, nicht aber für Datasets. Beim Anlegen von ZVOLs wird diese angelegt und ist nachträglicht nicht mehr änderbar. Man sollte sich also vor dem Erstellen Gedanken um den späteren Einsatzzweck machen.
Die volblocksize hat direkten Einfluss auf:
- IOPS
- ZFS-Komprimierung
- Latenzverhalten
- Plattenplatzverbrauch
Eine kleinere volblocksize (bspw. 4K) bedeutet eine geringere interne Fragmentierung bei kleinen Dateien, gleichzeitig aber auch mehr Metadaten und höhere IOPS-Last für ZFS.
Ein größerer Wert (bspw. 64K) ist effizienter bei großen Dateien und sequenziellen Zugriffen. Die IOPS-Last ist geringer, dafür wird mehr Speicherplatz bei vielen kleinen Dateien verschwendet (sog. „internal fragmentation“).
Die optimale ZFS volblocksize für VMs in Proxmox VE
Es gibt leider nicht die optimale volblocksize in Proxmox VE für VMs, welche alle Szenarien optimal und gleichzeitig abdeckt.
Windows-VMs
Für Windows-VMs mit NTFS-Dateisystem empfiehlt sich eine volblocksize von 16K. Der Installer von Windows formatiert die Systempartition im Setup automatisch mit 4K. Wird eine größere volblocksize (64K) verwendet, kommt es zur sog. Write Amplification – ZFS muss bei jeder kleinen 4K-Schreiboperation ganze 64K-Blöcke verwalten.
Standard Windows VM
- NTFS-Clustergröße: 4K
- Empfohlene volblocksize: 16K
- Gutes Alignment, geringere IOPS-Belastung, besser für Snapshots
Windows File/SQL-Server
- NTFS-Clustergröße: 64K (manuelle Formatierung)
- Empfohlene volblocksize: 64K
- Ideal für sequenzielle I/O, passend zum Cluster
Windows mit vielen kleinen Dateien
- NTFS-Clustergröße: 4K
- Empfohlene volblocksize: 8K - 16K
- Kleinere volblocksize reduziert Write Amplification bei 4K-Dateien
VM-Typ | NTFS-Clustergröße | Empfohlene volblocksize | Erklärung |
---|---|---|---|
Standard Windows VM | 4K | 16K | Gutes Alignment, geringere IOPS-Belastung, besser für Snapshots |
Windows File/SQL-Server | 64K (manuelle Formatierung) | 64K | Ideal für sequenzielle I/O, passend zum Cluster |
Windows mit vielen kleinen Dateien | 4K | 8K – 16K | Kleinere volblocksize reduziert Write Amplification bei 4K-Dateien |
Warum dann nicht 64K bei Standard Windows VMs?
Windows schreibt in 4K, ZFS müsste bei 64K volblocksize für jede 4K-Änderung den ganzen 64K-Block neu schreiben. Das bedeutet wiederum: mehr IOPS, mehr ZFS-Metadatenlast, ineffizientere Snapshots.
16K als guter Kompromiss
16K entspricht 4 × 4K-NTFS-Blöcken und erhöht Effizienz ohne zu viel Verwaltungsaufwand. Gleichzeitig bietet diese Größe bessere Kompression und weniger Fragmentierung als 4K oder 8K.
Separate virtuelle HDDs für File- oder SQL-Server
Werden virtuelle Windows File- oder SQL-Server verwendet, sollte man getrennte virtuelle Disks für das OS und die Dateien/DBs einrichten. Diese formatiert man in Windows bspw. mit 64K (LINK)
Linux-VMs
Linux verwendet je nach Dateisystem und Workload unterschiedlich große I/O-Blöcke. Während das Dateisystem ext4 typischerweise mit 4K-Blöcken arbeitet, sind viele Applikationen so gebaut, dass sie in größeren Chunks lesen/schreiben (z. B. 8K–16K). ZFS kann durch eine gut gewählte volblocksize deutlich effizienter arbeiten – vor allem bei Snapshots, Kompression und RAM-Nutzung.
Allgemeine Linux VM (rootfs)
- Dateisystem: ext4
- Empfohlene volblocksize: 16K
- Gutes Alignment mit typischem Linux-I/O (8K–16K), besser für ZFS-Snapshots
Datenbankserver (SQL)
- Dateisystem: ext4/XFS
- Empfohlene volblocksize: 8K - 16K
- Feinere I/O-Kontrolle, reduziert Write Amplification
Container Hosts (Docker, LXC)
- Dateisystem: ext4/XFS
- Empfohlene volblocksize: 8K - 16K
- Viele kleine Dateien & Layer – kleinere Blöcke effizienter
Archiv- oder Backup VMs
- Dateisystem: XFS/BTRFS
- Empfohlene volblocksize: 64K - 128K
- Große, sequenzielle Datenmengen – weniger IOPS, gute Kompression
IOPS intensive kleinere Dateien (LDAP, journald)
- Dateisystem: ext4
- Empfohlene volblocksize: 4K - 8K
- Kleine, häufige Schreibzugriffe – geringe Latenz durch kleinere Blöcke
VM-Typ | Dateisystem | Empfohlene volblocksize | Erklärung |
---|---|---|---|
Allgemeine Linux VM (rootfs) | ext4 | 16K | Gutes Alignment mit typischem Linux-I/O (8K–16K), besser für ZFS-Snapshots |
Datenbankserver (SQL) | ext4/XFS | 8K – 16K | Feinere I/O-Kontrolle, reduziert Write Amplification |
Container Hosts (Docker, LXC) | ext4/XFS | 8K – 16K | Viele kleine Dateien & Layer – kleinere Blöcke effizienter |
Archiv- oder Backup VMs | XFS/BTRFS | 64K – 128K | Große, sequenzielle Datenmengen – weniger IOPS, gute Kompression |
IOPS intensive kleinere Dateien (LDAP, journald) | ext4 | 4K – 8K | Kleine, häufige Schreibzugriffe – geringe Latenz durch kleinere Blöcke |
Warum 16K als Standardwert?
Ext4 nutzt intern 4K, aber die meisten Anwendungen (journald, apt, systemd, etc.) arbeiten effektiv mit größeren Zugriffsmustern. 16K bietet eine gute Balance aus Performance und Snapshot-Effizienz, bessere ZFS-Kompression und weniger Verwaltungsaufwand als 4K/8K.
Flexibilität
Datenbank- oder Container-VMs profitieren oft von kleineren Werten (z. B. 8K), da sie viele kleine Dateien oder Transaktionen verarbeiten. Backup-/Archiv-VMs profitieren von großen volblocksizes, da diese große Datenmengen effizienter schreiben und besser komprimieren.
Nützliche Befehle & Scripts
Proxmox VE
- Script zur Abfrage aller volblocksizes pro VM in Proxmox VE
#!/bin/bash
echo -e "ZVOL\t\t\tVMID\tSIZE\tUSED\tvolblocksize"
echo "---------------------------------------------------------------"
zfs list -t volume -o name,volsize,used | grep -E "vm-[0-9]+-disk" | while read -r line; do
ZVOL=$(echo "$line" | awk '{print $1}')
SIZE=$(echo "$line" | awk '{print $2}')
USED=$(echo "$line" | awk '{print $3}')
VMID=$(echo "$ZVOL" | grep -oP 'vm-\K[0-9]+')
BLOCKSIZE=$(zfs get -H -o value volblocksize "$ZVOL")
echo -e "$ZVOL\t$VMID\t$SIZE\t$USED\t$BLOCKSIZE"
done
Das Script muss nach dem Erstellen mittels chmod +x get_volblocksize.sh ausführbar gemacht werden. Nach dem Aufruf via ./get_volblocksize.sh sieht die Ausgabe bspw. so aus:
ZVOL VMID SIZE USED volblocksize
---------------------------------------------------------------
rpool/data/vm-100-disk-0 100 32G 2.5G 16K
rpool/data/vm-101-disk-0 101 64G 5.1G 64K
...
Es ist kompatibel mit allen Pools und zeigt auch den verwendeten Speicher an.
- Script zur Migration eines ZVOLs in eine neue volblocksize
Mit diesem Script kann das ZVOL einer VM auf eine neue volblocksize angepasst werden.
Hinweis: das Script „kopiert“ das bestehende ZVOL 1:1 und prüft mittels SHA256 Quelle und Ziel. Es sollte ausreichend Speicherplatz vorhanden sein.
Der Aufruf erfolgt bspw. wie folgt:
./migrate_zvol_blocksize.sh <ID DER VM> <DISK NUMMER> <VOLBLOCKSIZE>
Im Klartext:
./migrate_zvol_blocksize.sh 101 0 16K
Dies würde Disk 0 von VM 101 auf ein neues ZVOL mit 16K volblocksize kopieren. Das neue ZVOL erscheint dann als vm-101-disk-0-migrated
⚠ Diese Variablen sollten vorab angepasst werden: ⚠
POOL="DEIN-ZFS-POOLNAME" # ZFS-Poolnamen anpassen
...
dd if="/dev/zvol/${ORIG_ZVOL}" of="/dev/zvol/${NEW_ZVOL}" bs=16M status=progress conv=sync
bs=16M entspricht der Blockgröße während des Kopierens. Dieser Wert ist für leistungsfähige NVME angepasst und sollte bei langsameren Laufwerken nach unten korrigiert werden. Der gesamte Migrationsvorgang kann (je nach Größe des ZVOLs und Leistungsfähigkeit des Systems) sehr lange dauern. Der größte Zeitanteil entfällt dabei auf die SHA256-Prüfung.
Variante 1 (mit SHA256-Prüfung):
#!/bin/bash
# ⚙ Migration eines ZVOLs mit neuer volblocksize (z. B. 16K)
# Nutzung: ./migrate_zvol_blocksize.sh <VMID> <Disk-ID> <Neue-Blocksize>
# Beispiel: ./migrate_zvol_blocksize.sh 101 0 16K
set -euo pipefail
POOL="DEIN-ZFS-POOLNAME" # ZFS-Poolnamen anpassen
VMID="$1"
DISK_ID="$2"
NEW_BLOCKSIZE="$3"
ORIG_ZVOL="${POOL}/vm-${VMID}-disk-${DISK_ID}"
SNAP_NAME="migrate_$(date +%Y-%m-%d_%H-%M)"
SNAPSHOT="${ORIG_ZVOL}@${SNAP_NAME}"
NEW_ZVOL="${ORIG_ZVOL}-migrated"
echo "🔍 Prüfe Quell-ZVOL ${ORIG_ZVOL} ..."
zfs list -t volume "${ORIG_ZVOL}" > /dev/null
# Optional: prüfen, ob Ziel-ZVOL bereits existiert
if zfs list "${NEW_ZVOL}" &>/dev/null; then
echo "⚠️ Ziel-ZVOL ${NEW_ZVOL} existiert bereits."
read -rp "❓ Möchtest du es löschen? (y/N): " confirm
if [[ "${confirm,,}" == "y" ]]; then
echo "🗑️ Lösche vorhandenes ZVOL..."
zfs destroy -r "${NEW_ZVOL}"
else
echo "❌ Abbruch, um Überschreiben zu vermeiden."
exit 1
fi
fi
SIZE=$(zfs get -H -o value volsize "$ORIG_ZVOL")
echo "📸 Erstelle Snapshot: $SNAPSHOT"
zfs snapshot "$SNAPSHOT"
echo "📦 Erstelle Ziel-ZVOL ${NEW_ZVOL} mit volblocksize=${NEW_BLOCKSIZE} ..."
zfs create -V "$SIZE" -b "$NEW_BLOCKSIZE" "$NEW_ZVOL"
echo "🔁 Kopiere Daten mit dd ..."
dd if="/dev/zvol/${ORIG_ZVOL}" of="/dev/zvol/${NEW_ZVOL}" bs=16M status=progress conv=sync
echo "🔐 Prüfe SHA256 (Quelle vs. Ziel) ..."
SRC_HASH=$(sha256sum "/dev/zvol/${ORIG_ZVOL}" | cut -d ' ' -f1)
DST_HASH=$(sha256sum "/dev/zvol/${NEW_ZVOL}" | cut -d ' ' -f1)
echo "💠 Quelle: $SRC_HASH"
echo "💠 Ziel: $DST_HASH"
if [[ "$SRC_HASH" == "$DST_HASH" ]]; then
echo "✅ Prüfsumme OK. Migration erfolgreich."
echo "🧹 Entferne Snapshot $SNAPSHOT"
zfs destroy "$SNAPSHOT"
echo "❗ Neues ZVOL ist bereit: $NEW_ZVOL"
echo "👉 Du kannst es nun der VM manuell zuweisen oder das alte ZVOL ersetzen."
else
echo "❌ Prüfsummen stimmen nicht überein. Bitte manuell prüfen!"
exit 1
fi
Variante 2 (ohne SHA256-Prüfung):
#!/bin/bash
# ⚙ Migration eines ZVOLs mit neuer volblocksize (z. B. 16K) OHNE SHA256-Pruefung
# Nutzung: ./migrate_zvol_blocksize.sh <VMID> <Disk-ID> <Neue-Blocksize>
# Beispiel: ./migrate_zvol_blocksize.sh 101 0 16K
set -euo pipefail
POOL="DEIN-ZFS-POOLNAME" # ZFS-Poolnamen ggf. anpassen
VMID="$1"
DISK_ID="$2"
NEW_BLOCKSIZE="$3"
ORIG_ZVOL="${POOL}/vm-${VMID}-disk-${DISK_ID}"
SNAP_NAME="migrate_$(date +%Y-%m-%d_%H-%M)"
SNAPSHOT="${ORIG_ZVOL}@${SNAP_NAME}"
NEW_ZVOL="${ORIG_ZVOL}-migrated"
echo "🔍 Prüfe Quell-ZVOL ${ORIG_ZVOL} ..."
zfs list -t volume "${ORIG_ZVOL}" > /dev/null
# Optional: prüfen, ob Ziel-ZVOL bereits existiert
if zfs list "${NEW_ZVOL}" &>/dev/null; then
echo "⚠️ Ziel-ZVOL ${NEW_ZVOL} existiert bereits."
read -rp "❓ Möchtest du es löschen? (y/N): " confirm
if [[ "${confirm,,}" == "y" ]]; then
echo "🗑️ Lösche vorhandenes ZVOL..."
zfs destroy -r "${NEW_ZVOL}"
else
echo "❌ Abbruch, um Überschreiben zu vermeiden."
exit 1
fi
fi
SIZE=$(zfs get -H -o value volsize "$ORIG_ZVOL")
echo "📸 Erstelle Snapshot: $SNAPSHOT"
zfs snapshot "$SNAPSHOT"
echo "📦 Erstelle Ziel-ZVOL ${NEW_ZVOL} mit volblocksize=${NEW_BLOCKSIZE} ..."
zfs create -V "$SIZE" -b "$NEW_BLOCKSIZE" "$NEW_ZVOL"
echo "🔁 Kopiere Daten mit dd ..."
dd if="/dev/zvol/${ORIG_ZVOL}" of="/dev/zvol/${NEW_ZVOL}" bs=16M status=progress conv=sync
echo "✅ Migration abgeschlossen."
echo "🧹 Entferne Snapshot $SNAPSHOT"
zfs destroy "$SNAPSHOT"
echo "❗ Neues ZVOL ist bereit: $NEW_ZVOL"
echo "👉 Du kannst es nun der VM manuell zuweisen oder das alte ZVOL ersetzen."
- LBA size ermitteln und einstellen
Je nach Modell empfiehlt es sich, vor der Erstellung eines RAIDs die bestmögliche Formatierung (LBA-Größen) von SSD/NVME zu ermitteln und ggf. neu zu formatieren.
Bei der Verwendung von NVME wird nvme-cli
benötigt. Dies kann mittels apt installiert werden:
apt install nvme-cli -y
/dev/nvme0n1
): smartctl -a /dev/nvme0n1
Die Ausgabe sieht dann u.a. wie folgt aus:
...
Supported LBA Sizes (NSID 0x1)
Id Fmt Data Metadt Rel_Perf
0 + 512 0 2
1 - 4096 0 1
...
Zur Erläuterung:
- ID = fortlaufende Nummer von 0 – X
- Fmt = aktuelle Formatierung, gekennzeichnet durch das + Symbol
- Data = LBA-Größe
- Rel_Perf = dies ist der wichtigste Wert. Der ideale Wert ist 0. Je niedriger, desto besser
In diesem Fall wäre gemäß Rel_Perf der ideale Wert die Formatierung auf 4096 (ID = 1, Rel_Perf = 1). Zur Formatierung der NVME auf 4096 LBA-Größe diesen Befehl absetzen:
⚠ Achtung: alle Daten auf der NVME werden gelöscht! ⚠
nvme format /dev/nvme0n1 -l 1
Windows
- NTFS Clustergröße auslesen (in Windows VMs)
Variante 1 (CMD-Shell):
fsutil fsinfo ntfsinfo C:
In der Ausgabe auf „Bytes per Cluster“ achten (entspricht der NTFS-Clustergröße):
Bytes per Cluster : 4096
Bytes per Sector : 512
Variante 2 (PowerShell):
Get-WmiObject Win32_Volume | Where-Object { $_.DriveLetter -eq "C:" } | Select-Object DriveLetter, AllocationUnitSize
Um alle bzw. mehrere virtuellen NTFS-Volumes auszugeben, kann dieses Script verwendet werden:
Get-WmiObject Win32_Volume |
Where-Object { $_.FileSystem -eq "NTFS" -and $_.DriveLetter } |
Select-Object DriveLetter, Label, FileSystem, @{Name="ClusterSizeKB"; Expression={ $_.AllocationUnitSize / 1KB }} |
Format-Table -AutoSize
- Datenträger in Windows auf eine bestimmte Clustergröße formatieren
Wenn man in Windows VMs separate virtuelle HDDs verwendet und diese auf eine bestimmte Clustergröße formatieren möchte, sollte man dies über die CMD durchführen (die GUI der Datenträgerverwaltung bietet dieses Option nicht):
format X: /FS:NTFS /A:64K /Q /V:Label
Erklärung:
- X: = Laufwerksbuchstabe
- /FS:NTFS = Dateisystem NTFS
- /A:64K = Clustergröße 64K
- /Q = Schnellformatierung
- /V:Label = Volumebzeichnung (Label ersetzen)