Wenn dies dein erster Besuch hier ist, lies bitte zuerst die Hilfe - Häufig gestellte Fragen durch. Du musst dich vermutlich registrieren, bevor du Beiträge verfassen kannst. Klicke oben auf 'Registrieren', um den Registrierungsprozess zu starten. Du kannst auch jetzt schon Beiträge lesen. Suche dir einfach das Forum aus, das dich am meisten interessiert.
Wenn das hier soweit getestet und für gut befunden ist - jein Der wesentliche Unterbau in lib.db sollte eigentlich (TM) durch db_addon jetzt schon genutzt werden, ich habe da aber noch nicht reingeschaut. Ich würde dir Bescheid geben, wenn ich das für "an der Zeit" halte, dann können wir schauen, was ggf. angepasst werden sollte.
Ich hätte mal eine Frage, falls hier genügend Leute mitlesen, die eine Meinung bilden und äußern wollen
Im Moment ist ja das alte database_maxage von "nur löschen" zu "löschen oder aggregieren (sum, max, min, avg, int, last, ...)" geworden. Es wurde der Wunsch geäußert, dies mehrstufig zu ermöglichen, z.B. die ersten 6 Monate täglich, von 6-12 Monaten wöchentlich, und danach zweiwöchentlich.
Da wir ja mittlerweile die Möglichkeit haben (hätten - das müssten Plugins erst noch einbauen), Daten als "nicht verfügbar" zu kennzeichnen, könnten auch kurze oder längere Ausfälle in den Daten in der Datenbank gespeichert werden. Wenn jetzt aggregiert wird - z.B. avg - dann gehen diese Lücken in der Datenbasis verloren.
Gäbe es ein Interesse daran, die Rohdaten von aggregierten Daten "extern" aufzubewahren, um so etwas nachträglich nachvollziehen zu können?
Ich finde diese Option persönlich nicht notwendig - ob meine Heizungsanbindung vor 9 Monaten mal einen Tag offline war oder nicht, ist mir nach so langer Zeit wahrscheinlich egal, aber bevor ich mich endgültig für oder gegen eine entsprechende Implementierung entscheide, wollte ich mal nach Meinungen fragen.
Die mehrstufige Aggregierung zum weiteren Platzsparen auf der Platte baue ich mit ein.
Weil ich umfangreiches Feedback erhalten habe, und in der Hoffnung, dass das hier auch gelesen wird -
Es ist nicht unwahrscheinlich, dass (zumindest anfänglich) viele Warnungen oder sogar Fehler von lib.db oder plugins.database ausgeworfen werden. DAS IST NORMAL!
Warum? Weil diese Fehler in der Vergangenheit einfach geschluckt/verworfen wurden. Jetzt werden sie ausgegeben, und das kann als Anhalt genommen werden, ggf. das Datenbanksystem oder auch die eigene Datenbank zu prüfen (Leistung, Speicher, DB zu groß?).
Dazu kommt, dass nach dem Start z.B. maxage_compaction losläuft. Bei SQLite kann dies dazu führen, dass die Datenbank für längere Zeit (Stunden?!) durch maxage_compaction blockiert wird, wenn viele Daten gelöscht oder aggregiert werden müssen.
Bei MySQL/MariaDB können ein unglücklich provisionierter Datenbankserver oder eine zu große Datenbank zu häufigen Timeout-Fehlern führen. In dem Fall empfehle ich zuerst, den DB-Server zu optimieren (Speicherplatz, Plattenplatz, Swap), und dann die Datenbank zu verkleinern.
Beispiel:
Ich habe mein Swap von 8 GB (= phys. RAM) auf HDD auf 16 GB auf SSD umgestellt. Das allein war schon ein riesiger Performancegewinn.
Meine Datenbank von 19 GB (ca. 190 Mio Zeilen) auf 4 GB (32 Mio Zeilen) zu verkleinern hat dazu geführt, dass der DB-Server effektiv kein Swap mehr benötigt und den verfügbaren Speicher auch nicht mehr auslastet. Seitdem habe ich auch keine Warnungen mehr, vorher ständig Timeouts.
Entweder core und plugins auf develop (aktuell von heute) aktualisieren, da sind Fixes für alle Fehler drin, die ich bisher gefunden habe, bis zum nächsten Fixup-Release warten (im Prinzip sollte der Code soweit sein, ich möchte den vorher aber 2-4 Wochen öffentlich laufen haben), oder auf eine alte Version zurückwechseln und sich in der Ruhe im Log der Illusion hingeben, es gäbe keine Fehler (siehe mein vorheriger Post).
Ja, die Fehler sind zu viele und zu laut, dazu habe ich gerade noch einen Fix gepusht.
Sorry, bessere Möglichkeiten hast du gerade nicht (und Downgrade ist aus meiner Sicht nicht wirklich eine Möglichkeit...)
So, das - hoffentlich erstmal - finale Update für lib.db und das database-Plugin ist online. Ich habe jetzt drei Tage intensiv getestet und absurde Mengen an Daten in Datenbanken geschaufelt und wieder gelöscht, und bisher sieht das alles gut aus.
in Kürze:
- Aggregation von Daten über Item-Attribute database_maxage, database_maxage_action und database_maxage_interval (siehe hier),
- Daten als fehlend markieren, wenn die Quelle keine Daten liefert (offline, Verbindung unterbrochen) - für Plugins vorbereitet, es gibt aber noch kein Plugin, das das kann; für Items einzeln mit Item-Attribut database_invalid_after und Plugin-Attribut invalid_check_cycle (nur für Items mit cycle, crontab oder entsprechend regelmäßigen Updates),
- Unterstützung von TimescaleDB (Erweiterung von PostgreSQL) - liefert erheblich bessere Performance und deutliche Speicherplatzeinsparungen gegenüber PostgreSQL, MySQL/MariaDB und SQLite3, allerdings müssen bestimmte Randbedingungen eingehalten werden, um alle Vorteile nutzen zu können.
Da mein vorbereiteter Text hier nicht reinpasst, schreibe ich ein Blogbeitrag und verlinke den, sobald der online ist. Wer neugierig ist - die plugin.yaml vom Database-Plugin und die user_doc.rst haben viele Infos und ein paar Beispiele...
Okay, Blogpost bekomme ich auf die Schnelle nicht hin, also doch hier... 1/3:
Datenlücken statt "Wert unverändert"
Kurzer Überblick über eine Neuerung im database Plugin (develop Branch) - Datenlücken-Erkennung statt falscher "Wert unverändert"-Interpolation.
Wenn eine Datenquelle zeitweise ausfällt (offline, Verbindung nicht da) oder keine Daten liefert, behält das Item derzeit einfach seinen letzten Wert. Ohne Korrekturen wird dieser Wert für die gesamte Zeitspanne in der Datenbank gespeichert. Neben verfälschten Berechnungen haben auch Plots/Graphen dabei durchgehende Linien, obwohl ggf. keine Daten vorlagen.
Das ist ein altes Thema, über das in den Issues schon viel diskutiert wurde, und es gibt eine Lösung. Genaugenommen - zwei. db_mark_invalid() / db_mark_valid().
Code:
# Verbindung verloren
sh.solar.leistung.db_mark_invalid(caller='solar_plugin', source='connection_lost')
# Verbindung wieder da
sh.solar.leistung.db_mark_valid(caller='solar_plugin', source='connection_restored')
sh.solar.leistung(neuer_wert, 'solar_plugin')
Wichtig dabei: das passiert nicht von allein. Das jeweilige Plugin muss den Ausfall selbst erkennen und db_mark_invalid() aktiv aufrufen. Unterstützt ein Plugin das (noch) nicht, bleibt der letzte Wert wie bisher einfach unverändert stehen. Aktuell macht das noch kein Plugin von Haus aus - Gateway-Plugins wie MQTT (Verbindung zum Broker verloren) oder KNX (Bus-Timeout, knxd nicht erreichbar) wären die ersten Kandidaten, die ich in absehbarer Zeit angehen möchte. Wer Lust hat, das für sein Lieblings-Plugin nachzurüsten: immer her damit.
Neu und ohne Plugin-Unterstützung nutzbar: das Item-Attribut database_invalid_after. Bekommt ein Item über die angegebene Zeitspanne keine Änderung/kein Update, wird automatisch eine Lücke geöffnet:
Code:
solar:
leistung:
type: num
database: init
enforce_updates: true
database_invalid_after: 10m
enforce_updates muss gesetzt sein, sonst zählt ein unverändertes Update nicht als Lebenszeichen. Und: das ist nur für Items sinnvoll, die ohnehin regelmäßig aktualisiert werden (cycle/crontab o.ä.) - bei unregelmäßig/ereignisgesteuert aktualisierten Items gibt's sonst nur Fehlalarme.
Sobald wieder ein Wert gesendet wurde, wird automatisch db_mark_valid() aufgerufen und die Werte "zählen" wieder.
Lückeneinträge werden bei avg/sum/integrate/on/min/max automatisch ausgeschlossen, tauchen bei Rohwertabfragen als NULL (aus der Datenbank) auf (für Lücken in Plots), und lassen sich im Webinterface pro Datensatz auch manuell setzen/zurücknehmen.
Beispiel: KNX-Item und MQTT-Item
Zwei Items, zwei Transportwege, aber beide mit demselben eigentlichen Risiko: die Verbindung zur Außenwelt kann abreißen, ohne dass shng selbst oder das jeweilige Plugin davon groß Notiz nimmt - Telegramme bzw. Nachrichten bleiben einfach aus:
heizung.vorlauftemperatur kommt vom Sensor selbst zyklisch (in ETS am Gerät eingestellt, z.B. alle 5 Minuten senden, unabhängig von einer Wertänderung) - komplett unabhängig von shngs eigenem Scheduler. Fällt knxd aus oder bricht die Verbindung zum Bus ab, bleiben die Telegramme einfach aus. Das KNX-Plugin selbst muss dabei nicht abstürzen oder für dieses eine Item einen auffälligen Fehler werfen - andere Items mit zwischenzeitlich gecachten Werten sehen auf den ersten Blick unauffällig aus. Genau diesen Fall fängt database_invalid_after ab, ohne dass jemand ins Log schauen muss.
garage.aussentemperatur kommt per MQTT rein, mit demselben Prinzip: viele Sensoren (Zigbee2MQTT, Tasmota, ...) senden zyklisch, unabhängig davon ob sich der Wert geändert hat - hier alle 60s angenommen, daher 3m als Schwelle. Bleibt der Sensor tot oder der Broker weg, bleibt auch das MQTT-Update aus, ohne dass shng selbst etwas falsch macht.
Ein rein shng-internes cycle+eval-Item wäre dagegen kein gutes Beispiel: solange shng überhaupt läuft, feuert dessen Scheduler garantiert, und stünde der Scheduler wirklich still, würde auch die Prüfung selbst (invalid_check_cycle) nicht mehr laufen - es gäbe also niemanden mehr, der Alarm schlagen könnte.
Der Haken liegt nicht bei MQTT als Transportweg, sondern bei Quellen, die nur bei Zustandsänderung senden - klassisch Tür-/Fensterkontakte, viele Taster. Da ist "seit 30 Minuten kein Update" der absolute Normalfall, wenn eben nichts passiert ist, kein Ausfall. database_invalid_after ist nur sinnvoll, wenn die Quelle nachweislich regelmäßig sendet - egal ob per MQTT, KNX oder sonstwie -, und die Schwelle muss zu genau diesem Intervall passen, nicht zu einer geschätzten "sollte doch mal was kommen"-Zeit.
Das Risiko bei Fehlkonfiguration - falscher Itemtyp ohne regelmäßige Updates, oder zu kurz gewählte database_invalid_after-Zeit - sind Zeiträume, die in der Datenbank als "nicht vorhanden" eingetragen sind, obwohl eigentlich ein gültiger Wert vorlag. Das fällt seinerseits nur auf, wenn man es regelmäßig auf Plots überprüft.
Aggregierung statt Löschen - die Optionen im Überblick
Kurzer Überblick über database_maxage_action im database Plugin (develop Branch) - kompaktieren statt löschen, mit einem durchgerechneten Beispiel.
database_maxage ist ein bekanntes Item-Attribut und löscht alte Werte ersatzlos. Über database_maxage_action lässt sich stattdessen ein "Ersatzwert" zu einem Intervall verdichten:
delete - löschen (Standard), beliebiger Typ
avg - zeitgewichteter Mittelwert, num/bool
sum - Summe, num/bool
min / max - Minimal-/Maximalwert, num/bool
integrate - diskretes Integral über der Zeit, num/bool
countall - Anzahl Rohwerte im Intervall, beliebiger Typ
first / last - ältester/neuester Rohwert, unverändert, beliebiger Typ, auch str
Das läuft komplett Python-seitig im Plugin, identisch auf SQLite3, MySQL/MariaDB und PostgreSQL. Wer zusätzlich zum Mittelwert auch Min/Max eines Intervalls dauerhaft behalten will, kann dafür die mitgelieferten Structs database.min/database.max nutzen (legen automatisch eigene Kind-Items mit den jeweiligen Werten an).
Beispiel: vier Items, vier verschiedene Konfigurationen
In den Konfigurationen fehlen die eigentlichen Datenquellen, die darf sich jeder dazu denken.
wohnzimmer.temperatur: nach 90 Tagen werden die Rohwerte je Stunde zu einem Mittelwert verdichtet - Langzeitverlauf bleibt Jahre erhalten, die minütlichen Rohdaten nicht.
pv.leistung: nach einem Jahr wird aus der Momentanleistung je Tag die tatsächlich erzeugte Energie integriert - klassischer Fall für integrate.
haustuer.kontakt: setzt kein eigenes database_maxage_interval - fällt auf den Plugin-Parameter default_maxage_interval: 24h zurück. Nach 30 Tagen bleibt pro Tag nur noch die Anzahl der Türöffnungen übrig.
heizung.status: ein str-Item - hier funktionieren avg/sum/etc. nicht, aber last schon. Nach 14 Tagen bleibt je 6h-Intervall nur noch der letzte bekannte Status übrig.
Effekt in der Praxis: Die Kompaktierung läuft im selben Zyklus wie das bisherige Löschen (removeold_cycle) und arbeitet sich - genau wie das - in begrenzten Schritten durch alte Daten (max_aggregate_intervals), pro Item. Bei vier Items mit doch recht unterschiedlichem Alter blockiert also keines der anderen den Durchlauf.
Ein Punkt, den man im Hinterkopf behalten sollte: Ändert man später bei einem Item mit countall (hier haustuer.kontakt) nachträglich das database_maxage_interval, werden schon kompaktierte alte Intervalle beim nächsten Durchlauf mit in ein neues, anders breites Intervall gezogen - für avg/sum/min/max/integrate/duty_cycle rechnerisch unproblematisch, bei countall zählt ein bereits kompaktiertes Intervall dann aber nur noch als "1" statt als die ursprüngliche Anzahl Rohwerte. Kein Bug, nur eine Ungenauigkeit, die man bei countall + nachträglicher Intervalländerung kennen sollte.
Kurzer Überblick über die neue PostgreSQL+TimescaleDB Unterstützung im database Plugin (develop Branch), inklusive Vor-/Nachteilen und einem Beispiel.
Neben SQLite3 und MySQL/MariaDB unterstützt das Plugin jetzt auch PostgreSQL, optional mit der TimescaleDB-Erweiterung. Die Treiber-Auswahl ist jetzt auch komfortabler: driver akzeptiert neben den echten Modulnamen (psycopg2/psycopg) auch sprechende Namen wie postgres, postgresql, timescale, timescaledb (und mysql/mariadb statt pymysql) - welches Python-Modul installiert ist, wird automatisch erkannt.
Hypertables - die log-Tabelle wird automatisch in Zeitpartitionen (Dateien für einzelne Zeiträume) aufgeteilt, macht zeitbereichsbasierte Abfragen auf großen Tabellen deutlich schneller.
Native Kompression (timescale_compress: true) - TimescaleDBs eigene Doku nennt 10-20x (90-95% Speicherplatzersparnis) als üblich für Zeitreihendaten; im eigenen Test gegen einen echten 22-Millionen-Zeilen-Datensatz kamen 17,39x raus.
Native Aggregation (timescale_native_aggregation: true) - statt Python-seitiger Aggregierung übernimmt TimescaleDB das über Continuous Aggregates, direkt auf dem Server. Dieselben Item-Attribute (database_maxage_action etc.) steuern das weiterhin.
Native Retention (timescale_native_retention: true) - alte Rohdaten-Zeitpartitionen werden automatisch server-seitig fallengelassen statt Item für Item gelöscht.
Und die Nachteile, ehrlich gesagt:
Mehr Aufwand als SQLite3 (extra Server, extra Python-Paket psycopg2-binary oder psycopg).
Native Retention ist ein globaler Schwellwert für die ganze Tabelle, kein Wert pro Item mehr - auch Items mit database_maxage_action: delete warten dann auf denselben globalen Schwellwert.
Wichtig: Items ohne database_maxage sind davon nicht ausgenommen. ALLE Items werden nach der längsten eingestellten Zeit aggregiert - oder gelöscht. Wenn keine default_maxage_action eingestellt ist, werden alle nicht ausdrücklich anders konfigurierten Datenbankwerte der Items gelöscht. Wer native Retention nutzt, sollte default_maxage und default_maxage_action setzen.
Der Wechsel von nativer Aggregation zurück zu Plugin-Aggregierung ist nicht vorgesehen (nicht implementiert, da alles andere als trivial) und nicht verlustfrei möglich.
Auch die einmal aktivierte Kompression lässt sich über das Plugin nicht mehr rückgängig machen.
Warum gerade TimescaleDB
Es gibt schon ein InfluxDB-Plugin, und Influx gilt seit Langem als die Referenz für Zeitreihen-Datenbanken. Das Problem mit InfluxDB ist dabei zweierlei: zum Einen hat sich die API mit jeder größeren Version komplett geändert, was ständigen Updateaufwand mit sich bringt und für jede Version ein eigenes Plugin erfordern würde. Zum Anderen ist die aktuelle Open-Source-Linie (InfluxDB 3 Core) explizit als Edge-/Echtzeit-Collector positioniert - Compactor und Optimierungen für Langzeit-Speicherung fehlen, und genau die wären für unseren Stats-/Grafik-Anwendungsfall am wichtigsten. Für brauchbare Abfrage-Performance über die Historie braucht es InfluxDB Enterprise. Es gibt zwar eine kostenlose Hobby-Stufe, aber damit hängt man an einer Hersteller-Lizenzstufe statt an einer eigenständigen Open-Source-Installation - für uns als reines Community-Projekt nicht ideal.
TimescaleDB basiert auf PostgreSQL, einer seit Langem gepflegten, gut getesteten und leistungsfähigen Datenbank, die auf den meisten Systemen problemlos verfügbar ist. Die TimescaleDB-Erweiterung bringt mit der nativen Kompression einen großen Platzvorteil mit (siehe oben, 10-20x laut Timescale, 17,39x im eigenen Test), und die nativen Aggregations- und Retentionsfunktionen entlasten SmartHomeNG von Routineaufgaben, die sonst Python-seitig laufen müssten.
Was jetzt passiert: TimescaleDB legt ein Continuous Aggregate je tatsächlich genutzter Intervallbreite an, nicht eines je Item. Die Continuous Aggregates werden intern als virtuelle Tabelle über der log-Tabelle eingeblendet. Bei den vier Beispiel-Items sind das genau drei: 1h (wohnzimmer.temperatur), 6h (heizung.status) und 1d (pv.leistung und haustuer.kontakt über den default_maxage_interval-Fallback teilen sich hier eines, weil beide auf dieselbe Intervallbreite kommen). Die Aggregation selbst läuft weiter pro Item korrekt nach dessen eigener Konfiguration.
Der eigentliche Unterschied, konkret an diesem Beispiel: pv.leistung hat mit 365 Tagen das mit Abstand längste database_maxage. Unter nativer Retention wird genau dieser Wert (plus eine Zeitpartition Sicherheitsspanne) zum einzigen Schwellwert für die gesamte Tabelle. Die Rohdaten von haustuer.kontakt - eigentlich für 30 Tage konfiguriert - bleiben deswegen faktisch bis zu ein Jahr lang liegen, weil TimescaleDB pro Zeitpartition löscht und diese Item-übergreifend geteilt werden. Ohne native Aggregation/Retention (die übliche Python-seitige Kompaktierung) würde haustuer.kontakt dagegen zuverlässig nach 30 Tagen kompaktiert. Das ist genau der "globaler statt Item-genauer Schwellwert"-Nachteil von oben, nur diesmal mit echten Zahlen dahinter. Durch die Kompression schrumpft der Platzbedarf für die eigentlich nicht erforderlichen Daten so weit, dass der zusätzliche Speicherplatz in der Praxis kein Problem darstellt. Trotzdem lässt sich dieses Feature optimal nutzen, wenn database_maxage bei allen Items in derselben Größenordnung liegt.
Für die meisten Home-Installationen reicht SQLite3 grundsätzlich aus. Da ich aber gehört habe, wie viele Millionen Zeilen viele Leute im Forum in ihren Datenbanken haben, wird TimescaleDB schon wieder interessant - und sei es nur aufgrund der Kompression, selbst ohne native Aggregation/Retention.
Migration von Datenbanken
Wer bestehende Daten zwischen Backends umziehen will (z.B. SQLite3 → TimescaleDB, oder umgekehrt): dafür gibt's jetzt tools/db_migrate.py als eigenständiges Skript (läuft nur bei gestopptem SmartHomeNG). Migriert Item- und Log-Daten direkt zwischen zwei beliebigen unterstützten Backends, unterstützt Resume und --dry-run. Aber vorsicht - selbst auf einem schnellen Rechner sollte man für 22 Millionen Zeilen schon gute drei Stunden reine Transferzeit einplanen.
Wir verarbeiten personenbezogene Daten über die Nutzer unserer Website mithilfe von Cookies und anderen Technologien, um unsere Dienste bereitzustellen. Weitere Informationen findest Du in unserer Datenschutzerklärung.
Indem Du unten auf "ICH stimme zu" klickst, stimmst Du unserer Datenschutzerklärung und unseren persönlichen Datenverarbeitungs- und Cookie-Praktiken zu, wie darin beschrieben. Du erkennst außerdem an, dass dieses Forum möglicherweise außerhalb Deines Landes gehostet wird und bist damit einverstanden, dass Deine Daten in dem Land, in dem dieses Forum gehostet wird, gesammelt, gespeichert und verarbeitet werden.
Kommentar