syncova-backup/docs/performance.md
Jerrit Fritzsche 610719c316
Some checks failed
CI / Backend (Go) (push) Failing after 3m7s
CI / Frontend (React/TypeScript) (push) Successful in 37s
CI / Sicherheitsprüfungen (push) Successful in 44s
Syncova Backups V1
Enterprise-Backup-, Recovery-, Verification-, Security- und
Monitoring-Plattform fuer Proxmox VE, Windows, Linux und Dateisysteme.

Der Leitsatz, der fast jede Entscheidung erklaert: Ein Backup gilt erst als
vertrauenswuerdig, wenn Integritaet geprueft und Wiederherstellbarkeit
nachgewiesen wurde. Deshalb steigt ein Wiederherstellungspunkt erst nach einem
tatsaechlich durchgefuehrten Restore-Test auf "recoverable", und Unbekanntes
geht in keine Bewertung als "gut" ein.

Umfang (Phasen 0-23):

- Repository Engine: inhaltsadressierte Bloecke, atomares Commit-Protokoll,
  Katalogaufbau allein aus den Manifesten — ohne Datenbank
- Backup Engine: inhaltsabhaengiges Chunking, Deduplizierung trotz
  Verschluesselung, zstd, AES-256-GCM, Streaming mit Gegendruck
- Agenten fuer Windows und Linux mit Auftragsabholung (Pull-Modell)
- Proxmox-Provider mit beiden Zugriffswegen auf die Sicherungsarchive
- Scheduler, Recovery Engine mit Pruefpunkt, Verification, Unveraenderlichkeit
- Weboberflaeche, Kennzahlen, Meldungen, Berichte, Security Center,
  Ransomware-Heuristik (meldet, handelt nie)
- Disaster Recovery, Haertung, Leistungsmessung, Chaos Testing
- Eingefrorene Vertraege fuer API, Migrationen, Backup-Format und Repository
- Auslieferungspaket fuer linux/amd64, linux/arm64 und windows/amd64

Nicht enthalten und als solches gekennzeichnet: Kapazitaetsprognose, Backup
Copy, Changed Block Tracking bei Proxmox, erweiterte Attribute und ACLs.

Gebaut, aber nie auf echter Hardware gefahren: der Windows-Dienst, die
systemd-Einheit und der verpflichtende Proxmox-Meilenstein — ob eine
wiederhergestellte VM startet, ist ungeprueft. Einzelheiten in CHANGELOG.md
und docs/release-candidate.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:10:54 +02:00

7.5 KiB
Raw Permalink Blame History

Leistungsmessung (Phase 20)

Die Regel dieser Phase steht in einem Satz:

Eine Zahl ohne Messbedingungen ist wertlos — und wird trotzdem zitiert.

Deshalb trägt jede Messung die Maschine, die Datenmenge, die Beschaffenheit der Daten und die Einstellungen bei sich. „420 MiB/s" ohne den Zusatz „inkompressible Daten, lokale SSD, acht Kerne" ist eine Zahl, die in einer Präsentation landet und dort etwas anderes behauptet, als gemessen wurde.

Das Projekt hat diesen Fehler bereits einmal gemacht: In Phase 4 zeigte ein Lauf eine 1021-fache Kompression, weil die Testdaten periodisch waren. Seither gilt: Durchsatzmessungen ausschließlich mit inkompressiblen Daten. Result.Validate() erzwingt das jetzt im Modell — wer eine Messreihe ergänzt, stolpert darüber, statt es zu übersehen.

make build
./bin/syncova-bench run --workdir /pfad/zum/arbeitsverzeichnis --scale klein

Messbedingungen

Maschine Apple M1, 8 Kerne, 8 GiB Arbeitsspeicher
Betriebssystem darwin/arm64
Laufzeitumgebung go1.26.5
Ablage lokale SSD
Daten inkompressible Zufallsdaten
Verschlüsselung aus (siehe „Was nicht gemessen wurde")
Umfang klein

Ergebnisse

Szenario Daten Dauer Durchsatz Kerne Speicher
Eine große Quelle 512 MiB, 1 Datei 3,37 s 151,9 MiB/s 0,7 138 MiB
Viele kleine Dateien 62,5 MiB, 4000 Dateien 36,99 s 1,7 MiB/s (107 Dateien/s) 0,2 98 MiB
Zweiter Lauf, unverändert 512 MiB, 1 Datei 0,77 s 662,8 MiB/s 1,8 32 MiB
Vier gleichzeitige Aufträge 256 MiB, 4 Ziele 1,72 s 148,4 MiB/s 0,8 386 MiB
Langsames Netz (1 MiB/s) 8 MiB 7,10 s 1,1 MiB/s 0,0 200 MiB
Wenig CPU (zwei Kerne) 128 MiB 0,97 s 131,4 MiB/s 0,5 98 MiB
Wenig Speicher (256 MiB) 256 MiB 1,50 s 171,2 MiB/s 0,8 195 MiB

Was die Zahlen bedeuten

Der zweite Lauf ist viermal schneller als der erste (662,8 gegen 151,9 MiB/s). Gelesen und gehasht wird weiterhin alles — gespart wird das Ablegen. Das bestätigt die Aussage aus Phase 6 mit einer zweiten, unabhängigen Messung: Der Gewinn einer Zusatzsicherung ist Zeit, nicht Speicher.

Bei einer großen Quelle ist die Platte der Engpass, nicht die CPU. 0,7 ausgelastete Kerne von acht bedeuten: Die Pipeline wartet. Mehr Arbeiter brächten hier nichts.

Vier gleichzeitige Aufträge halten den Durchsatz (148,4 gegen 151,9 MiB/s einzeln). Das Nadelöhr ist also nicht die Anlage, sondern die Ablage — vier Aufträge teilen sich dieselbe Bandbreite.

Die Streaming-Pipeline hält, was Phase 4 verspricht. Bei einer Speichergrenze von 256 MiB und einer Quelle von 256 MiB blieb der Höchststand bei 195 MiB. Ein Verfahren, das die Quelle in den Speicher lädt, käme hier nicht durch.

Der Bandbreitenbegrenzer wirkt. 8 MiB bei 1 MiB/s in 7,10 s, bei 0,0 ausgelasteten Kernen — die Anlage wartet, wie sie soll.

Der Fund: 4 MiB Puffer für jede 16-KiB-Datei

Der erste Messlauf über 4000 kleine Dateien forderte 16,0 GiB Speicher an — für 62,5 MiB Nutzdaten. Faktor 256, dazu 1447 Speicherbereinigungen.

Die Ursache stand exakt in der Zahl: Der Chunker legte seinen Lesepuffer stets in Höchstblockgröße an, also 4 MiB. 4000 × 4 MiB = 16 GiB.

ChunkerOptions.ExpectedSize gibt ihm jetzt die bekannte Dateigröße mit. Die Engine kennt sie ohnehin aus der Erfassung.

vorher nachher
Angeforderter Speicher 16,0 GiB 411 MiB
Speicherbereinigungen 1447 90
Rechenzeit im Programm 5,44 s 1,27 s

Der eigentliche Engpass bei kleinen Dateien: fsync

Der Speicherfix senkte den Rechenaufwand um den Faktor vier — die Laufzeit blieb bei 37 Sekunden. Der Speicher war also nicht der Engpass.

Bei 0,2 ausgelasteten Kernen wartet die Anlage auf die Platte. Eine direkte Messung auf derselben Maschine zeigt, worauf:

  500 Dateien schreiben, mit fsync:    4,41 s  (113 Dateien/s)
  500 Dateien schreiben, ohne fsync:   0,11 s  (4722 Dateien/s)
  Faktor:                              42x

113 Dateien je Sekunde ist die Obergrenze dieser Maschine bei haltbarem Schreiben. Die Anlage erreicht 107 — der Eigenanteil über die Haltbarkeitsgarantie hinaus liegt bei rund fünf Prozent.

Das ist kein Implementierungsfehler, sondern der Preis des Commit-Protokolls aus Phase 2: Temp-Datei → fsync Datei → rename → fsync Verzeichnis. Ohne die beiden fsync überlebt eine Datei den Stromausfall unvollständig.

Eine mögliche Verbesserung, die hier bewusst nicht umgesetzt wurde: Der Verzeichnis-fsync ließe sich bündeln — einmal am Ende der Session statt nach jedem Block. Das Argument dafür ist stichhaltig: Vor dem Commit-Marker sind die Blöcke ohnehin nur herumliegende Daten, und ein Backup ohne gültiges Manifest ist unvollständig, egal wie viele Verzeichniseinträge überlebt haben.

Der Eingriff gehört aber nicht in eine Messphase. Er verändert die Haltbarkeitszusage der Anlage, und eine solche Entscheidung will für sich getroffen und für sich nachgewiesen werden — nicht nebenbei, weil eine Messung eine Zahl zeigte.

Was nicht gemessen wurde

Ein Messbericht, der verschweigt, was er nicht gemessen hat, liest sich vollständig und ist es nicht. Dieselbe Regel wie bei den ungeprüften Bereichen im Security Center (Phase 15).

  • Eine große virtuelle Maschine. Ohne Proxmox-Verbund nicht messbar; der verpflichtende End-to-End-Nachweis der Phase 7 ist nicht erbracht. Die große Einzelquelle misst denselben Datenpfad, nur ohne die Datenträgerabfrage des Hypervisors.
  • Langsames Repository. Ein künstlich verlangsamtes Ziel würde die Wartezeit messen, die man ihm vorgibt — eine Zahl, die man sich selbst ausgedacht hat. Aussagekräftig wäre eine Messung gegen echten Netzwerkspeicher.
  • Datenträger-Ein-/Ausgaben je Sekunde. Auf macOS ohne erweiterte Rechte nicht je Prozess auszulesen. Die Zahl der geschriebenen Blöcke steht ersatzweise in den Ergebnissen.
  • Netzwerkdurchsatz. Alle Messungen liefen gegen ein lokales Repository. Eine Netzmessung ohne entfernte Gegenstelle wäre eine Messung des Rückschleifen-Geräts.
  • Der Aufschlag der Verschlüsselung. Die Messungen liefen ohne Datenschlüssel; sonst ginge jeder Block durch AES-256-GCM, und die Zahl beantwortete eine andere Frage. Der Aufschlag gehört gesondert gemessen.
  • Repository-Konkurrenz. Die vier gleichzeitigen Aufträge schrieben in vier eigene Repositories. Ein gemeinsames Ziel hält nur eine Schreibsperre bereit — gemessen würde dann das Warten darauf, nicht der Durchsatz.

Zwei Funde am Messwerkzeug selbst

Sie gehören hierher, weil sie zeigen, wie leicht eine Messreihe unbemerkt das Falsche misst.

Der zweite Aufruf scheiterte an einer bereits vergebenen Backup-Kennung. Ein Messwerkzeug, das sich nicht wiederholen lässt, ist keines — die zweite Messung ist die, die zählt. Die Kennung trägt jetzt den Zeitpunkt.

Der zweite Aufruf maß etwas anderes als der erste. Weil das Repository aus dem vorherigen Lauf bestehen blieb, deduplizierte „Eine große Quelle" beim zweiten Mal einfach alles: 641 MiB/s statt 152, und keine abgelegten Bytes. Beide Läufe lieferten plausible Zahlen — nur beantworteten sie verschiedene Fragen. Aufgefallen ist es allein an der fehlenden Zeile „Abgelegt".

Jedes Szenario setzt sein Repository jetzt zurück; nur das Deduplizierungsszenario nutzt das bestehende ausdrücklich (ReuseRepository: true).