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