[DB de 5Go] Lenteurs au premier démarrage

Salut à tous, salut @pierre-gilles,

J’en parle sur le forum avant de définir ensemble ce que tu veux voir sur GitHub :wink:
Ca fait un moment que je le ressent, mais j’ai pu le confirmer maintenant en enregistrant mon écran.

Démarrage de Gladys
Si le conteneur de Gladys est arrêté puis démarré à nouveau (après une MAJ par exemple), le premier chargement des graphiques prend énormément de temps.
Cela est certainement dû en partie à la taille de la DB 4.5Go ! Mais pas que, puisque tu as expliqué ne prendre qu’une toute petite partie de donnée pour accélérer l’affichage.

Vidéo démo (1m30)
Note :

  1. La page Gladys est ouverte au début, mais ne pas le prendre en compte
  2. Je (re) lance Gladys, puis j’actualise la page web de gladys
  3. C’est là qu’on voit les lenteurs et la forte utilisation du “disque” (carte SD chez moi)

Intéressant @lmilcent, merci du retour avec la vidéo :slight_smile:

Déjà, je vois que tous tes graphiques sont des affichages “dernières heures”, et ça c’est de la donnée LIVE (pas agrégées), donc c’est possible que ce soit des milliers de lignes/centaines de milliers selon si tes capteurs sont verbose ou pas.

Est-ce que tu pourrais:

  • Exporter ta DB
  • L’ouvrir sur ton ordinateur (avec un outil comme TablePlus )
  • Ensuite, essaie de filtrer ta table t_device_feature_state pour voir combien il y a de lignes par capteur et par heure, l’idée c’est de comprendre de quel volume de donnée on parle.

Ensuite, on verra si c’est un problème “normal” (dans le sens où il faudrait juste voir comment réduire ton flux de data si il est trop important), ou si côté Gladys il y a des optimisations DB à aller chercher :slight_smile:

En exécutant ta requête de la dernière fois pour chercher ce qui va pas.

Comme avant, ce sont les prises électriques qui ont le plus d’événements :

Par contre je me pose la question de ce qu’est la valeur tout en haut :

Et pour info, l’agrégation semble très très très verbeuse chez moi (1-300 of ~8 510 037), peut être due à l’historique (le bug NaN justement) :

Ca ressemble a une date !!

Ca fait bien longtemps que je soumet l’idée de mettre une option pour chaque feature pour dire si on souhaite enregistrer toutes les valeurs ou seulement les changements d’etats. Et de pair avec cela mettee a jour seulement la last_value de la table device_feature qui permettrait d avoir la date de derniere valeur remontée pour ensuite pourvoir mettre une alerte si plus de remonter pendant x secondes/minutes/etc.
Si on selectionne “Enregistrer seulement les changement d’etat” lorsque un changement d etat apparait on renvoi l’ancienne valeur avant pour bien specifier sur les courbes qu on est rester un certain laps de temps a la meme valeur.

Pour info c’est ce que j’ai fait sur l’integration Netatmo de @damalgos et cela fonctionne tres bien. Pas de valeurs inutiles stockées.

En attendant, c’est dommage, mais tu peux toujours passer par Node-red avant pour trier les valeurs a enregistrer ou non ?

Ah, est-ce que ces prises font pas partie des appareils qui sont beaucoup trop verbose ? Je crois que certains appareils Zigbee envoient une valeur toutes les 15-20 secondes même sans changement d’état, ce qui pourrait expliquer pourquoi c’est si plein alors que j’imagine que tu n’a pas allumé ta prise 375 000 fois :stuck_out_tongue:

Hésite pas à faire du clean en SQL sur ta DB en attendant qu’on ait une feature dans Gladys qui permette de le faire dans l’UI ^^

Oho, intéressant, je pensais pas qu’il y avait eu des NaN dans les données agrégées aussi ! C’est pas normal !

Je rajouterais un clean dans la prochaine mise à jour de Gladys, en attendant pareil que la dernière fois, tu peux cleaner ces données agrégées qui sont clairement inutile.

C’est vrai je me rappelle ! Il y a une demande de fonctionnalités pour ça ?

1 Like

Mmmh me semblait mais pas sûr !!^^ je reverifie !!

Edit :
@pierre-gilles, j’ai retrouvé : Case à cochée "Ne pas enregistrer les nouvelles valeurs identiques" + Choix de durée de backup indépendant pour chaque Device Features

Je veux bien tenter de m’en occuper au plus vite (je l’ai fait pour le service Netatmo qui tourne à la maison et ça fonctionne parfaitement). Il faudrait que tu valides le principe de fonctionnement toutefois pour ne pas partir sur un truc et devoir tout reprendre.

  • Côté front :

    • une case à cocher sur chaque feature “Enregistrer uniquement les changements d’états”,
    • cette case est décochée d’origine pour rester comme maintenant,
  • Côté server :

    • si la case “Enregistrer uniquement les changements d’états” est cochée, soit :
      • on ajoute dans la table t_device_feature une colonne “only_keep_value_changes” celle-ci est à 0 d’origine,
      • ou lorsque cochée on ajoute un paramètre dans device_param :
{
  "name": "[id de la feature]:only_keep_value_changes",
  "value": true
}
  • si la valeur de ce paramètre est true et que la nouvelle valeur est la même que la précédente, on passe par un nouveau fichier server/lib/device/device.saveLastStateChanged.js pour enregistrer dans la table t_device_feature la saveLastStateChanged comme tu me l’avais demandé à l’époque du dev Netatmo :
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,
};
  • Lorsque la valeur change de nouveau, on repasse par la fonction du fichier server/lib/device/device.saveState.js qu’on doit modifier pour réenregistrer, avant la nouvelle valeur, l’ancienne valeur déjà en base, pour avoir une courbe ajustée :
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,
};

exemple, si on reçoit pour une puissance consommée :

{
  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
}

On retrouvera en DB seulement

{
  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
},
// ici on a pas sauvegardé les valeurs identiques
{
  value: 1014.9,
  created_at: 2022-04-19 05:10:13.277 +00:00
},
// Après avoir écrit la valeur ci-dessous, on a renvoyé
{
  value: 78.0,
  created_at: 2022-04-19 05:20:13.277 +00:00
}

Ou tu vois ça différemment ? Dans ce cas aurais-tu grossièrement la ligne à suivre ?

Pour le second point sur l’historique,

  • Côté front :

    • un champs de sélections sur chaque feature “Conserver l’historique des états de la feature” proposant les mêmes choix que dans l’onglet Paramètres/Systèmes,
    • Le champs est sélectionné d’origine sur la configuration générale de Gladys (sauf pour les caméras
  • Côté server :

    • Soit :
      • on ajoute dans la table t_device_feature une colonne “delay_keep_history” d’origine sur la conf générale de Gladys ?
      • ou on modifie la colonne “keep_history” pour pouvoir y mettre directement la valeur ?
      • ou on passe par un parametre, si existant alors on tient compte de ce paramètre pour les values de la feature ?

Je modifie la db pour pas tout historiser, l’exemple tout bête c’est les capteurs aqara, de mon point de vue cela ne sert à rien d’hisstoriser la pression atmo dans chaque pièce car les valeurs vont être identiques.

Ma DB fait 2GB

Mais c’est vrai qu on a déjà eu cette discussion quelque part sur le forum

Et enfin une PR pour les scènes pour pouvoir envoyer une alerte en cas de non réception de valeur pendant un laps de temps sur un device.
Exemple :
Trigger : “Toutes les 10 minutes” (Existant)
Action : “Récupérer la date de dernière mise à jour de la (des) fonctionnalités” (last_value_changed) avec sélection d’une feature par device par exemple la batterie (A développer)
Action : “Continuer seulement si plus de 10 minutes” (A développer - soustraction)
Action : “Envoyer un message” (avec variable(s)) (Sûrement last_value_changed à ajouter)

De mon côté j’ai retrouvé la demande de fonctionnalité, tu avais en effet voté pour également ^^ Mais pas de réponse dessus. Je ne trouve pas d’autres sujets similaires …

Si tu as le temps de lire les 2 posts précédents et donner ton avis sur la proposition ce sera avec plaisir ^^

Merci a tous pour vos réponses. Je vous laisse et vous remercie de travailler là dessus :blush:

Je suis en congés sans accès facile à mon pi ou la db, donc je ferais tout ça en rentrant.

1 Like

Salut @lmilcent, un petit cadeau pour quand tu seras rentré de vacances, j’ai fais une rapide PR pour nettoyer la table t_device_feature_state_aggregate des valeurs NaN :slight_smile:

Ce n’est pas intégré à la release que je vais faire aujourd’hui, ça partira dans la prochaine.

Dans un premier temps, faisons simple et bien, je pense que côté serveur il n’y a rien à faire.

Il faut juste à rajouter dans l’interface un bouton « Conserver l’historique des valeurs de cet appareil » : coché ou pas.

Ce booléen modifiera l’attribut keep_history qui existe déjà, et qui fonctionne déjà côté serveur. C’est 2h de travail tout compris le temps de faire la PR, et il n’y a pas de spécification à écrire car la fonctionnalité existe déjà :slight_smile:

Pour rajouter une historisation par appareil, ça complexifie pas mal le produit et ça se réfléchit (quid de l’agrégation? Qu’est ce qui est prioritaire: le paramètre global ou le paramètre par appareil?). Bref, à mon avis restons focus sur l’essentiel pour l’instant.

Un message a été scindé en un nouveau sujet : Sauvegarder ou pas l’historique d’une fonctionnalités précise

Trop bien, merci :slight_smile:
Sympa comme surprise de retour de vacances !

Donc, pas d’aggrégation des valeurs sur 24h, vu que seule la dernière valeur est conservée, right ?

Il n’y a pas d’historique du tout du coup, seule une valeur est gardée, la dernière :slight_smile:

Comment tu fais ça ?

J’ouvre la db avec db browser et j’édite la colonne keep_history dans la table device_feature

1 Like

@lmilcent Correctif DB déployé !

1 Like

J’ai constaté que Gladys a travaillé pendant de très longues minutes donc j’en déduis que ça a nettoyé les données, sans avoir encore vérifié.
J’ai lancé manuellement un vacuum qui m’a fait gagné 100mo.

1 Like

Génial :slight_smile: On verra à la prochaine sauvegarde ce que ça donne