[5-GB-DB] Langsamkeit beim ersten Start

Hallo zusammen, hallo @pierre-gilles,

Ich rede zuerst auf dem Forum darüber, bevor wir gemeinsam definieren, was du auf GitHub sehen möchtest :wink:
Ich habe das schon länger gespürt, aber jetzt konnte ich es bestätigen, indem ich meinen Bildschirm aufgenommen habe.

Start von Gladys
Wenn der Gladys-Container gestoppt und dann erneut gestartet wird (z. B. nach einem Update), dauert das erste Laden der Diagramme extrem lange.
Das liegt sicherlich teilweise an der Größe der DB (4,5 GB)! Aber nicht nur, denn du hast erklärt, dass du nur einen winzigen Teil der Daten nimmst, um die Anzeige zu beschleunigen.

Demo-Video (1m30)
Hinweis:

  1. Die Gladys-Seite ist zu Beginn geöffnet, aber das soll nicht berücksichtigt werden
  2. Ich (starte) Gladys neu, dann aktualisiere ich die Gladys-Webseite
  3. Hier sieht man die Verzögerungen und die starke Nutzung des « Laufwerks » (SD-Karte bei mir)

Interessant @lmilcent, danke für das Feedback mit dem Video :slight_smile:

Ich sehe schon, dass alle deine Grafiken „letzte Stunden“-Anzeigen sind, und das sind LIVE-Daten (nicht aggregiert), also ist es möglich, dass es tausende Zeilen/hunderttausende gibt, je nachdem, ob deine Sensoren ausführlich sind oder nicht.

Könntest du:

  • Deine DB exportieren
  • Sie auf deinem Computer öffnen (mit einem Tool wie TablePlus )
  • Dann versuche, deine Tabelle t_device_feature_state zu filtern, um zu sehen, wie viele Zeilen es pro Sensor und pro Stunde gibt. Die Idee ist es, zu verstehen, von welchem Datenvolumen wir sprechen.

Danach sehen wir, ob es ein „normales“ Problem ist (im Sinne, dass man nur sehen müsste, wie man deinen Datenstrom reduzieren kann, wenn er zu groß ist), oder ob es auf Gladys-Seite DB-Optimierungen gibt, die man suchen sollte :slight_smile:

Beim Ausführen deiner letzten Anfrage, um herauszufinden, was nicht stimmt.

Wie zuvor sind es die Steckdosen, die die meisten Ereignisse haben:

Allerdings frage ich mich, was der Wert ganz oben bedeutet:

Und nur zur Info, die Aggregation scheint bei mir extrem ausführlich zu sein (1-300 von ~8.510.037), möglicherweise aufgrund des Historiens (genau der NaN-Fehler):

Das sieht aus wie ein Datum!!

Schon lange schlage ich vor, für jede Funktion eine Option einzuführen, um festzulegen, ob alle Werte oder nur die Zustandsänderungen gespeichert werden sollen. Und damit verbunden sollte nur der letzte Wert in der Tabelle device_feature aktualisiert werden, was es ermöglichen würde, das Datum des letzten empfangenen Wertes zu haben, um dann einen Alarm auszulösen, wenn keine Werte für x Sekunden/Minuten/etc. empfangen werden.
Wenn man „Nur Zustandsänderungen speichern“ auswählt, wird beim Auftreten einer Zustandsänderung der alte Wert zurückgesendet, um in den Diagrammen klar zu spezifizieren, dass man für eine bestimmte Zeitspanne beim gleichen Wert geblieben ist.

Nur zur Info, das ist das, was ich in der Netatmo-Integration von @damalgos gemacht habe und es funktioniert sehr gut. Keine unnötigen Werte werden gespeichert.

In der Zwischenzeit ist es schade, aber du kannst immer noch Node-red verwenden, um die zu speichernden Werte zu filtern?

Ah, gehören diese Steckdosen nicht zu den Geräten, die viel zu ausführlich sind? Ich glaube, einige Zigbee-Geräte senden einen Wert alle 15-20 Sekunden, selbst ohne Zustandsänderung, was erklären könnte, warum es so voll ist, obwohl ich mir vorstelle, dass du deine Steckdose nicht 375.000 Mal eingeschaltet hast :stuck_out_tongue:

Zögere nicht, in SQL in deiner Datenbank aufzuräumen, bis wir eine Funktion in Gladys haben, mit der du das in der UI tun kannst ^^

Oho, interessant, ich dachte nicht, dass es NaN in den aggregierten Daten gab! Das ist nicht normal!

Ich werde eine Bereinigung in der nächsten Gladys-Aktualisierung hinzufügen, bis dahin kannst du, wie beim letzten Mal, diese aggregierten Daten bereinigen, die eindeutig unnötig sind.

Stimmt, ich erinnere mich! Gibt es dafür einen Feature-Request?

Mmmh, dachte ich, aber ich bin mir nicht sicher!!^^ Ich überprüfe das nochmal!!

Edit:
@pierre-gilles, ich habe es gefunden: Case à cochée "Ne pas enregistrer les nouvelles valeurs identiques" + Choix de durée de backup indépendant pour chaque Device Features - #2 par pierre-gilles

Ich würde gerne versuchen, mich so schnell wie möglich darum zu kümmern (ich habe es für den Netatmo-Dienst gemacht, der zu Hause läuft, und es funktioniert perfekt). Es müsste aber das Funktionsprinzip validiert werden, damit wir nicht etwas anfangen und dann alles wieder von vorne machen müssen.

  • Frontend:

    • ein Kontrollkästchen für jede Funktion « Nur Zustandsänderungen speichern »,
    • dieses Kästchen ist standardmäßig deaktiviert, um es wie bisher zu belassen,
  • Server:

    • wenn das Kontrollkästchen « Nur Zustandsänderungen speichern » aktiviert ist, dann:
      • eine Spalte « only_keep_value_changes » in der Tabelle t_device_feature hinzufügen, diese ist standardmäßig auf 0 gesetzt,
      • oder wenn aktiviert, einen Parameter in device_param hinzufügen:
{
  "name": "[id der Funktion]:only_keep_value_changes",
  "value": true
}
  • wenn der Wert dieses Parameters true ist und der neue Wert derselbe wie der vorherige ist, wird eine neue Datei server/lib/device/device.saveLastStateChanged.js verwendet, um in der Tabelle t_device_feature die saveLastStateChanged zu speichern, wie du es mir damals während der Netatmo-Entwicklung vorgeschlagen hast:
const db = require('../../models');
const logger = require('../../utils/logger');
const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../utils/constants');

/**
 * @description Save new device feature state in DB.
 * @param {Object} deviceFeature - A DeviceFeature object.
 * @example
 * saveLastValueChanged({
 *   id: 'fc235c88-b10d-4706-8b59-fef92a7119b2',
 *   selector: 'my-light'
 * });
 */
async function saveLastStateChanged(deviceFeature) {
  // logger.debug(`device.saveLastStateChanged of deviceFeature ${deviceFeature.selector}`);
  const now = new Date();
  // save local state in RAM
  this.stateManager.setState('deviceFeature', deviceFeature.selector, {
    last_value_changed: now,
  });
  await db.sequelize.transaction(async (t) => {
    // update deviceFeature lastValue in DB
    await db.DeviceFeature.update(
      {
        last_value_changed: now,
      },
      {
        where: {
          id: deviceFeature.id,
        },
      },
      {
        transaction: t,
      },
    );
  });

  // send websocket event
  this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, {
    type: WEBSOCKET_MESSAGE_TYPES.DEVICE.NEW_STATE_NO_CHANGED,
    payload: {
      device_feature_selector: deviceFeature.selector,
      last_value_changed: now,
    },
  });
}

module.exports = {
  saveLastStateChanged,
};
  • Wenn sich der Wert erneut ändert, wird die Funktion der Datei server/lib/device/device.saveState.js verwendet, die geändert werden muss, um vor dem neuen Wert den alten Wert, der bereits in der Datenbank gespeichert ist, erneut zu speichern, um eine angepasste Kurve zu erhalten:
const db = require('../../models');
const logger = require('../../utils/logger');
const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../utils/constants');
const { BadParameters } = require('../../utils/coreErrors');

const DEFAULT_OPTIONS = {
  skip: 0,
  order_dir: 'DESC',
  order_by: 'created_at',
};

/**
 * @description Save new device feature state in DB.
 * @param {Object} deviceFeature - A DeviceFeature object.
 * @param {number} newValue - The new value of the deviceFeature to save.
 * @example
 * saveState({
 *   id: 'fc235c88-b10d-4706-8b59-fef92a7119b2',
 *   selector: 'my-light'
 * }, 12);
 */
async function saveState(deviceFeature, newValue) {
  if (Number.isNaN(newValue)) {
    throw new BadParameters(`device.saveState of NaN value on ${deviceFeature.selector}`);
  }
  const optionsWithDefault = Object.assign({}, DEFAULT_OPTIONS);

  // logger.debug(`device.saveState of deviceFeature ${deviceFeature.selector}`);
  const now = new Date();
  const previousDeviceFeature = this.stateManager.get('deviceFeature', deviceFeature.selector);
  const previousDeviceFeatureValue = previousDeviceFeature ? previousDeviceFeature.last_value : null;

  const previousDeviceFeatureLastValueChanged = previousDeviceFeature ? previousDeviceFeature.last_value_changed : null;

  const deviceFeaturesState = await db.DeviceFeatureState.findOne({
    attributes: ['device_feature_id', 'value', 'created_at'],
    order: [[optionsWithDefault.order_by, optionsWithDefault.order_dir]],
    where: {
      device_feature_id: deviceFeature.id,
    },
  });
  const previousDeviceFeatureStateLastValueChanged = deviceFeaturesState ? deviceFeaturesState.created_at : 0;

  // save local state in RAM
  this.stateManager.setState('deviceFeature', deviceFeature.selector, {
    last_value: newValue,
    last_value_changed: now,
  });
  // update deviceFeature lastValue in DB
  await db.DeviceFeature.update(
    {
      last_value: newValue,
      last_value_changed: now,
    },
    {
      where: {
        id: deviceFeature.id,
      },
    },
  );
  // if the deviceFeature should keep history, we save a new deviceFeatureState
  if (deviceFeature.keep_history) {
    // if the previous created deviceFeatureState is different of deviceFeature
    // last value changed, we save a new deviceFeatureState of old state
    if (previousDeviceFeatureLastValueChanged - previousDeviceFeatureStateLastValueChanged > 0) {
      await db.DeviceFeatureState.create({
        device_feature_id: deviceFeature.id,
        value: previousDeviceFeatureValue,
        created_at: previousDeviceFeatureLastValueChanged,
      });
    }
    await db.DeviceFeatureState.create({
      device_feature_id: deviceFeature.id,
      value: newValue,
    });
  }
  // });

  // send websocket event
  this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, {
    type: WEBSOCKET_MESSAGE_TYPES.DEVICE.NEW_STATE,
    payload: {
      device_feature_selector: deviceFeature.selector,
      last_value: newValue,
      last_value_changed: now,
    },
  });

  // check if there is a trigger matching
  this.eventManager.emit(EVENTS.TRIGGERS.CHECK, {
    type: EVENTS.DEVICE.NEW_STATE,
    device_feature: deviceFeature.selector,
    previous_value: previousDeviceFeatureValue,
    last_value: newValue,
    last_value_changed: now,
  });
}

module.exports = {
  saveState,
};

Beispiel, wenn wir für einen verbrauchten Leistungswert erhalten:

{
  value: 1014.7,
  created_at: 2022-04-19 04:20:13.277 +00:00
},
{
  value: 1014.9,
  created_at: 2022-04-19 04:30:13.277 +00:00
},
{
  value: 1014.9,
  created_at: 2022-04-19 04:40:13.277 +00:00
},
{
  value: 1014.9,
  created_at: 2022-04-19 04:50:13.277 +00:00
},
{
  value: 1014.9,
  created_at: 2022-04-19 05:00:13.277 +00:00
},
{
  value: 1014.9,
  created_at: 2022-04-19 05:10:13.277 +00:00
},
{
  value: 78.0,
  created_at: 2022-04-19 05:20:13.277 +00:00
}

In der Datenbank wird nur folgendes gespeichert:

{
  value: 1014.7,
  created_at: 2022-04-19 04:20:13.277 +00:00
},
{
  value: 1014.9,
  created_at: 2022-04-19 04:30:13.277 +00:00
},
// Hier wurden die identischen Werte nicht gespeichert
{
  value: 1014.9,
  created_at: 2022-04-19 05:10:13.277 +00:00
},
// Nach dem Schreiben des folgenden Wertes wurde zurückgeschickt
{
  value: 78.0,
  created_at: 2022-04-19 05:20:13.277 +00:00
}

Siehst du das anders? In diesem Fall hättest du grob die Richtung, die wir einschlagen sollten?

Zum zweiten Punkt zur Historie,

  • Frontend :

    • ein Auswahlfeld für jedes Feature « Historisierung der Feature-Zustände » mit denselben Optionen wie im Tab Einstellungen/Systeme,
    • Das Feld ist standardmäßig auf die allgemeine Gladys-Konfiguration ausgewählt (außer für Kameras
  • Backend :

    • Entweder :
      • wir fügen in der Tabelle t_device_feature eine Spalte « delay_keep_history » hinzu, die standardmäßig auf die allgemeine Gladys-Konfiguration gesetzt ist?
      • oder wir ändern die Spalte « keep_history », um direkt den Wert dort einzutragen?
      • oder wir nutzen einen Parameter, wenn vorhanden, dann berücksichtigen wir diesen Parameter für die Feature-Werte?

Ich ändere die Datenbank, damit nicht alles historisiert wird. Ein einfaches Beispiel sind die Aqara-Sensoren. Aus meiner Sicht bringt es nichts, den Luftdruck in jedem Raum zu historisieren, da die Werte identisch sein werden.

Meine Datenbank ist 2 GB groß.

Aber stimmt, wir hatten diese Diskussion schon irgendwo im Forum.

Und schließlich ein PR für Szenen, um eine Warnung zu senden, falls kein Wert innerhalb eines bestimmten Zeitraums auf einem Gerät empfangen wird.
Beispiel:
Trigger: « Alle 10 Minuten » (Bestehend)
Aktion: « Datum der letzten Aktualisierung der (des) Funktion(en) abrufen » (last_value_changed) mit Auswahl einer Funktion pro Gerät, z. B. die Batterie (Zu entwickeln)
Aktion: « Nur fortsetzen, wenn mehr als 10 Minuten » (Zu entwickeln - Subtraktion)
Aktion: « Nachricht senden » (mit Variable(n)) (Wahrscheinlich last_value_changed hinzufügen)

Von meiner Seite aus habe ich die Funktionsanfrage wiedergefunden, du hast tatsächlich auch dafür gestimmt ^^ Aber es gab keine Antwort darauf. Ich finde keine anderen ähnlichen Themen…

Falls du Zeit hast, die beiden vorherigen Beiträge zu lesen und deine Meinung zur Vorschlag zu äußern, wäre das super ^^

Danke an alle für eure Antworten. Ich lasse euch jetzt damit arbeiten und bedanke mich :blush:

Ich bin im Urlaub und habe keinen einfachen Zugriff auf meinen PC oder die Datenbank, also mache ich das alles, wenn ich zurück bin.

Hallo @lmilcent, ein kleines Geschenk für dich, wenn du aus dem Urlaub zurück bist. Ich habe einen schnellen PR gemacht, um die Tabelle t_device_feature_state_aggregate von NaN-Werten zu bereinigen :slight_smile:

Das ist nicht in die Veröffentlichung enthalten, die ich heute mache, das geht in die nächste.

Zuerst machen wir es einfach und gut, ich denke, auf der Serverseite gibt es nichts zu tun.

Man muss nur in der Oberfläche einen Button « Historische Werte dieses Geräts speichern » hinzufügen: angekreuzt oder nicht.

Dieser Boolesche Wert ändert das Attribut keep_history, das bereits existiert und auf der Serverseite bereits funktioniert. Das sind insgesamt 2 Stunden Arbeit, um den PR zu erstellen, und es gibt keine Spezifikationen zu schreiben, da die Funktion bereits existiert :slight_smile:

Um eine Historisierung pro Gerät hinzuzufügen, wird das Produkt deutlich komplexer und muss gut durchdacht werden (was ist mit der Aggregation? Was hat Vorrang: der globale Parameter oder der Parameter pro Gerät?). Naja, meiner Meinung nach bleiben wir vorerst auf das Wesentliche fokussiert.

Toll, danke :slight_smile:
Nette Überraschung nach dem Urlaub!

Also keine Aggregation der Werte über 24 Stunden, da nur der letzte Wert gespeichert wird, richtig?

Es gibt überhaupt keine Historie, es wird nur ein Wert gespeichert, der letzte :slight_smile:

Wie machst du das?

Ich öffne die Datenbank mit dem DB-Browser und bearbeite die Spalte keep_history in der Tabelle device_feature

@lmilcent DB-Korrektur bereitgestellt!

Ich habe festgestellt, dass Gladys sehr lange gearbeitet hat, daher gehe ich davon aus, dass sie die Daten bereinigt hat, ohne dass ich das bisher überprüft habe.
Ich habe manuell ein Vacuum gestartet, wodurch ich 100 MB gewonnen habe.

Genial :slight_smile: Wir werden beim nächsten Backup sehen, wie es aussieht.