docs(savepoint): v3.20 - die Bremse war das Problem, nicht die Last
Ampel / ampel (push) Successful in 31s

SAVEPOINT v3.20 mit den Messwerten: /jobs und /capabilities byteweise identisch
ueber zwanzig Sekunden (es lud also nichts neu), 97 Antworten mit HTTP 429 im
nginx-Log, 812 von 876 Anfragen scheinbar von einer IP, und die Rechnung, die
zeigt warum: ein offener Tab braucht 123 Anfragen/min, erlaubt waren 100.

Dazu drei neue Lehren in AGENTS.md:
- Ein verpasster Abruf ist keine Nachricht ueber die Welt (`catch(() => [])`).
- Eine eigene Schutzbremse gegen die eigene Last rechnen - und jedes Greifen
  protokollieren, sonst ist sie unsichtbar.
- Eine geschluckte Warnung ist eine Falle (`|| echo` in einem 200-Zeilen-Log).
- Und: wenn der Commander eine Korrelation nennt, ist das eine Spur, auch wenn
  seine vermutete Erklaerung daneben liegt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-26 17:06:56 +02:00
parent 9156e8a4a9
commit 163216a68e
2 changed files with 184 additions and 1 deletions
+39
View File
@@ -87,6 +87,11 @@ Bibliotheks-APIs: `--help`/Doku prüfen und die Fundstelle im Commit nennen.
einstellbar), Auswurf wirkt wirklich (MakeMKV verriegelt die Tür), externer
Worker mit Verwaltungsfenster/Deinstaller/Slots, Schlüssel-Automatik für 4K,
Mount-Wiederanbindung 202 s → 8 s, Weitergabe an einem frischen Klon geprüft
-**Etappe 24 (v3.20):** Rippy bremste sich selbst aus — das Rate-Limit lag
unter der eigenen Last (100/min gegen 123/min), hinter dem Proxy teilten alle
Clients einen Eimer, und ein abgewiesener Abruf leerte das UI. Dazu: der
„Neu"-Knopf kennt jetzt die Phase (Rip oder Kompression) und fragt, wo er es
nicht weiß; zwei Fehler im Deploy-Weg behoben
- 📝 **Details immer in SAVEPOINT.md** — diese Sektion nennt nur die Etappe
## Was diese Sitzungen wiederholt gekostet hat
@@ -131,3 +136,37 @@ Kind-Prozess lässt sich abbrechen: `timeout N ls -d <pfad>` (Muster in
`mounts.ist_erreichbar` und `rohdaten.verzeichnis_da`). Und „konnte nicht
nachsehen" ist etwas anderes als „ist nicht da" — beides zu vermischen erzeugt
falsche Aussagen im UI.
**Ein verpasster Abruf ist keine Nachricht über die Welt** (26.07.2026). Im UI
stand fünfmal `catch(() => [])`: Jeder fehlgeschlagene Abruf hieß damit „es gibt
keine Jobs, keine Laufwerke, keine Ablagen" — die Liste leerte sich für einen
Takt und füllte sich vier Sekunden später wieder. Der Commander meldete das als
„wird oft neu geladen", und die Ursache war unsichtbar, weil der Fehlerzweig
nichts protokollierte. Wer nichts Neues weiß, behält, was er wusste: bei
Fehlschlag `null` und den alten Stand stehen lassen — nie einen leeren Wert, der
als Aussage gelesen wird.
**Eine eigene Schutzbremse gegen die eigene Last rechnen** (26.07.2026). Das
Rate-Limit stand auf 100 Anfragen/min, während ein einziger offener Tab 111/min
verursacht (Dashboard 75 + Log-Kasten 24 + Laufwerke 12). Rippy bremste sich
also permanent selbst aus, und niemand sah es: Der 429 stand in keinem Log, und
das UI verbuchte ihn als Leermeldung. Dazu der zweite Fehler — hinter einem
Reverse-Proxy ist `request.client.host` IMMER der Proxy, also hatten Browser,
zweiter Tab und Windows-Tray EINEN gemeinsamen Eimer (812 von 876 Anfragen kamen
scheinbar von einer IP). Wer eine Grenze setzt, rechnet die eigene Grundlast vor,
schreibt sie als Kommentar dazu und lässt jedes Greifen protokollieren.
**Eine geschluckte Warnung ist eine Falle** (26.07.2026). `cp "$ENV_SRC" .env
2>/dev/null || echo "WARNUNG: …"` scheiterte auf der Ziel-VM bei JEDEM Deploy,
weil die .env dort anders lag. Die Zeile scrollte im Build-Rauschen vorbei,
gebaut wurde still mit einer zwei Tage alten Kopie — mit einem toten
Download-Notbehelf darin, an dem jeder worker-Build abbrach. Entweder abbrechen
oder so laut werden, dass es nicht zu übersehen ist (Dateidatum, Kandidatenliste);
ein `|| echo` in einem 200-Zeilen-Log ist keins von beidem.
**Wenn der Commander eine Korrelation nennt, ist das eine Spur.** „Wenn der
Worker installiert ist, wird der Bereich oft neu geladen" klang nach Bauchgefühl
und war exakt richtig: `tray.py` fragt `/api/jobs` über Port 80, landet damit im
Rate-Limit-Eimer des Browsers und drückt ihn über die Grenze. Dieselbe Lehre wie
bei „auf Windows ginge das sofort" (Disc-Schlüssel) — die Beobachtung ernst
nehmen, auch wenn die vermutete Erklärung („Celery-Ping?") daneben liegt.