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

164 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
```bash
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`).