Page:
Statistik Analyse
No results
Table of Contents
- Analyse: Statistik-Funktion (Mails pro Tag / Gefiltertes)
- Was die Funktion soll
- Architektur (Kurzüberblick)
- Gefundene Probleme
- 1. Kein Stundenwechsel in den In-Memory-Zählern (Hauptursache)
- 2. Doppelte Schreibpfade für Filter-Matches
- 3. „Neue Mails“ nur über In-Memory + Stundenspeicherung
- 4. Race Conditions (bereits in LOGGING_PROBLEME.md)
- 5. Nach Report: komplette Löschung des Tages
- Warum „Mails pro Tag“ und „was gefiltert wurde“ falsch sind
- Vorschläge zur Behebung (ohne Code zu ändern)
- Option A: In der bestehenden Architektur bleiben (minimale konzeptionelle Änderung)
- Option B: Statistik nur noch tagesbasiert
- Option C: Event-basierte Statistik (Architektur-Anpassung)
- Empfehlung / Umsetzung
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.
Analyse: Statistik-Funktion (Mails pro Tag / Gefiltertes)
Was die Funktion soll
- Zählen, wie viele Mails pro Tag angekommen sind (pro Account/Ordner).
- Zählen, was gefiltert wurde (Filter-Matches, Aktionen, ggf. fehlgeschlagen).
- Diese Werte im täglichen Report anzeigen und dafür persistent speichern.
Architektur (Kurzüberblick)
- Speicher: Im Scheduler laufen in-memory Zähler (
_new_mails_counts,_filter_match_counts,_action_stats, …). - Persistenz: SQLite-Tabelle
statisticsmit Schlüssel(date_hour, account_name, stat_type, stat_key)– also pro Stunde. - Laden: Beim Start wird nur die aktuelle Stunde aus der DB geladen (
_load_statistics_from_db()mitnow). - Speichern: In festem Intervall (
save_interval_hours, z.B. 1h) werden die aktuellen In-Memory-Zähler in die DB geschrieben – und zwar nur für die aktuelle Stunde (save_statistics(now, stats)), mit Überschreiben (UPDATE SET count = ?), nicht Inkrement. - Tagesreport:
load_statistics_for_day(today)aggregiert alle Zeilen mitdate_hour LIKE 'YYYY-MM-DD%'(Summe pro Typ/Account/Key). Anschließend werden diese Werte mit den In-Memory-Zählern zusammengeführt (Maximum), Report versendet, dann alle Tages-Statistiken (Speicher + DB) zurückgesetzt.
Gefundene Probleme
1. Kein Stundenwechsel in den In-Memory-Zählern (Hauptursache)
- Die Zähler
_new_mails_counts,_filter_match_counts,_action_statsusw. werden niemals beim Wechsel der Stunde zurückgesetzt. - Es gibt keinen Job oder keine Logik, die zur vollen Stunde die Zähler für die neue Stunde „startet“ und die alte Stunde nur noch in der DB hält.
Folge:
- Ab 14:00 Uhr laufen z.B. 10 neue Mails und 5 Filter-Matches.
- Um 14:15 wird gespeichert → Zeile
2025-02-24 14:00bekommt 10 neue Mails, 5 Matches. Korrekt. - Ab 15:00 Uhr kommen 3 weitere Mails, 2 Matches. In-Memory steht jetzt 13 neue Mails, 7 Matches (weil nie zurückgesetzt).
- Um 15:15 wird gespeichert → Zeile
2025-02-24 15:00wird mit 13 und 7 überschrieben.- Die 15:00-Stunde in der DB ist damit falsch (zeigt 13/7 statt 3/2).
- Die 14:00-Stunde wird nach 15:00 nie mehr aktualisiert – Mails, die zwischen 14:15 und 15:00 ankamen, fehlen in der 14:00-Zeile.
Konsequenz: Tagesaggregat (Summe über alle Stunden des Tages) ist falsch: eine Stunde ist überbewertet, die andere unterbewertet; bei Neustarts oder Report-Zeitpunkt wirkt sich das direkt auf „Mails pro Tag“ und „gefiltert“ aus.
2. Doppelte Schreibpfade für Filter-Matches
- Bei jedem Filter-Match:
- In-Memory:
_filter_match_counts[filter_key] += 1 - DB:
increment_statistic(now, 'filter_match', rule_name, account_name)(atomares +1 für die aktuelle Stunde).
- In-Memory:
- Beim periodischen Speichern:
save_statistics(now, stats)überschreibt die Zeile der aktuellen Stunde mit dem In-Memory-Stand.
Damit gilt für die aktuelle Stunde: Zuerst korrekte Inkremente in der DB, dann ein einziges Überschreiben mit dem (stundenübergreifenden) In-Memory-Wert. Das Überschreiben dominiert und führt wieder auf das gleiche Problem wie unter 1: In-Memory enthält mehrere Stunden.
3. „Neue Mails“ nur über In-Memory + Stundenspeicherung
_new_mails_countswird nur im Scheduler erhöht und nicht perincrement_statistic()geschrieben.- „Neue Mails“ landen also nur in der DB, wenn
_save_statistics_to_db()für genau diese Stunde läuft – und dann mit dem gemischten Wert (alle Stunden seit Start/Report), siehe 1.
Folge:
- Wenn der Prozess z.B. um 15:30 startet und bis 16:15 keine Speicherung ausgeführt wird, sind alle in dieser Zeit gezählten neuen Mails nur im Speicher und der 15:00-Slot wurde nie geschrieben, der 16:00-Slot erst beim nächsten Save – wieder mit falscher Stundenzuordnung.
4. Race Conditions (bereits in LOGGING_PROBLEME.md)
- Report-Job und Account-Jobs laufen in verschiedenen Threads.
- Es gibt zwar
_stats_lockbeim Lesen/Schreiben der Zähler und beim Zurücksetzen – wenn der Report aber genau zwischen Lock-Freigabe und nächster Erhöhung zurücksetzt, können theoretisch noch Verluste entstehen. Das ist gegenüber dem Stunden-Bug eher zweitrangig, aber für „funktioniert nicht richtig“ mitzudenken.
5. Nach Report: komplette Löschung des Tages
- Nach dem Versand wird
clear_statistics_for_day(today)aufgerufen und alle In-Memory-Statistiken gelöscht. - Das ist für „Report pro Tag“ sinnvoll. Problematisch ist nur: Weil die gespeicherten Stundendaten schon falsch sind (siehe 1–3), ist auch das, was vor dem Löschen im Report stand, fehlerbehaftet.
Warum „Mails pro Tag“ und „was gefiltert wurde“ falsch sind
- Pro Tag: Die Tagesanzahl ist die Summe über
date_hour LIKE 'YYYY-MM-DD%'. Da die Stundenzeilen teils zu hoch (gemischte Stunden) und teils zu niedrig (nicht aktualisierte vorherige Stunde) sind, ist die Tages-Summe systematisch verfälscht. - Was gefiltert wurde: Filter-Matches und Aktionen hängen an denselben Zählern und derselben Speicherlogik → gleiche Verzerrung.
Vorschläge zur Behebung (ohne Code zu ändern)
Option A: In der bestehenden Architektur bleiben (minimale konzeptionelle Änderung)
- Stunden-Granularität konsequent umsetzen:
- Zur vollen Stunde (z.B. per Cron-Job oder Scheduler-Job auf
:00) die aktuellen In-Memory-Zähler für die gerade abgelaufene Stunde in die DB schreiben (eine Zeile pro Stunde), danach die In-Memory-Zähler für die Statistik auf 0 setzen (oder einen klaren „Stunden-Bucket“ wechseln). - Beim periodischen Save (z.B. alle 1h) nur noch die aktuelle Stunde schreiben/aktualisieren, und zwar mit Zählern, die nur diese Stunde betreffen (also nach Stundenwechsel zurückgesetzt).
- Zur vollen Stunde (z.B. per Cron-Job oder Scheduler-Job auf
- Konsequenz: Ein einziger „Stundenwechsel“-Punkt (z.B. jede volle Stunde): „Speichere aktuellen Stand unter
date_hour = letzte Stunde, setze Zähler für Statistik auf 0.“ Dann ist jede Zeile in der DB genau einer Stunde zugeordnet, undload_statistics_for_dayliefert korrekte Tages-Summen.
Option B: Statistik nur noch tagesbasiert
- Vereinfachung: Keine stündlichen Zeilen mehr; nur noch ein Datum (Tag).
- Zähler im Speicher bleiben „pro Tag“ (z.B. Key
(date, account_name, stat_type, stat_key)oder ein Tages-Datum im StateManager). Beim Speichern immer für heute schreiben (UPSERT pro Tag, nicht pro Stunde). Beim Report: Werte für heute laden, mit In-Memory zusammenführen, Report, dann zurücksetzen. - Vorteil: Kein Stundenwechsel nötig; weniger Fehlerquellen; Report braucht keine stündliche Aggregation.
- Nachteil: Keine stündlichen Auswertungen mehr (falls gewünscht).
Option C: Event-basierte Statistik (Architektur-Anpassung)
- Jedes relevante Ereignis („neue Mail gesehen“, „Filter gematcht“, „Aktion ausgeführt“) wird einzeln in der DB festgehalten (z.B. eine Zeile pro Event mit Zeitstempel oder zumindest Datum+Stunde).
- Tages- (und ggf. Stunden-)Statistiken werden nur noch aus diesen Events aggregiert (COUNT/SUM per Tag/Stunde). Keine „laufenden“ In-Memory-Gesamtzähler mehr, die über Stunden hinweg gemischt werden.
- Vorteil: Keine Race zwischen „Stunde wechseln“ und „Zähler zurücksetzen“; Neustart verliert nur die noch nicht persistierten Events (optional: sofortiges Schreiben oder kurzer Buffer). Korrekte Nachauswertung pro Tag/Stunde möglich.
- Nachteil: Mehr Schreibzugriffe, evtl. Tabelle wächst; Aggregation für Report muss sauber definiert werden (z.B. Indizes auf Datum/Stunde).
Empfehlung / Umsetzung
- Umgesetzt: Option C (event-basierte Statistik). Siehe Code:
- StateManager: Tabelle
statistics_events(occurred_at, stat_type, stat_key, account_name),record_stat_event(),aggregate_statistics_for_day(),clear_statistics_events_for_day(),cleanup_old_statistics_events(retention_days). - Scheduler: Jedes relevante Ereignis (neue Mail, Filter-Match, Aktion, Aktion fehlgeschlagen) wird per
record_stat_event()geschrieben. Täglicher Report liest nur nochload_statistics_for_day()(Aggregation aus Events). Nach Report:clear_statistics_for_day()und Zurücksetzen von_filter_action_counts/_action_log. Kein periodisches „Statistiken speichern“ mehr; stattdessen Job zur Bereinigung alter Events (Retention, z.B. 90 Tage, konfigurierbar untergeneral.statistics.retention_days). - CLI:
clear-statistics --day/--hour/--today/--allarbeitet weiterhin; löscht jetzt Events in derstatistics_events-Tabelle.
- StateManager: Tabelle