NEWS
[Frage] Asynchrone Scripts = Datenverlust?
-
Liege ich vorsichtig richtig, dass aus der dem Inhalt des Threads https://forum.iobroker.net/viewtopic.php?t=6175#p63581 aus 2017 bislang noch nichts entstanden ist?
Anweisungen wie
createStateSync setObjectSync extendObjectSync ... sleep
scheinen allgemein noch ziemlich unbekannt zu sein. Aus diesem Grund mache ich mir Sorgen um den folgenden Scriptansatz:
on({id: 'adapter.0.testVariable', change: "ne"}, function (obj) { try { obj = JSON.parse(getState('adapter.0.testVariable').val); // <-- ist hier immer noch das gleiche drin, wie beim Triggermoment oben? } catch (e) { console.error(e.message + ' --- Fehler beim JSON-parsen: ' + getState('adapter.0.testVariable').val); return; } [...blafasel...] setState('javascript.0.Counter',getState('javascript.0.Counter').val+1,true); // <-- das läuft hier doch alles asynchron, oder? -> Probleme! });
Wie kann ich das mit dem bestehenden ioBroker lösen, wenn auf das Triggerobjekt 'adapter.0.testVariable' innerhalb von 3s z.B. ein Burst von 500 Änderungen eingeht? Je Event ca 4Kb Objektgröße.
Der Counter zeigt nämlich in diesem Fall etwa 1-2% Eventverlust. Keine weiteren Fehlermeldungen.
Jetzt wäre ich eigentlich fertig, aber …
PS: Übrigens, ein Lob an das Eventhandling innerhalb vom ioBroker js-controller und dem History-Adapter! Da verschwindet nichts. Nur die Task-Queue im History-Adpater wächst. Vor allem, wenn man solche Bursts wiederholt und etwas länger anstehen lässt. Dann auch mal gerne auf 1+GB RAM. Raspberry rulez! :roll:
Ach ja, der Admin-Adapter hat einen lustigen Einstellparameter, der dort auf gut deutsch "Ereignisschwellenwert" genannt wird. Wer mir mir auf Anhieb sagen kann, was er bedeutet, gewinnt (an Erfahrung). Immerhin gibt es den schon seit Version 2.0.11. Mit diesem Wort in der Forumssuche findet man genau - nix. Selbst Google, Bing und Co. schweigen sich aus. Hätte den doch mal jemand auf Englisch gelassen. Dort heißt er 'Events threshold value' und bedeutet hier wie dort, die maximale Anzahl an Events, die innerhalb eines Zeitfensters (3s) an den Admin-Client durchflutschten darf, bevor sich der Admin von fast allen States trennt und von neuen loslegt. Der Default ist 200 und man kann sich vorstellen, was bei 500 Änderungen am Client so passiert. Siehe auch viewtopic.php?f=8&t=6177 mit der Vorgeschichte. Hat schon mal jemand Performance-Tests durchgeführt? Wäre doch fast mal eine Idee für einen neuen Adapter zum Ausmessen unserer Boards… Events/s, Strombedarf/Event,...hach... :mrgreen:
PPS: Irgendwas passt im SQL-Adapter (bei MSSQL als Backend) nicht ganz. Ich komme nur noch nicht genau darauf, wo das Problem liegt. Unter Beobachtung…
-
Liege ich vorsichtig richtig, dass aus der dem Inhalt des Threads https://forum.iobroker.net/viewtopic.php?t=6175#p63581 aus 2017 bislang noch nichts entstanden ist?
Anweisungen wie
createStateSync setObjectSync extendObjectSync ... sleep
scheinen allgemein noch ziemlich unbekannt zu sein. `
Ich denke das hast Du recht. Ich habe mal ein Github Issue angelegt https://github.com/ioBroker/ioBroker.ja … issues/155
Aus diesem Grund mache ich mir Sorgen um den folgenden Scriptansatz:
on({id: 'adapter.0.testVariable', change: "ne"}, function (obj) { try { obj = JSON.parse(getState('adapter.0.testVariable').val); // <-- ist hier immer noch das gleiche drin, wie beim Triggermoment oben? } catch (e) { console.error(e.message + ' --- Fehler beim JSON-parsen: ' + getState('adapter.0.testVariable').val); return; } [...blafasel...] setState('javascript.0.Counter',getState('javascript.0.Counter').val+1,true); // <-- das läuft hier doch alles asynchron, oder? -> Probleme! });
Wie kann ich das mit dem bestehenden ioBroker lösen, wenn auf das Triggerobjekt 'adapter.0.testVariable' innerhalb von 3s z.B. ein Burst von 500 Änderungen eingeht? Je Event ca 4Kb Objektgröße.
Der Counter zeigt nämlich in diesem Fall etwa 1-2% Eventverlust. Keine weiteren Fehlermeldungen.
An der Stelle bringst Du aber Dinge durcheinander. Bei der Idee oben geht es darum für alle "Skript-Schreiber" es so einfach wie möglich zu machen weil der asynchrone Ansatz immer schwierig ist und die erwartung ist das eine Aktion sofort Auswirkungen hat.
Subscribes wie in dem Fall werden auch danach immer asynchron laufen weil das deren natur ist.
Für mich ist es so: DIe Zusage bei subscriptions/on ist das die Funktion aufgerufen wird wenn die erwartete Änderung passiert ist und dann das geänderte Objekt als Parameter mitgegeben wird !! Also wenn du das obj.state.val nimmst was mitkommt anstelle den Wert neu abzufragen dann solltest Du keinerlei Datenverluste haben!
Ein "getState" liefert den Wert der zum Zeitpunkt der Abfrage beim JavaScript-Adapter als zum Zeitpunkt der Abfrage gespeichert ist (wenn man das einlesen aller Werte in den Einstellungen aktiviert hat). Das kann ein anderer Wert sein als der der hier asynchron als Trigger ausgeführt wurde.
Damit ist die Frage was Du im Trigger-Code erreichen willst:
-
Willst Du auf die eigentliche Werteänderung reagieren und keinen Wert verlieren, dann nimm das übergebene Objekt als Wert.
-
Willst Du nur den "trigger der Änderung" aber auf den aktuellsten Wert gehen dann nutze getState mit einem Callback, das sollte dann immer direkt gegen die State-DB ausgeführt werden und Du bekommst den alleraktuellsten Wert. Dann verlierst Du aber die Werte dazwischen und hast ggf viele "trigger" obwohl DU schon was abgearbeitet hast.
Hier muss man sich also mit der asynchronität auseinandersetzen - vor allem in einem "Hochlastumfeld". Da helfen auch die oben beschriebenen "Methoden die Dinge selbst tun synchron ausführen" nicht!
PS: Übrigens, ein Lob an das Eventhandling innerhalb vom ioBroker js-controller und dem History-Adapter! Da verschwindet nichts. Nur die Task-Queue im History-Adpater wächst. Vor allem, wenn man solche Bursts wiederholt und etwas länger anstehen lässt. Dann auch mal gerne auf 1+GB RAM. Raspberry rulez! :roll: `
Nutze Redis als State-DB und dein Limit ist echt nur der das RAM (ok und die CPU-Geschwindigkeit weil wenn änderungen schneller kommen als sie verarbeitet werden ist irgendwann das RAM voll und der Rechner Tod). AN der Stelle ist das ganze Event-System von nodejs/JavaScript sehr durchdacht und performant.
Ach ja, der Admin-Adapter hat einen lustigen Einstellparameter, der dort auf gut deutsch "Ereignisschwellenwert" genannt wird. Wer mir mir auf Anhieb sagen kann, was er bedeutet, gewinnt (an Erfahrung). Immerhin gibt es den schon seit Version 2.0.11. Mit diesem Wort in der Forumssuche findet man genau - nix. Selbst Google, Bing und Co. schweigen sich aus. Hätte den doch mal jemand auf Englisch gelassen. Dort heißt er 'Events threshold value' und bedeutet hier wie dort, die maximale Anzahl an Events, die innerhalb eines Zeitfensters (3s) an den Admin-Client durchflutschten darf, bevor sich der Admin von fast allen States trennt und von neuen loslegt. Der Default ist 200 und man kann sich vorstellen, was bei 500 Änderungen am Client so passiert. Siehe auch viewtopic.php?f=8&t=6177 mit der Vorgeschichte. Hat schon mal jemand Performance-Tests durchgeführt? Wäre doch fast mal eine Idee für einen neuen Adapter zum Ausmessen unserer Boards… Events/s, Strombedarf/Event,...hach... :mrgreen: `
Dieser Parameter wurde mit Admin2 nötig weil der immer alle Änderungen bekommen hat. Damit war bei größeren Systemen sehr schnell der Admin nicht mehr wirklich nutzbar weil er mehr mit Event-kram zu tun hatte als auf UI-Events zu reagieren.
Seit Admin3 ist das wieder viel entspannter (kenne keinen Fall wo Admin3 gegen das Limit gerannt ist) weil der nur das an Daten anfordert/subscribed was gebraucht oder angezeigt wird. Aus dem Grund sind mit Admin3 tests sehr schwierig.
Wir wissen das ein System mit 60.000 Objekten problemlos läuft …
PPS: Irgendwas passt im SQL-Adapter (bei MSSQL als Backend) nicht ganz. Ich komme nur noch nicht genau darauf, wo das Problem liegt. Unter Beobachtung… `
Wenn Du es rausgefunden hast leg Issue oder Thread hier an
-
-
Danke für Deine ausführlichen Hinweise. In bin davon ausgegangen, dass eine synchrone Ausführung die Objekte serialisiert. Mal schauen, was mein i7 so hergibt. Ich halte Dich auf dem Laufenden, wenn Du möchtest.
Für mich ist nur wichtig, das das mitgegebene Objekt nicht an anderer Stelle ohne Berücksichtigung verändert oder gar gelöscht wird. Beispiel, was ist, wenn ich einen Status lese, verarbeite und zurückschreibe, aber zwischendurch schon jemand anderer ebenfalls denn State geschrieben hat und mein Script parallel getriggert wird. Gehen die Events auch bei Multiserverumgebungen in der richtigen Reihenfolge ein?
-
Danke für Deine ausführlichen Hinweise. In bin davon ausgegangen, dass eine synchrone Ausführung die Objekte serialisiert. Mal schauen, was mein i7 so hergibt. Ich halte Dich auf dem Laufenden, wenn Du möchtest. `
Gern!Also nochmal geschaut. Code-technisch ist das interessant …
Der Ablauf "eine State-Änderung kommt beim JS-Adapter an" sollte dann an sich synchrons sein!
-
kommt an
-
wird gecheckt und wert im internen cache aktualisiert
-
alle subscriptions werden nacheinander aufgerufen
Da ist an sich keine asynchronität drin.
Dait sollte mal mindestens der "getState().val Wert und obj.state.val übereinstimmen
Bleibt damit nur noch das Thema das ggf die Änderungen nicht 100%ig in der Reighenfolge ankommen beim JS-Adapter in dem Sie passiert sind.
Kannst ja mal das auch testen ob die Daten übereinstimmen (PS: hab ich ich wieder was neues gelernt)
Für mich ist nur wichtig, das das mitgegebene Objekt nicht an anderer Stelle ohne Berücksichtigung verändert oder gar gelöscht wird. Beispiel, was ist, wenn ich einen Status lese, verarbeite und zurückschreibe, aber zwischendurch schon jemand anderer ebenfalls denn State geschrieben hat und mein Script parallel getriggert wird. Gehen die Events auch bei Multiserverumgebungen in der richtigen Reihenfolge ein? `
Das ist genau das Thema. Siehe oben.Am Ende ist die "State-DB" (sei es die js-controller Mem-DB oder Redis) dumm. Deswegen haben Adapter auch kein" oldState" (wird bei JavaScript manuell hinzugefügt). Hier findet meines Wissens nach keinerlei Syncronisierung statt - geht auch gar nicht bei Multi-Adapter Benutzung.
Wenn eine Änderung geschrieben wird bekommt die den aktuellen Timestamp und landet in der State-DB-Ende. Reihenfolge bei "Multi-Adapter" oder "multihost" Umfeldern ist "wer zuerst schreibt ist erster"
Die "Syncronisierung" erfolgt nur auf DB Ebene weil dort eine Schreibaktion atomar ist. State-Änderungen werden dann (mechanismen unterschiedlich) an subscriber notifiziert die dann per Socket.io (js.controller) oder Redis-Verbindung von den änderungen informiert werden. Dort landet das dann wieder synchronisiert pro Adapter-Prozess in der "onStateChanged"-Logik und ist dann da wieder "eins nach dem anderen je nach Empfangsreihenfolge".
Soweit zur Theorie
Also: Bei einem Multi-Adapter/Host betrieb kann niemand eine definitve Reihenfolge der Nachrichten sicherstellen. Auch Zeitstempel könnten gemischt sein. Am Ende hängt alles davon ab wann der Wert geschrieben wurde und wie die State-DB daraus die Notifies an die subscriber weitergibt.
Schreibaktionen in einem Adapter-Prozess sind sequenziell weil nodejs nur einen Thread hat. Ebenso damit der "Empfangs-Adapter"
-
-
Datenbanktechnisch ausgedrückt braucht man also zur Umsetzung der Anforderung - es könnten ja auch mehrere JS-Adapter laufen oder sich doch bezgl. der Ausführgeschwindgkeit bis sie an die Codestelle gelangen sich überlappen - so ein Konstrukt:
if exists (select 1 from mystates with (updlock) where state = @id) update mystates set counter = counter + 1 where state = @id;
Da wird ein Lock auf das Objekt in der State-DB gelegt, das so lange anhält, bis das Update vorbei ist. Das LESEN verursacht eine Verriegelung, so das jeder andere, der auch Lesen will erst mal vor verschlossener Tür steht und in der Queue warten muss.
So etwas geht im JS-Adapter? Sag bitte ja. :roll: Wenn nicht, geht sowas als Adapter? Ach was sag ich, der kann diesbezüglich ja auch nicht mehr. Seufz.
Und zu
> Schreibaktionen in einem Adapter-Prozess sind sequenziell weil nodejs nur einen Thread hat. Ebenso damit der "Empfangs-Adapter"
-> Ist letztendlich egal, da nodejs und Empfangsadapter "dumm" sind.
-
Für solche Anwendungsfälle ist die State-DB nicht gedacht. Von daher (korrekt) kann die das nicht und braucht Sie auch nicht.
Wenn Du wirklich sowas brauchst brauchst Du eine eigene Kommunikationsschiene für diese Daten wo Du das implementierst. Also keine Ahnung einfaches TCP protokoll oder socket.io aber eigenes mit eigener Logik dahinter der die "Datenreihenfolge" und "Konsistenz" sicherstellt. Von dort kannst Du dann weitermachen.
Jetzt bin ich aber bei der Frage des "in diesem Umfeld realen Usecases"
-
Ha, sie könnte es doch!
redis-lock: https://github.com/mike-marcacci/node-redlock
Könnte, hätte, fahrradkette….
-
npm kann fast alles … aber wie gesagt: Die ioBroker-State-DB Logik hat nicht das Ziel das zu küönnen weil Sie es für alle bisher bekannten Usecases nicht braucht
-
Bis jetzt war Queue nicht nötig und wird auch imho nicht nötig sein. Wenn ein Wert so oft geschrieben wird, dann ist was falsch und nicht dafür gedacht.
Was man einbauen kann, dass wenn neue ZeitStempel älter als gespeicherte ist, dann wird es abgelehnt.
Das setzt aber Herausforderung alle slaves zeitsynchron zu halten.
-
> Wenn ein Wert so oft geschrieben wird, dann ist was falsch
Das hat IMHO nichts mit zu vielen Änderungen in kurzer Zeit zu tun. Eher damit, dass ein Konstrukt wie z.B.
`let value=getState('javascript.0.Counter').val; value++; setState('javascript.0.Counter',value,true); oder [code]setState('javascript.0.Counter',getState('javascript.0.Counter').val + 1,true);[/code]` aufgrund von sich möglichen überlappenden Zugriffen unbedingt zu vermeiden ist. Es kann <u>~~[u]~~nicht[/u]</u> sichergestellt werden, dass 'javascript.0.Counter' sich nicht zwischen get und set verändert. Da fehlt ein (einfacher) Locking-Mechanismus für States und Objekte. Der Zeitstempel hilft da nicht. Evtl. könnte bei redis node-redlock helfen. -> neuer "getStateLock"-Befehl, der einen State bis zum nächten setState aus der gleichen Eventquelle locked :roll: Aber wie apollon77 schreibt: Use Case. Es gibt wichtigeres... Man muss es nur wissen.
-
Zum erhöhen oder erniedrigen von werten hätte redis auch eigene Funktionen, aber ja die Frage ist ob das usecase technisch relevant ist.
Und Zeit synchron halten … vergiss es
Gesendet vom Handy ...