Logbuch#netzwerk#monitoring#control-center
457 Datensätze verschwanden — und kein einziger Zähler schlug aus
Von Niclas Hennrich
Etwas ist ausgefallen oder kaputtgegangen. Gehört zum Vorhaben Homelab Control Center.
- verschwundene Datensätze
- 457
- Fehlermeldungen dabei
- 0
- Platzbedarf je Flow
- 504Byte
Der Aufbau: eine Pipeline, die Netzwerk-Flows empfängt, anreichert und in PostgreSQL schreibt — der Weg vom bewiesenen Collector in die Datenbank und wieder heraus. 76 neue Unit-Tests, insgesamt 476 grün. Sah gut aus.
Meine Testsuite läuft gegen In-Memory-SQLite und einen Mock. Das ist schnell und für Logik völlig ausreichend — und hat mir im selben Projekt schon einmal einen HTTP 500 in Produktion durchgelassen, weil der echte Treiber nie angefasst wurde. Seitdem gibt es einen Pflichtlauf gegen die echte Kette: Wegwerf-PostgreSQL im Container, echtes Collector-Image, echte Pakete.
Erster Fehler, sofort sichtbar. Der Consumer stürzte beim ersten Batch ab:
AttributeError: 'IteratorResult' object has no attribute 'rowcount'Ich hatte die Zahl der geschriebenen Zeilen aus result.rowcount gelesen. Bei
einem Mehrzeilen-Insert liefert SQLAlchemy je nach Dialekt ein Result ohne
dieses Attribut. Unter SQLite lief derselbe Code über einen Sonderweg, der die
Zeilen vorher und nachher zählte — deshalb waren die Tests grün.
Der Fix wurde sauberer als der Ursprung: RETURNING id. Zurück kommt genau
eine Zeile je tatsächlich eingefügtem Satz, übersprungene Duplikate fehlen
einfach. Das funktioniert in beiden Datenbanken und macht den SQLite-Sonderweg
überflüssig.
Zweiter Fehler — und der war das eigentliche Thema. Nach dem ersten Fix landeten 2 541 von 2 998 Flows in der Datenbank. 457 fehlten. Nicht verworfen, nicht doppelt, nicht abgestürzt: kein Zähler meldete irgendetwas. Die einzige Spur war der Vergleich zwischen „empfangen" und „geschrieben".
Die Ursache steckte in der Flush-Logik. Sie prüfte zwischen zwei gelesenen Datensätzen, ob das Zeitlimit für den angefangenen Batch abgelaufen war. Das kann nur greifen, solange etwas ankommt. Wurde es ruhig, blieb der Batch einfach im Speicher liegen. Der Kommentar darüber behauptete genau das Gegenteil — er beschrieb meine Absicht, nicht das Verhalten.
Vier Flows haben es bewiesen, nicht dreitausend. Bei 3 000 Flows sah alles nach Paketverlust auf dem Netzweg aus. Vier Flows senden und dann still sein: vier empfangen, null geschrieben. Eindeutig, in einem einzigen Durchgang.
Die Lösung ist eine Trennung: Ein Lese-Thread füllt eine Warteschlange, die Schreibschleife holt mit Zeitlimit daraus ab. Damit läuft die Uhr unabhängig vom Datenstrom. Danach: 3 000 gesendet, 3 000 geschrieben — und vier gesendet, vier geschrieben.
Nebenbei eine Schätzung korrigiert: 2 000 Flow-Zeilen belegen inklusive Indizes 984 KiB, also rund 504 Byte je Flow. Gerechnet hatte ich mit 250. Auf die Entscheidung hatte das keinen Einfluss, aber die Kapazitätsrechnung im Architekturdokument war um den Faktor zwei zu optimistisch.
Was ich mitnehme: Fehlende Daten ohne Fehlerzähler sind das gefährlichste Fehlerbild, das ich kenne — wer nur auf Fehlerzähler schaut, sieht sie nie. Und ein Kommentar ist kein Beweis: Über der kaputten Bedingung stand, sie verhindere genau das Problem, das sie verursachte.