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>
164 lines
7.5 KiB
Markdown
164 lines
7.5 KiB
Markdown
# 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`).
|