...

XFS Allocation Groups: Performance auf NVMe-Systemen optimal nutzen

XFS NVMe entfaltet seine Stärke erst, wenn ich Allocation Groups, Blockgrößen, Mount-Optionen und den I/O‑Scheduler konsequent auf die Eigenschaften moderner NVMe‑SSDs abstimme. Dieser Beitrag zeigt konkret, wie ich ein XFS‑Dateisystem auf NVMe plane, formatiere und betreibe, damit AG‑Parallelität, Log‑Tuning und Hardware‑Queue‑Tiefe messbaren Durchsatz und geringe Latenz liefern.

Zentrale Punkte

  • AG-Design: Genug Allocation Groups für Parallelität, aber ohne übermäßigen CPU‑Overhead wählen.
  • Blockgröße: Dateisystemblöcke an physische 4K‑Sektoren koppeln, um Mehrfachzugriffe zu vermeiden.
  • Mount-Tuning: noatime, allocsize, logbufs/logbsize gezielt kombinieren statt Default.
  • Scheduler: „none“ oder „mq‑deadline“ je nach Latenz‑Zielen testen und festlegen.
  • Workloads: Datenbank, Streaming und KI‑Scratch mit passender AG‑Anzahl und readahead ausrichten.

Warum Allocation Groups NVMe beschleunigen

Allocation Groups trennen freie Blöcke, Inodes und B+-Bäume in voneinander unabhängige Bereiche, sodass mehrere Threads gleichzeitig arbeiten können und Sperren seltener greifen. Genau diese Aufteilung passt zu NVMe, das mit vielen Queues und tiefer Parallelität Anfragen zeitgleich bedient und damit Block‑Konflikte reduziert. In der Praxis übersetze ich Hardware‑Parallelität durch ausreichend viele AGs in parallele Allokationen und schnelle Metadaten‑Updates, was Latenzspitzen glättet. Ein Performance-Vergleich von Dateisystemen zeigt häufig, wie XFS bei parallelen Zugriffen skaliert, während sequentielle Lasten weiter zuverlässig laufen. Wichtig bleibt jedoch die Balance: Zu wenige AGs limitieren parallele Allokationen, zu viele kosten spürbar CPU-Zeit.

AG-Anzahl und AG-Größe bestimmen

Bei der Formatierung setze ich die AG‑Anzahl bewusst, typischerweise im Spektrum von einigen Dutzend bis hin zu 64–128 AGs pro Terabyte, um ausreichend Nebenläufigkeit ohne übertriebenen Verwaltungsaufwand zu erhalten und die Parallelität auszuschöpfen. Mit mkfs.xfs -f -d agcount=64 /dev/nvme0n1 lege ich die Verteilung explizit fest; über -d size= lässt sich alternativ die AG‑Größe steuern. Für Workloads mit vielen kleinen Dateien wähle ich eher mehr, für große sequentielle Streams etwas weniger AGs, um CPU‑Last im Griff zu behalten. Extremwerte meide ich, weil sehr kleine AGs in großer Zahl beim Auffüllen des Dateisystems viel Verwaltung verursachen. Entscheidend bleibt: Ich orientiere mich an Kapazität, RAM‑Ausstattung und typischer I/O‑Charakteristik, damit Allokationen gleichmäßig über AGs streuen.

Blockgrößen korrekt an die Hardware koppeln

Viele NVMe‑SSDs arbeiten intern mit 4K‑Sektoren, auch wenn sie extern 512 Byte anbieten, daher richte ich die Dateisystemblockgröße auf 4096 Byte aus und senke so interne Read‑Modify‑Write‑Zyklen für Schreibzugriffe. Bei der Formatierung nutze ich beispielsweise mkfs.xfs -f -b size=4096 /dev/nvme0n1, wenn die physische Sektorgröße 4K beträgt. Ein falsch ausgerichtetes Dateisystem erzeugt unnötige Zusatz‑I/Os, was gerade bei zufälligen kleinen Writes fühlbar bremst. Die richtige Blockgröße macht Zugriffe konsistent, glättet die Latenz und liefert bessere IOPS bei kurzen Anfragen. Für Spezialfälle mit sehr großen Sequential‑Jobs kombiniere ich 4K‑Blöcke mit größerem readahead, damit die Durchsatzrate steigt.

Mount-Optionen für NVMe-Last

Ohne Tuning läuft XFS bereits flott, doch gezielte Mount‑Optionen holen noch Reserven heraus und sparen unnötige Metadaten‑Aktualisierungen bei Leselast. Ich aktiviere noatime,nodiratime, setze je nach Workload eine größere allocsize (z. B. 64M) und erhöhe Logpuffer mit logbufs=8,logbsize=256k für mehr Metadaten‑Durchsatz. Statt discard am Mount führe ich fstrim periodisch aus, damit TRIM‑Befehle gebündelt laufen. Eine Beispielzeile in /etc/fstab sieht so aus: /dev/nvme0n1 /data xfs noatime,nodiratime,allocsize=64m,logbufs=8,logbsize=256k 0 0. Die folgende Tabelle ordnet gängige Optionen ihrem Effekt und typischen Einsatzfall zu, damit ich Entscheidungen schneller treffe und die Konfiguration dokumentiere.

Option Wirkung Wann einsetzen
noatime,nodiratime Reduziert Metadaten‑Writes bei Zugriffen Viele Reads, Web‑/Analyse‑Workloads
allocsize=64m Bündelt Allokationen, mindert Fragmentierung Große sequentielle Schreibströme
logbufs=8 Mehr parallele Logpuffer für Metadaten Transaktionslast, viele kleine Updates
logbsize=256k Größere Log‑Schreibblöcke Höherer Metadaten‑Durchsatz
kein discard Vermeidet synchrone TRIM‑Kosten Stattdessen regelmäßiges fstrim

I/O-Scheduler: none, mq-deadline und Co.

NVMe‑Controller ordnen Anfragen selbst effizient, daher fahre ich oft mit none am besten und halte so den Overhead niedrig. Für Workloads mit strenger Latenzanforderung teste ich mq-deadline, weil es Antwortzeiten stabilisieren kann, auch wenn der Maximaldurchsatz geringfügig sinkt. Während bfq für Interaktivität punkten kann, ist er auf Server‑NVMe selten erste Wahl. Die Wahl treffe ich erst nach Messungen mit fio, die IOPS, Durchsatz und Latenz getrennt für Read/Write und Random/Sequential erfassen. Details zum Abwägen der Optionen vertiefe ich in diesem kompakten I/O-Scheduler Guide, bevor ich die Einstellung produktiv setze.

Workloads gezielt abstimmen

Datenbanken mit vielen Commits profitieren von moderater AG‑Anzahl, ausgerichteten 4K‑Blöcken, noatime und angehobenem logbsize, damit Metadaten‑Transaktionen zügig laufen. Analytics‑Jobs und Streaming‑Pipelines setze ich mit größerer allocsize und mehr readahead auf hohen sequenziellen Durchsatz. Für KI/ML‑Scratch‑Daten und viele parallele Worker wähle ich eher mehr AGs, noatime, gebündelte Allokationen und none als Scheduler. Backups oder Archivläufe profitieren zusätzlich von periodischem fstrim, um SSD‑Garbage‑Collection zu entlasten. Jede Anpassung sichere ich mit reproduzierbaren Messreihen ab, bevor ich die Defaults dauerhaft ersetze.

Häufige Symptome schnell deuten

Meldet XFS „No space left on device“ trotz offensichtlich freier Kapazität, ist oft eine einzelne AG ausgeschöpft, weshalb ich Datenverteilung, agcount und freie Metadatenräume prüfe. Unerwartet hohe Latenz bei kleinen Random‑Writes deute ich meist als Zeichen für ungeeignete Blockausrichtung, zu kleine allocsize oder überaktive Metadaten‑Updates. In solchen Fällen helfen 4K‑Blöcke, größere Allokationshappen und noatime, um Schreibvorgänge zu bündeln. Steigt die CPU‑Last im Dateisystem auffällig, war die AG‑Anzahl womöglich zu hoch gewählt, speziell wenn das Filesystem nahezu gefüllt ist. Dann reduziere ich AGs bei einer Neuformatierung oder erweitere die Partition, um Verwaltung zu senken.

Parameter-Übersicht als Schnell-Check

Für wiederkehrende Setups halte ich eine kurze Checkliste parat, die ich vor jeder Formatierung abarbeite und so Konstanz in die Ergebnisse bringe. Erstens prüfe ich physische Sektorgröße, Queue‑Tiefe und Controller‑Features der NVMe‑Geräte. Danach lege ich AG‑Anzahl oder AG‑Größe fest und richte die Blockgröße an 4K aus. Anschließend definiere ich Mount‑Optionen, die zur Last passen, und plane ein periodisches fstrim. Zuletzt teste ich I/O‑Scheduler‑Varianten und dokumentiere die schnellste Kombination für den jeweiligen Anwendungsfall.

Schrittweise Planung eines neuen XFS auf NVMe

Zu Beginn ermittle ich Kapazität, physische Sektorgröße, typische Dateigrößen und parallele Thread‑Zahl, damit die AG-Planung fundiert startet. Danach formatiere ich mit angepasster AG‑Anzahl, 4K‑Blockgröße und optionalen Inode‑Parametern, falls viele kleine Dateien zu erwarten sind. Im nächsten Schritt mounte ich mit noatime, passender allocsize sowie optimierten Log‑Parametern und prüfe die Resultate mit fio. Darauf folgt die Scheduler‑Wahl, wobei ich none und mq-deadline vergleiche und dabei sowohl IOPS als auch Latenz im Blick behalte. Abschließend etabliere ich Monitoring und geplantes fstrim, damit die Performance langfristig konstant bleibt und keine Überraschungen auftreten.

Einordnung in Hosting-Umgebungen

In Hosting‑Szenarien mit Containern, Web‑Stacks und Datenbanken zahlt sich eine sauber geplante XFS‑Konfiguration direkt in Antwortzeit und Durchsatz aus. Ich berücksichtige dabei die Queue‑Tiefe und die Zahl paralleler Worker, um AG‑Anzahl und Scheduler sinnvoll zu kombinieren. Eine fundierte Erklärung, warum die Warteschlangenlänge auf NVMe den Takt vorgibt, habe ich mir im Beitrag zur Queue Depth erarbeitet. Für datenintensive Microservices erhöhe ich oft readahead, bündele Allokationen und messe iterativ nach jeder Änderung. Wer seine Anwendungen auf leistungsfähigen Managed‑ oder Root‑Servern betreibt, profitiert damit von niedriger Latenz, hoher Parallelität und gut planbarem Betrieb auf XFS.

Reflink, Inodes und Metadaten-Features bewusst wählen

Bei der Formatierung entscheide ich, ob CoW/Reflink für meinen Anwendungsfall sinnvoll ist. Mit mkfs.xfs -m reflink=1 aktiviere ich Copy‑on‑Write und schnelle Klone, was beim Erstellen vieler Kopien, VM‑Images oder Build‑Artefakte Platz und Zeit spart. Für stark schreibintensive Datenbanken deaktiviere ich Reflink (reflink=0), um Metadaten‑Overhead zu reduzieren und Log‑Druck zu senken. Zusätzlich prüfe ich finobt (Free‑Inode‑B‑Tree), der Allokationsentscheidungen bei vielen Inodes beschleunigt und in aktuellen Tools typischerweise ohnehin aktiv ist.

Die Inode‑Größe bestimme ich über -i size=. Für Workloads mit vielen Extended Attributes (ACLs, SELinux, Applikations‑Metadata) wähle ich 512 oder 1024 Byte, damit Attribute häufiger im Inode Platz finden und nicht in separaten Blöcken landen. Beispiel: mkfs.xfs -f -b size=4096 -i size=512 /dev/nvme0n1. Größere Inodes kosten etwas Platz, sparen aber Zugriffe, wenn Metadaten häufig gelesen oder geschrieben werden. Features wie bigtime verlängern den nutzbaren Zeitstempelbereich moderner Systeme und sind bei neuen Installationen sinnvoll, ohne messbaren Performance‑Nachteil. Auf optionale Strukturen wie rmapbt verzichte ich für reine Performance‑Volumes meist, da sie primär Verwaltbarkeit und Prüfbarkeit erhöhen, aber Mehraufwand verursachen.

Externes Log, Loggröße und Stripe‑Ausrichtung

Für Metadaten‑schwere Lasten lohnt ein separates Log‑Gerät (Journal) auf einer zweiten, sehr latenzarmen NVMe, um Konkurrenz zwischen Nutzdaten und Log‑Writes zu minimieren. Das setze ich bei der Formatierung mit -l logdev=/dev/nvme1n1,size= um und halte die Loggröße so, dass Burst‑Phasen nicht dauernd Log‑Forces auslösen (häufig 1–4 GiB, je nach Transaktionsmuster). Zusammen mit logbufs/logbsize am Mount stabilisiert ein externes Log Transaktionszeiten merklich, wenn viele kleine Dateien oder Metadaten‑Updates anfallen.

Liegt die NVMe hinter einem RAID oder Device‑Mapper, richte ich XFS auf Stripe‑Größen aus, damit Schreibvorgänge Stripe‑Grenzen sauber treffen. Das erfolgt bei der Formatierung über -d su=<stripe_unit>,sw=<stripe_width_in_units>. Anschließend prüfe ich die Werte mit xfs_info /mount. Wichtig: Diese Parameter lassen sich später nicht ohne Neuformatierung ändern. Auf Einzel‑NVMe ohne darunterliegendes Striping überlasse ich XFS das Autotuning.

Partition- und Block‑Ausrichtung sicherstellen

Bevor ich formatiere, lege ich Partitionen 1 MiB‑ausgerichtet an, damit Dateisystemblöcke sauber auf physische 4K‑Grenzen fallen. Mit parted -a optimal oder entsprechender GPT‑Konfiguration verhindere ich unglückliche Offsets. Die effektive physische und logische Sektorgröße verifiziere ich mit cat /sys/block/nvme0n1/queue/physical_block_size und logical_block_size. Erst wenn diese Basis stimmt, entfalten 4K‑Blöcke und Allokationshappen ihre volle Wirkung.

Direct I/O, Page Cache und Writeback steuern

Für Datenbanken und Log‑Ströme, die ihren eigenen Cache managen, setze ich gezielt O_DIRECT, um doppeltes Caching im Page Cache zu vermeiden. XFS skaliert hier sehr gut, solange ich nicht gleichzeitig dieselben Dateien gemischt gepuffert und direkt beschreibe. Für Streaming‑Workloads beschleunigt eine höhere readahead den Durchsatz: blockdev --setra 4096 /dev/nvme0n1 (entspricht 2 MiB) ist ein pragmatischer Startwert, den ich messe und bei Bedarf feiner einstelle.

Zur Systembalance passe ich die Writeback‑Schwellen vorsichtig an. Statt Prozentwerten setze ich auf Größenangaben, um bei großen RAM‑Konfigurationen nicht zu viel Dirty‑Data anzusammeln. Beispiel (mit Bedacht testen):

sysctl -w vm.dirty_background_bytes=268435456
sysctl -w vm.dirty_bytes=2147483648

So verhindere ich lange Flush‑Wellen, die Latenzen anheben. Diese Settings dokumentiere ich je Host, damit sie reproduzierbar bleiben und nicht unbemerkt von Distribution‑Defaults übersteuert werden.

Queue- und CPU‑Topologie ausnutzen

NVMe nutzt Multi‑Queue I/O: Pro CPU‑Core existieren i. d. R. eigene Hardware‑Queues, sodass ich IRQ‑Verteilung und CPU‑Affinity nicht dem Zufall überlasse. Ein laufender irqbalance ist die Baseline; bei Latenz‑Spezialfällen pinne ich die NVMe‑IRQs per /proc/irq/*/smp_affinity gezielt an NUMA‑nahe Cores. cat /sys/block/nvme0n1/queue/scheduler zeigt mir den aktiven Scheduler, nr_requests und rq_affinity beeinflussen, wie Anfragen auf Queues verteilt werden. Für sehr parallelisierte Worker erhöhe ich testweise /sys/block/nvme0n1/queue/nr_requests moderat, um Spitzen besser abzufedern, ohne den Treiber zu überfüllen.

Zusätzlich kann ich Interrupt‑Kollabierung der NVMe‑Geräte feinsteuern (Controller‑Feature). Eine maßvolle Erhöhung der Coalescing‑Parameter glättet IRQ‑Last, darf aber die Latenzziele nicht reißen. Solche Eingriffe belege ich immer mit fio‑Latenzpercentiles, bevor sie in Produktion landen.

Quotas, Projekte und Isolation

In Multi‑Tenant‑Umgebungen setze ich auf Project Quotas, damit Lasten und Platzverbrauch sauber voneinander getrennt bleiben. Ich mounte mit prjquota und verwalte Grenzen über xfs_quota samt /etc/projects und /etc/projid. So lassen sich etwa Build‑Verzeichnisse, Datenbank‑Instanzen oder Mandantenverzeichnisse hart begrenzen, ohne die AG‑Parallelität zu beschneiden.

Für ingest‑lastige Verzeichnisbäume, die viele große Dateien sequenziell schreiben, kann der filestreams‑Allocator sinnvoll sein. Er hält Dateien eines Verzeichnisses enger zusammen und reduziert Fragmentierung. Ich aktiviere ihn gezielt per Mount‑Option für Volumes, die klar streaming‑orientiert sind, und messe den Effekt auf Durchsatz und CPU‑Last.

Wachstum, Snapshots und Lebenszyklus

XFS kann online wachsen, nicht aber schrumpfen. Deshalb plane ich Kapazität und AG‑Layout so, dass künftige Erweiterungen via LVM/VMDK problemlos möglich sind. Mit xfs_growfs /mount erweitere ich das Dateisystem nach oben, die AG‑Struktur wächst dabei mit. Parameter wie sunit und swidth sind festgelegt – wer RAID‑Geometrien ändert, sollte daher Neuformatierung und Restore einplanen.

Für konsistente Snapshots in Kombination mit LVM oder Storage‑Backends friere ich das Dateisystem kurz ein: xfs_freeze -f /mount, Snapshot erstellen, xfs_freeze -u /mount. Das minimiert Log‑Replays und garantiert saubere Wiederherstellungen. Zur Laufzeitgesundheit plane ich regelmäßiges xfs_scrub (wo verfügbar) und halte xfs_repair als Offline‑Tool parat. SMART‑Daten, nvme smart-log und iostat -x stehen auf meiner Watchlist, um Degradation früh zu erkennen.

Testmethodik und belastbare Baselines

Bevor ich Defaults ersetze, messe ich reproduzierbar. Ich starte mit klaren fio‑Profiles, die IOPS, Durchsatz und Latenz getrennt betrachten und Warm‑up‑Phasen beinhalten:

[global]
ioengine=io_uring
direct=1
runtime=60
time_based=1
group_reporting=1
randrepeat=0

[randread-4k]
filename=/data/testfile
rw=randread
bs=4k
iodepth=64
numjobs=8

[randwrite-4k]
filename=/data/testfile
rw=randwrite
bs=4k
iodepth=64
numjobs=8

[seqread-1m]
filename=/data/testfile
rw=read
bs=1m
iodepth=32
numjobs=4

[seqwrite-1m]
filename=/data/testfile
rw=write
bs=1m
iodepth=32
numjobs=4

Je nach Ziel passe ich numjobs an die CPU‑Kerne und iodepth an die gewünschte Queue‑Tiefe an. Wichtig sind konsistente Randbedingungen (gleicher Füllstand, identische Mount‑Optionen, sauber getrimmtes Volume). Ausreißer filtere ich, indem ich mehrere Läufe mittlere und 99‑Perzentile vergleiche. So treffe ich fundierte Entscheidungen zwischen none und mq-deadline, zwischen kleinerer und größerer allocsize oder bei der Frage, ob ein externes Log wirklich hilft.

Feinabstimmung von allocsize, Reflink und Co.

allocsize ist ein nützliches Werkzeug, aber kein Allheilmittel. Bei rein zufälligen Klein‑Writes erzeugen zu große Allokationshappen unnötigen Schreibaufwand. Ich wähle daher je Workload konservative Werte und prüfe Fragmentierung sowie Latenz. Mit aktiviertem Reflink meide ich Dauer‑Kleinupdates in denselben Dateibereichen, weil CoW zusätzliche Metadatenarbeit bedeutet. Brauche ich schnelle Klone, halte ich Log‑Puffer groß und sorge für viel freien, zusammenhängenden Raum in mehreren AGs.

Sichere Defaults: Barriers, Discard und Konsistenz

Schreibbarrieren (Write Barriers) und FUA sind auf modernen Stacks standardmäßig aktiv – daran rüttle ich nicht, um Datenverlustrisiken zu vermeiden. nobarrier kommt für mich nicht infrage, selbst wenn einzelne Benchmarks kurzfristig zulegen. discard am Mount bleibt aus, der systemweite fstrim‑Timer erledigt TRIM effizient in Ruhephasen. Diese Kombination liefert mir verlässlich niedrige Latenz bei hoher Nachhaltigkeit der SSD‑Leistung.

Kurz zusammengefasst

XFS skaliert durch Allocation Groups horizontal und nutzt so den inhärenten Parallelismus von NVMe effektiv aus. Ich bestimme AG‑Anzahl, Blockgrößen, Mount‑Optionen und Scheduler nicht nach Bauchgefühl, sondern nach Workload‑Profil und Messdaten. Für kleine Random‑Writes zählt saubere Ausrichtung und schlanker Scheduler, für große Streams eher großzügige allocsize und readahead. Typische Stolpersteine wie unausgewogene AGs oder synchrone Discards löse ich durch Neuaufteilung und periodisches fstrim. Wer diese Stellschrauben systematisch anfasst, hält Latenz niedrig, treibt IOPS hoch und sichert dauerhaft Performance.

Aktuelle Artikel

Serverrack mit Linux-Systemen und Visualisierung von eBPF Kernelanalyse
Technologie

bpftool – Einstieg in moderne Kernelanalyse mit eBPF

Lerne, wie du bpftool für moderne Kernelanalyse mit eBPF einsetzt: Grundlagen, wichtigste Kommandos und Praxisbeispiele für Observability im Linux-Kernel mit Fokus auf bpftool.