Beim Testen der neuen Aktivitätsansicht in der Version 4.82 stießen wir auf etwas Unerwartetes — und das führte zu drei sich ergänzenden PRs. Ein riesiges Dankeschön an @Will_71 für die Tests an seiner eigenen Datenbank!
Episode 1 — der seit 2 Jahren unsichtbare Bug (PR #2650)
Beim Versuch, die Zustände eines redseligen Sensors zu löschen (Option „Verlauf speichern“ deaktiviert), antwortete die Aufgabe sofort: 0 states to delete… obwohl die Aktivitätsansicht tausende Zustände für dieses Feature anzeigte.
Urteil nach einer git-Archäologie:
Frage
Antwort
Seit wann kaputt
v4.45.0 (August 2024)
Ursache
Die Migration zu DuckDB (#2104) hat die Zustände verschoben, aber 2 SQLite-Abfragen wurden vergessen
Ort 1
purgeStatesByFeatureId (der Schalter „Verlauf speichern“) zählte und löschte in SQLite, das nun leer ist → komplette No-Op
Ort 2
Der Schutzmechanismus von device.destroy („zu viele Zustände“) zählte ebenfalls in SQLite → tote Schutzfunktion gegen Blockierung
Warum unsichtbar
Nichts zeigte den Verlauf pro Feature an… bis zur Aktivitätsansicht
Die #2650 migriert die beiden zu DuckDB (dieselbe Abfrage, die destroy bereits verwendete) und behält die Bereinigung der SQLite-Reste für noch nicht migrierte Installationen bei.
Episode 2 — wie viele Waisen-Zustände haben Sie zu Hause? (PR #2651)
Folge des Bugs: Jedes Device/Feature, das in den letzten 2 Jahren gelöscht wurde, konnte seine „Waisen“-Zustände in DuckDB zurücklassen — unsichtbar, aber von jeder Abfrage gescannt und Speicherplatz belegend.
Auf Vorschlag von @pierre-gilles: eine automatische Einmal-Aufgabe beim Starten (Migrationsmuster von DuckDB: Systemvariable wird nur am Ende des Runs gesetzt → wenn Gladys mitten im Prozess neu startet, startet sie beim nächsten Boot automatisch — getestet, indem der Container mitten im Prozess abgeschaltet wurde ).
Und „so langsam wie möglich“: Keine vorherige Zählung (die 15-20 Minuten die Leseverbindung blockierte auf meiner Datenbank), Durchlauf in wöchentlichen Abschnitten mit einer Pause von 5× der Dauer jedes Abschnitts — die Bereinigung verbraucht nie mehr als einen Bruchteil der Ressourcen und passt sich selbst an die Maschine an.
Feldtest
Datenbank
Gefundene Waisen
Dauer
Wahrgenommene Auswirkungen
Ich (geteilter Server mit HA + 2. Gladys)
448 M Zustände
20
1 h 49 (kalter Cache) / 29 min (warm)
Aktivitätsansicht: 125 ms während der Bereinigung (gegenüber 44 s mit der ersten, ungebremsten Version)
Noch einmal ein großes Dankeschön an @Will_71 für die vollständigen Tests der 2 PRs
Episode 3 — die Aufgaben-Seite, die den Bug in einem Tag entdeckt hätte (PR #2652)
Wenn die Aufgaben-Seite „0 Zustände gefunden“ gegenüber einer vollen Aktivität angezeigt hätte, wäre der Lösch-Bug nicht 2 Jahre überlebt. Das ist jetzt der Fall — die Aufgaben haben strukturierte Daten (übersetzt auf der Frontend-Seite, also in allen Sprachen):
Ziel: Bewegungsmelder Küche › Bewegungserkennung
Live-Schritte: Warten auf die Datenbank… / Zählen der Anzahl der Zustände… / Löschen der Zustände… / Löschen der Aggregationen… (zwei gleichzeitige Löschvorgänge zeigen ehrlich an, welche arbeitet und welche wartet)
Zählungen: Zu löschende Zustände: N DuckDB + N SQLite — N Aggregationen, dann Gelöschte Zustände: … im dauerhaften Bericht
Live-Zähler für die Bereinigung von Waisen, % pro Abschnitten, tickende Dauer für laufende Aufgaben
Das Design wurde durch die Diskussionen in der #2528 validiert: keine Änderung des Job-Wrappers — die Aufgaben werden weiterhin durch das bestehende Ereignismuster gestartet, und jeder Job hängt seine Daten über einen optionalen Parameter von updateProgress an.
Für die Überprüfung
@pierre-gilles die 3 PRs sind bereit (Tests + 100 % Abdeckung auf den geänderten Zeilen, grüne CI, validiert in der Praxis auf zwei Installationen). Merge-Reihenfolge: #2650 → #2651 → #2652 (die letzte ist auf die beiden anderen gestapelt, ihr Diff wird nach deren Merges schmaler).
CodeRabbit hatte recht mit dem kritischen Punkt: Der letzte unbeschränkte Abschnitt bewertete die während der Löschung geschriebenen Zustände gegen eine statische Momentaufnahme der Features — ein gepaartes Gerät während der ~30 Minuten Löschung konnte seine ersten Zustände verlieren. Behoben mit einem Cutoff, der vor der Momentaufnahme erfasst wurde, angewendet auf alle Abschnitte, + Regressions-Test. Der fehlende Test des „Null-Feature“-Zweigs wurde ebenfalls hinzugefügt; die SQL-Injection-Warnung ist ein falscher Positivfall (nur Platzhalter), darauf in der PR geantwortet.
Teilweise mein Fehler, geteilt mit Fable 5 in diesem Fall: Zwei für sich genommen vernünftige Entscheidungen (letzter offener Abschnitt, um während des Laufs geschriebene Zustände abzudecken + Feature-Snapshot zu Beginn) wurden in Kombination gefährlich — und ich hatte keine dedizierte Review-Runde am Ende der Entwicklung mit Fable eingeleitet. Gutes Beispiel dafür, dass die Cross-Review von Bot + Agent + Mensch unterschiedliche blinde Flecken aufdeckt