Migration abgeschlossen. Quelle: 500 GB. Ziel: 920 GB.

Gleiche Anzahl Dateien. Gleiche Dateigrößen. Jede Checksumme besteht. Aber das Ziel verbraucht fast doppelt so viel Speicherplatz. Du führst du dreimal aus, weil du es nicht glaubst.

Das passiert, wenn dein Migrationstool Hard Links nicht versteht.

Ein Hard Link ist, wenn zwei Dateinamen auf dieselben Daten auf der Festplatte zeigen. Keine Kopie. Kein Symlink. Dieselben Bytes, mit zwei (oder mehr) Verzeichniseinträgen, die auf denselben Inode verweisen.

# Eine Datei und einen Hard Link dazu erstellen
echo "important data" > original.txt
ln original.txt linked.txt

# Gleicher Inode, gleiche Daten, eine Kopie auf der Festplatte
ls -li original.txt linked.txt
1048577 -rw-r--r-- 2 root root 15 Feb 13 10:00 original.txt
1048577 -rw-r--r-- 2 root root 15 Feb 13 10:00 linked.txt

Beachte, dass die Inode-Nummer (1048577) identisch ist. Der Link-Count ist 2. Es gibt nur eine Kopie von “important data” auf der Festplatte, aber sie hat zwei Namen.

Das ist kein obskurer Sonderfall. Hard Links tauchen überall auf:

Warum die Migration sie zerstört

Wenn ein Migrationstool auf original.txt und linked.txt trifft, sieht es zwei Dateien. Beide melden eine Größe von 15 Bytes. Beide haben Inhalt zum Kopieren. Also kopiert es die Daten zweimal.

Quelle (1 Kopie auf der Festplatte):
  original.txt  →  inode 1048577  →  [data: 15 bytes]
  linked.txt    →  inode 1048577  ↗

Ziel (2 Kopien auf der Festplatte):
  original.txt  →  inode 2097153  →  [data: 15 bytes]
  linked.txt    →  inode 2097154  →  [data: 15 bytes]

Jeder Hard Link wird zu einer eigenständigen Datei. Die Beziehung zwischen ihnen ist weg.

Speicherverbrauch-Multiplikator

Wenn eine 1-GB-Datei auf der Quelle 4 Hard Links hat, verbraucht sie 1 GB Speicher. Nach der Migration sind es 4 GB. Der Multiplikator ist der Link-Count. Backup-Verzeichnisse mit viel Hard-Linking können auf dem Ziel leicht 3x oder 4x so groß werden.

Wie viel Speicherplatz verlierst du?

Das hängt ganz von deiner Link-Dichte ab. Ein Standard-Dateiserver ohne Hard Links verliert nichts. Ein Server mit rsnapshot und 30 täglichen Snapshots und vielen unveränderten Dateien dazwischen? Erwarte, dass das Ziel 5x bis 10x den tatsächlichen Speicherverbrauch der Quelle hat.

Schnelle Methode zur Abschätzung vor der Migration:

# Dateien mit Link-Count > 1 zählen (Hard-gelinkte Dateien)
find /source -type f -links +1 | wc -l

# Die größten Hard-gelinkten Dateien anzeigen (größter Speicher-Impact)
find /source -type f -links +1 -exec stat -c '%s %h %n' {} \; \
  | sort -rn | head -20

Die zweite Spalte ist der Link-Count. Eine 500-MB-Datei mit Link-Count 12 bedeutet, dass dein Tool 6 GB statt 500 MB kopiert.

Wie du es nach der Migration prüfst

Vergleiche den tatsächlichen Speicherverbrauch zwischen Quelle und Ziel:

# Scheinbare Größe (Summe der Dateigrößen, zählt gelinkte Dateien mehrfach)
du -sh --apparent-size /source/path
du -sh --apparent-size /dest/path

# Tatsächlicher Speicherverbrauch (was wirklich auf der Festplatte liegt)
du -sh /source/path
du -sh /dest/path

Auf der Quelle, wenn Hard Links vorhanden sind, ist du -sh deutlich kleiner als du -sh --apparent-size. Auf dem Ziel nach einer naiven Kopie sind sie nahezu identisch, weil jeder Link zu einer echten Datei wurde.

Quelle:
  Scheinbare Größe:          920 GB
  Tatsächlicher Verbrauch:   500 GB    ← Hard Links sparen 420 GB

Ziel (nach Migration):
  Scheinbare Größe:          920 GB
  Tatsächlicher Verbrauch:   920 GB    ← Hard Links sind weg, alle Daten dupliziert

Schneller Plausibilitätscheck

Wenn du -sh und du -sh --apparent-size auf deiner Quelle denselben Wert zurückgeben, hast du kein signifikantes Hard-Linking. Wenn ein großer Unterschied besteht, ist dieser Unterschied genau der zusätzliche Speicherplatz, den dein Ziel nach einer naiven Migration verbraucht.

Was rsync -H macht

rsync hat ein Flag dafür: -H (oder --hard-links). Es verfolgt Inodes während des Transfers und stellt Hard-Link-Beziehungen auf dem Ziel wieder her.

rsync -aH /source/ /dest/

Es funktioniert. Aber es hat seinen Preis.

Um zu erkennen, dass zwei Dateien denselben Inode teilen, muss rsync sich jeden Inode merken, den es während des gesamten Transfers gesehen hat. Bei einem Dataset mit 10 Millionen Dateien sind das 10 Millionen Inode-Einträge im Speicher. Bei großen Datasets kann das mehrere Gigabyte RAM verbrauchen und die initiale Dateilistenerstellung deutlich verlangsamen.

Für kleine Datasets ist -H selbstverständlich. Für Datasets mit Dutzenden Millionen Dateien musst du den Speicher-Overhead einplanen oder den Transfer in kleinere Läufe aufteilen.

rsync -H Speicherverbrauch

rsync speichert Inode-zu-Pfad-Zuordnungen im Speicher für den gesamten Lauf. Bei ungefähr 100 Bytes pro Eintrag brauchen 10 Millionen Dateien etwa 1 GB RAM allein für das Hard-Link-Tracking. 100 Millionen Dateien treiben das auf 10 GB. Wenn rsync mitten im Transfer vom OOM-Killer beendet wird, ist das oft der Grund.

Cross-Protocol macht es schlimmer

Hard-Link-Erhaltung ist eine Dateisystem-Operation. Du brauchst ein Ziel-Dateisystem, das das Erstellen von Links unterstützt, und dein Tool muss eine Möglichkeit haben, es anzuweisen.

Bei einem lokalen oder NFS-Transfer ist das einfach. Der link()-Syscall funktioniert. Aber wenn SMB im Spiel ist, wird es schwierig. SMB hat keine native Remote-Hard-Link-Erstellung in gängigen Implementierungen. Du kannst einem entfernten SMB-Server nicht sagen “mach diese Datei zu einem Hard Link auf jene andere Datei”, so wie es mit einem lokalen Dateisystem-Aufruf geht.

Das bedeutet:

Wenn deine Migration SMB auf einer Seite involviert, geh davon aus, dass Hard Links verloren gehen, und plane den zusätzlichen Speicherverbrauch ein.

Die Scan-Phase ist entscheidend

Die Kernidee: Hard-Link-Erkennung muss beim Scannen passieren, nicht beim Transfer. Bis du Dateien kopierst, musst du bereits wissen, welche Dateien Inodes teilen, damit du die Daten einmal kopieren und für den Rest Links erstellen kannst.

Das erfordert einen Zwei-Phasen-Ansatz:

  1. Scan: Quelle durchlaufen, Inode-Nummern aufzeichnen, Link-Gruppen identifizieren
  2. Transfer: Für jede Link-Gruppe die erste Datei normal kopieren, dann Hard Links für den Rest erstellen

Tools, die Dateien direkt streamen (gleichzeitig durchlaufen und kopieren), haben es damit schwerer, weil sie den zweiten Link treffen können, bevor sie den ersten fertig geschrieben haben.

syncopio advantage

syncopio erkennt Hard Links während der Scan-Phase, indem es Inode-Nummern über das gesamte Dataset trackt. Beim Transfer wird die erste Datei jeder Link-Gruppe normal kopiert. Jede weitere Datei mit demselben Inode wird zu einem Hard-Link-Task statt einem Kopier-Task. Keine doppelten Daten geschrieben, kein zusätzlicher Speicherverbrauch.

Vor der Migration: die Checkliste

1. Hard Links auf der Quelle prüfen

find /source -type f -links +1 | wc -l

Wenn das Ergebnis Null ist, bist du fein raus. Wenn nicht, lies weiter.

2. Speicherersparnis durch Hard Links messen

echo "Apparent: $(du -sh --apparent-size /source | cut -f1)"
echo "Actual:  $(du -sh /source | cut -f1)"

Die Differenz zwischen diesen Zahlen ist, was du verlierst, wenn Links nicht erhalten werden.

3. Hard-Link-Support deines Tools prüfen

ToolHard-Link-FlagSpeicherkosten
rsync-HHoch (trackt alle Inodes im RAM)
cp-a erhält nur lokalNiedrig
tarErhält standardmäßigMittel
rcloneKein SupportN/A
RobocopyKein SupportN/A

4. Nach der Migration verifizieren

# Gleicher Check wie oben
du -sh /source
du -sh /dest

Wenn die Zahlen übereinstimmen, wurden die Links erhalten. Wenn das Ziel deutlich größer ist, wurden sie es nicht.


Weiterführende Lektüre: