syncova-backup/docs/metrics.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

197 lines
8.7 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.

# 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.