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>
197 lines
8.7 KiB
Markdown
197 lines
8.7 KiB
Markdown
# Kennzahlen und Diagramme
|
||
|
||
Phase 13. Zwölf Diagramme über sieben Zeiträume — und die eine Regel, die
|
||
darüber entscheidet, ob eine Kurve etwas wert ist:
|
||
|
||
> **Eine Lücke ist keine Null.**
|
||
|
||
Ein Zeitfenster ohne Sicherungslauf hat keinen Durchsatz. Nicht null Byte je
|
||
Sekunde — *keinen*. Wer Lücken als Nullen zeichnet, erzeugt eine Kurve, die
|
||
nachts auf den Boden fällt: Die Anlage sieht aus, als wäre ihre Leistung
|
||
eingebrochen, obwohl sie nur nichts zu tun hatte.
|
||
|
||
Deshalb trägt jeder Punkt ein `has_value`, und das Diagramm unterbricht die
|
||
Linie, statt sie durch den Nullpunkt zu ziehen.
|
||
|
||
## Woher die Daten kommen
|
||
|
||
Zehn der zwölf Reihen werden aus Tabellen **aggregiert**, die es bereits gibt:
|
||
|
||
| Quelle | Reihen |
|
||
| --- | --- |
|
||
| `backup_job_runs` | Erfolg/Fehlschlag, Dauer, Durchsatz, Datenmengen |
|
||
| `backups` | Deduplizierung, Alter des jüngsten Wiederherstellungspunkts |
|
||
| `restore_jobs` | Dauer der Wiederherstellungen |
|
||
| `verification_jobs` | Ergebnisse der Prüfungen |
|
||
| `metric_samples` | Speicherwachstum, Auslastung |
|
||
|
||
Diese Zahlen ein zweites Mal als Zeitreihe abzulegen wäre die übliche Lösung und
|
||
die schlechtere: Zwei Quellen für dieselbe Aussage laufen auseinander, und man
|
||
merkt es erst, wenn jemand nachrechnet.
|
||
|
||
`metric_samples` nimmt deshalb nur auf, was sonst **verloren geht** —
|
||
Momentaufnahmen, die beim nächsten Schreiben überschrieben werden.
|
||
`repositories.used_bytes` ist genau das: der heutige Stand, ohne jede Erinnerung
|
||
an gestern.
|
||
|
||
### Was nicht gemessen wird
|
||
|
||
| Diagramm | Warum nicht |
|
||
| --- | --- |
|
||
| Ressourcenlast der Agenten | Die Agenten melden keine Ressourcendaten. Ihre Lebendmeldung trägt Version und Zeitpunkt, sonst nichts. |
|
||
| Wirkung der Kompression | Die Engine komprimiert und verschlüsselt in einem Zug und meldet nur die abgelegte Menge; `compressed_bytes` bleibt leer. |
|
||
|
||
Beide erscheinen im Katalog mit dieser Begründung. Die zweite Lücke fiel erst im
|
||
Nachweis auf: Das Diagramm war als verfügbar geführt und blieb leer — die
|
||
unangenehmste Sorte Fehler, weil sie funktionsfähig aussieht.
|
||
|
||
`compressed_bytes` mit der abgelegten Menge zu füllen wäre falsch: Der
|
||
Verschlüsselungsaufwand von 28 Byte je Block erschiene dann als schlechte
|
||
Kompression.
|
||
|
||
## Zeiträume und Auflösung
|
||
|
||
| Zeitraum | Fensterbreite | Punkte |
|
||
| --- | --- | --- |
|
||
| 1h | 1 Minute | 60 |
|
||
| 24h | 15 Minuten | 96 |
|
||
| 7d | 1 Stunde | 168 |
|
||
| 30d | 6 Stunden | 120 |
|
||
| 90d | 1 Tag | 90 |
|
||
| 1y | 7 Tage | 53 |
|
||
| custom | abgeleitet, mindestens 1 Minute | ≤ 500 |
|
||
|
||
Die Breite folgt dem Zeitraum und ist nicht frei wählbar: Eine Kurve, deren
|
||
Punktabstand der Aufrufer bestimmt, lässt sich zwischen zwei Ansichten nicht
|
||
vergleichen. Ein Jahr in Minutenschritten ergäbe über eine halbe Million Punkte.
|
||
|
||
### Der letzte Abschnitt wird aufgerundet
|
||
|
||
Ein Jahr geteilt durch sieben Tage ergibt 52,14 Fenster. Abgerundet fielen die
|
||
letzten ein bis sieben Tage aus der Auswertung — ausgerechnet die jüngsten
|
||
Ereignisse. Im Nachweis zeigte der Jahresverlauf **null Messungen**, obwohl am
|
||
selben Tag mehrere Läufe stattgefunden hatten.
|
||
|
||
Das letzte Fenster ist dadurch schmaler als die übrigen. Der bessere Tausch: Ein
|
||
leicht kürzerer Rand fällt niemandem auf, eine fehlende Woche schon.
|
||
|
||
## Was die Zahlen bedeuten
|
||
|
||
- **Ein Teilfehler zählt nicht als Erfolg.** Die Reihen `succeeded`,
|
||
`partial_failure` und `failed` stehen getrennt.
|
||
- **Abgebrochene Läufe fehlen in der Dauer.** Ihre Laufzeit ist die Zeit bis zum
|
||
Abbruch und sagt nichts über die Leistung.
|
||
- **Der Durchsatz wird neu berechnet,** nicht aus `throughput_bps` gelesen: Die
|
||
Spalte bleibt bei kurzen Läufen leer, und ein Mittelwert über teils fehlende
|
||
Werte wäre schief. Läufe unter einer Sekunde fallen heraus — bei ihnen
|
||
bestimmt die Messungenauigkeit das Ergebnis.
|
||
- **Eine negative Deduplizierungsersparnis ist möglich** und kein Fehler: Bei
|
||
inkompressiblen Daten ohne Wiederholungen kostet die Verschlüsselung je Block
|
||
ein paar Bytes mehr, als sie einspart.
|
||
- **Das Alter des jüngsten Wiederherstellungspunkts** entsteht nicht durch
|
||
Gruppieren: Gefragt ist für *jedes* Zeitfenster der Abstand zum bis dahin
|
||
jüngsten Backup, auch wenn in diesem Fenster keines lief. Genau das ist der
|
||
Sinn der Kurve — sie steigt, solange nichts gesichert wird. Vor dem ersten
|
||
Backup gibt es keinen Wert, nicht das Alter null.
|
||
- **Speicherwachstum misst das Dateisystem, nicht das Repository.** Bei geteilter
|
||
Ablage wächst die Kurve auch durch fremde Daten. Die genaue Repositorygröße
|
||
verlangte einen Durchlauf über Millionen Blöcke — im Fünfminutentakt liefe der
|
||
Dienst nur noch damit.
|
||
- **Die Auslastung ist ein Mittelwert, keine Summe.** Zwei zu 80 % gefüllte
|
||
Repositories ergeben 80 %, nicht 160.
|
||
|
||
## Einordnung statt Schönfärberei
|
||
|
||
Jedes Diagramm bekommt einen Hinweis, wenn die Zahlen zwar stimmen, aber wenig
|
||
hergeben:
|
||
|
||
- Keine Messung im Zeitraum → *„Die leere Fläche bedeutet ‚keine Daten', nicht
|
||
‚Wert null'."*
|
||
- Weniger als drei Messungen → *„Das ist zu wenig für eine Aussage über eine
|
||
Entwicklung."*
|
||
|
||
Zwei Punkte sehen aus wie ein Trend und sind keiner.
|
||
|
||
## Darstellung
|
||
|
||
Das Liniendiagramm ist selbst geschrieben (SVG, rund 300 Zeilen). Die gängigen
|
||
Bibliotheken bringen mehr Code mit als die ganze Oberfläche — und beherrschen
|
||
die eine Eigenschaft, auf die es hier ankommt, standardmäßig falsch: Sie
|
||
zeichnen Lücken als Nullen.
|
||
|
||
- **Die Werteachse beginnt immer bei null.** Eine abgeschnittene Achse lässt
|
||
kleine Schwankungen wie Einbrüche aussehen — der häufigste Weg, mit korrekten
|
||
Zahlen etwas Falsches zu zeigen.
|
||
- **Eine einzelne Messung wird als Punkt gezeichnet.** Als Linie hätte sie die
|
||
Länge null und wäre unsichtbar; die Kurve sähe aus wie „nichts gemessen".
|
||
- **Jede Lücke trennt die Linie in einen eigenen Zug.** Geprüft durch einen
|
||
Test, der die gezeichneten Pfade auszählt.
|
||
|
||
## Endpunkte
|
||
|
||
| Methode | Pfad | Recht |
|
||
| --- | --- | --- |
|
||
| GET | `/api/v1/metrics` | `monitoring.read` |
|
||
| GET | `/api/v1/metrics/{metric}?range=7d` | `monitoring.read` |
|
||
|
||
Ein Diagramm ohne Datengrundlage antwortet mit **501**, nicht 404: Es ist
|
||
vorgesehen, es fehlt nur die Messung. Der Unterschied entscheidet, was der
|
||
Aufrufer tut — warten oder den Namen korrigieren.
|
||
|
||
```bash
|
||
curl "/api/v1/metrics" # Katalog
|
||
curl "/api/v1/metrics/backup_throughput?range=24h" # eine Reihe
|
||
curl "/api/v1/metrics/storage_growth?range=custom&from=…&to=…"
|
||
```
|
||
|
||
## Erfassung
|
||
|
||
Der Sammler läuft neben Sicherung, Wiederherstellung und Prüfung. Alle fünf
|
||
Minuten misst er Belegung und Kapazität jedes Repositorys über einen
|
||
`statfs`-Aufruf.
|
||
|
||
- **Der erste Durchgang läuft sofort**, nicht erst nach fünf Minuten. Sonst
|
||
begänne jede Verlaufsreihe verspätet, und ein kurz laufender Dienst erfasste
|
||
nie etwas.
|
||
- **Ein nicht erreichbares Repository trägt keine Null ein**, sondern gar nichts.
|
||
Eine Null ließe die Kurve auf den Boden fallen, obwohl die Daten da sind.
|
||
- **Der Messzeitpunkt wird auf die volle Minute gerundet.** Ohne diese Rundung
|
||
entstünden bei zwei gleichzeitig sammelnden Control-Servern zwei Messungen mit
|
||
Millisekundenabstand; der Eindeutigkeitsindex griffe nicht, und die Belegung
|
||
erschiene doppelt.
|
||
- **Aufbewahrung 400 Tage.** Das deckt den längsten Zeitraum von einem Jahr ab
|
||
und lässt Raum für einen Jahresvergleich. Ohne Aufräumen wüchse die Tabelle
|
||
unbegrenzt.
|
||
|
||
### NaN wird abgelehnt
|
||
|
||
Ein einziger NaN-Wert verseucht jede Summe und jeden Durchschnitt der Reihe — das
|
||
Ergebnis ist danach selbst NaN, ohne dass irgendwo ein Fehler auftaucht. Die
|
||
Prüfung lautet ausdrücklich `value <> 'NaN'` und **nicht** das übliche
|
||
`value = value`: PostgreSQL wertet `NaN = NaN` als wahr, anders als IEEE 754. Der
|
||
Standardtrick greift hier nicht — real geprüft, der erste Anlauf ließ NaN durch.
|
||
|
||
## Nachgewiesen
|
||
|
||
Gegen den laufenden Dienst mit echten Läufen:
|
||
|
||
- Katalog: 10 von 12 Diagrammen mit Datengrundlage, zwei mit Begründung.
|
||
- Alle sieben Zeiträume liefern brauchbare Punktzahlen (60 bis 180).
|
||
- Durchsatz aus einem 172-MB-Lauf: 155,8 MiB/s, mit dem Hinweis „nur eine
|
||
Messung".
|
||
- Erfolg *und* Fehlschlag in derselben Kurve: Aufträge früherer Phasen scheitern
|
||
mit `REPOSITORY_UNAVAILABLE`, weil ihre Verzeichnisse entfernt wurden.
|
||
- 501 für `agent_resources`, 404 für einen unbekannten Namen, 422 für einen
|
||
ungültigen Zeitraum.
|
||
|
||
## Bekannte Grenzen
|
||
|
||
- **Keine Verdichtung alter Messungen.** Nach 400 Tagen werden sie gelöscht statt
|
||
zusammengefasst. Bei wenigen Repositories ist das unkritisch.
|
||
- **Kein Filter je Auftrag** in den Laufreihen. Der Endpunkt kennt
|
||
`repository_id` nur für die Speicherreihen.
|
||
- **Keine Zeitachsenbeschriftung** im Diagramm. Die Zeitpunkte stehen in den
|
||
Daten, gezeichnet wird nur die Werteachse.
|
||
- **Kein automatisches Aktualisieren.** Die Seite lädt beim Öffnen und beim
|
||
Wechsel des Zeitraums.
|