Freeze pendant MAJ d'un device Z2M

Bonjour à tous,

CECI EST UN APPEL À L’AIDE XD

Je tente de mettre a jours mes devices, mais sur certain gladys freeze car je suppose qu’ils ont beaucoup d’état.
Je dois m’acheter deux SSD NVME pour mon nas à la fin d’année pour ameliorer ma ram (que les prix son chere maintenant :eyes:)

  • Que puis je faire, en attendant ?
  • Que dois je chercher dans les logs pour vérifier si gladys est stuck ou si elle bosse ?

Merci par avance (en attendant ma domotique est en carafe :smiley:

Est-ce que tu as un accès facile à la base de données ? Est-ce que tu es prêt à supprimer des anciens états pour ces appareils ?
Si oui, on peut te guider pour lancer des requêtes à la main.

Sinon, on peut considérer que c’est un bug et améliorer la migration pour qu’elle soit faite par morceaux ou en fond.

Tu essayes de mettre à jour dans l’interface Gladys pour z2m ? Pour ajouter une fonctionnalité ?

Oui sur ça pas de soucis :slight_smile:

Je tentais de mettre a jour ma prise pour y ajouter les features de conso d’energie depuis l’interface gladys dans decouverte puis MAJ

Le mieux serait quand même de ne pas supprimé :smiley:

J’ai poursuivi le diagnostic avec ChatGPT, en profitant du fait que Gladys était encore dans l’état « freeze ».

Le problème apparaît lors de la mise à jour/synchronisation d’un device Zigbee2MQTT depuis Gladys. Dans mon cas, il s’agissait d’une prise qui devait récupérer de nouvelles fonctionnalités liées à l’énergie/consommation.

Plusieurs vérifications ont permis d’écarter quelques pistes :

  • Le conteneur Gladys ne sature pas : environ 3 % CPU et 2,4 Go de RAM sur 19 Go disponibles pendant le freeze.
  • Zigbee2MQTT ne semble pas non plus saturé.
  • Le process Node de Gladys reste vivant.
  • GET / sur Gladys répond immédiatement en HTTP 200, donc Express est toujours capable de servir le frontend statique.
  • En revanche, les appels à l’API dynamique, par exemple la récupération des devices via mon proxy, finissent en timeout.
  • SQLite reste accessible depuis un autre processus pendant le freeze : des requêtes simples répondent en environ 0,5 seconde.
  • Le WAL SQLite faisait environ 1 Go, mais PRAGMA wal_checkpoint(PASSIVE) retourne 0|227|227, donc pas d’indication d’un checkpoint bloqué.
  • t_device_feature_state contient 0 ligne (les historiques ont été migrés vers DuckDB) et t_device_feature seulement 446 lignes.

Avec ChatGPT, on a ensuite inspecté directement le code présent dans mon conteneur Gladys.

La partie Zigbee2MQTT getDiscoveredDevices.js contient bien les correctifs récents concernant les features énergie : conservation des features consumption/cost, merge avec le device existant, puis appel à addEnergyFeatures().

En revanche, dans mon device.create.js, après la mise à jour d’une feature, j’ai actuellement :

await deviceFeature.update(featureToUpdate, { transaction });

if (deviceFeature.keep_history === false) {
  deviceFeaturesIdsToPurge.push(deviceFeature.id);
}

ChatGPT a comparé ce comportement avec le code actuel de Gladys et a identifié une différence importante. Le code corrigé mémorise l’ancienne valeur :

const keepHistoryBeforeUpdate = deviceFeature.keep_history;

await deviceFeature.update(featureToUpdate, { transaction });

if (
  keepHistoryBeforeUpdate !== false &&
  deviceFeature.keep_history === false
) {
  deviceFeaturesIdsToPurge.push(deviceFeature.id);
}

La différence est que dans ma version, chaque mise à jour d’un device peut redemander une purge pour toutes les features qui ont déjà keep_history=false, même si cette valeur n’a pas changé.

Après la transaction, Gladys émet ensuite :

this.eventManager.emit(
  EVENTS.DEVICE.PURGE_STATES_SINGLE_FEATURE,
  deviceFeatureIdToPurge
);

L’hypothèse actuelle proposée par ChatGPT est donc que les mises à jour Z2M provoquent une accumulation de tâches de purge inutiles. Ces traitements passent notamment par DuckDB et pourraient s’accumuler jusqu’à rendre les API dynamiques de Gladys non réactives, alors que le process Node lui-même continue de fonctionner.

Cette hypothèse correspond particulièrement bien au comportement observé : CPU/RAM normaux + frontend statique accessible + SQLite accessible + API Gladys qui timeout.

Ce n’est pas la même intégration mais ça ressemble très étrangement à ce que j’ai eu et que j’ai ouvert en Issue : Overkiz integration: assigning a room to a device takes several minutes (freezes the interface) · Issue #2900 · GladysAssistant/Gladys · GitHub

@pierre-gilles @cicoub13 petite avancé

Résumé par chatGPT:

Freeze complet de Gladys lors de la mise à jour d’un appareil Zigbee2MQTT

Je rencontre un blocage reproductible de Gladys lorsque je mets à jour depuis l’interface Zigbee2MQTT une prise nommée Station informatique.

À l’origine, je voulais simplement mettre à jour cet appareil car certaines fonctionnalités liées à l’énergie/consommation étaient absentes.

Symptômes

Lors de la mise à jour de l’appareil depuis Gladys :

  • la requête finit par timeout ;

  • l’interface devient partiellement ou totalement inutilisable ;

  • les écritures SQLite commencent ensuite à échouer avec :

    SequelizeTimeoutError
    SQLITE_BUSY: database is locked

Par exemple :

Unable to reset failure count of integration ...
SQLITE_BUSY: database is locked

Le serveur HTTP lui-même continue toutefois à répondre sur le port 8455.

Configuration DB

La base SQLite est assez importante :

gladys-production.db        ~12 Go
gladys-production.db-wal   ~1 Go
gladys-production.duckdb   ~1,9 Go

SQLite fonctionne en WAL :

PRAGMA journal_mode;
wal

Un checkpoint passif fonctionne :

PRAGMA wal_checkpoint(PASSIVE);
0|227|227

Les requêtes simples sur SQLite restent rapides :

SELECT COUNT(*) FROM t_device_feature;
446

real 0m0.090s

Et :

SELECT COUNT(*) FROM t_device_feature_state;
0

Les états semblent donc bien avoir été migrés vers DuckDB.

Appareil concerné

L’appareil est :

id:
fd928ba6-ca0d-4a0d-8483-5b74cfddce8c

external_id:
zigbee2mqtt:Station informatique

Avant la tentative de mise à jour, ses features enregistrées sont notamment :

Commutateur
Puissance consommée
Intensité consommée
Tension moyenne
Energie consommée
Intensité du signal
Mode de contrôle d'accès

La feature « Mode de contrôle d’accès » correspond à :

zigbee2mqtt:Station informatique:access-control:mode:child_lock

Investigation dans device.create.js

J’ai instrumenté :

/src/server/lib/device/device.create.js

afin de déterminer précisément où la transaction se bloque.

Le début de la mise à jour fonctionne normalement :

[DEBUG DEVICE CREATE] START zigbee2mqtt:Station informatique
[DEBUG DEVICE CREATE] BEFORE getDeviceInDb zigbee2mqtt:Station informatique
[DEBUG DEVICE CREATE] AFTER getDeviceInDb zigbee2mqtt:Station informatique
[DEBUG DEVICE CREATE] BEFORE device update zigbee2mqtt:Station informatique
[DEBUG DEVICE CREATE] AFTER device update zigbee2mqtt:Station informatique
[DEBUG DEVICE CREATE] BEFORE feature cleanup zigbee2mqtt:Station informatique

Le blocage se produit donc pendant cette partie de device.create.js :

await Promise.map(deviceInDb.features, async (existingFeature) => {
  if (!matchFeatureInList(existingFeature, features)) {
    await existingFeature.destroy({ transaction });
  }
});

J’ai ensuite instrumenté chaque feature.

Résultat :

[DEBUG FEATURE CLEANUP] ...:switch:binary:state matched= true
[DEBUG FEATURE CLEANUP] ...:switch:power:power matched= true
[DEBUG FEATURE CLEANUP] ...:switch:current:current matched= true
[DEBUG FEATURE CLEANUP] ...:switch:voltage:voltage matched= true
[DEBUG FEATURE CLEANUP] ...:switch:energy:energy matched= true

Puis :

[DEBUG FEATURE CLEANUP] zigbee2mqtt:Station informatique:access-control:mode:child_lock matched= false
[DEBUG FEATURE DESTROY] BEFORE zigbee2mqtt:Station informatique:access-control:mode:child_lock

Et aucun DEBUG FEATURE DESTROY AFTER n’apparaît.

La feature suivante est encore inspectée par le Promise.map :

[DEBUG FEATURE CLEANUP] ...:signal:integer:linkquality matched= true

mais le Promise.map ne termine jamais puisque le destroy() de child_lock reste bloqué.

Ce qui semble se produire

La nouvelle définition remontée par Zigbee2MQTT ne contient plus la feature :

access-control:mode:child_lock

Gladys considère donc logiquement cette ancienne feature comme supprimée et exécute :

await existingFeature.destroy({ transaction });

C’est précisément cet appel qui ne retourne pas.

La transaction device.create() reste alors ouverte.

Peu après, d’autres composants de Gladys essayent d’écrire dans SQLite et commencent à produire :

SQLITE_BUSY: database is locked

Par exemple les mises à jour de t_service :

UPDATE `t_service`
SET `failure_count`=$1,`updated_at`=$2
WHERE `id` = $3

finissent en SequelizeTimeoutError.

Cela donne donc, à ce stade, la séquence suivante :

Mise à jour appareil Z2M
    ↓
device.create()
    ↓
deviceInDb.update()                  OK
    ↓
nettoyage des anciennes features
    ↓
child_lock absent du nouveau device
    ↓
existingFeature.destroy({transaction})
    ↓
BLOCAGE
    ↓
transaction SQLite restant ouverte
    ↓
autres écritures
    ↓
SQLITE_BUSY / database is locked

À propos de l’énergie

Le problème est apparu pendant que j’essayais de récupérer les fonctionnalités de consommation d’énergie de cette prise, ce qui m’avait initialement orienté vers le nouveau code d’energy monitoring.

Cependant les traces montrent maintenant que le blocage intervient avant la création/mise à jour des features énergie.

La feature energy existante est d’ailleurs correctement reconnue :

zigbee2mqtt:Station informatique:switch:energy:energy matched=true

Le blocage est déclenché par la tentative de suppression de l’ancienne feature child_lock.

Autre observation

Pendant le freeze, le service Energy Monitoring continue à démarrer ses traitements et indique notamment :

Found 52 energy devices
Found 0 devices with both INDEX and thirty-minutes-consumption features

Puis les SQLITE_BUSY apparaissent sur différentes parties de Gladys.

J’ai également vérifié t_job : les jobs de migration SQLite → DuckDB et de purge des états orphelins DuckDB sont indiqués success.

Patch testé sans succès

J’avais également testé une modification de device.create.js afin de ne déclencher la purge des états que lorsque keep_history passe réellement de true à false :

const keepHistoryBeforeUpdate = deviceFeature.keep_history;

await deviceFeature.update(featureToUpdate, { transaction });

if (keepHistoryBeforeUpdate !== false && deviceFeature.keep_history === false) {
  deviceFeaturesIdsToPurge.push(deviceFeature.id);
}

Cela ne corrige pas ce problème : grâce aux traces supplémentaires, on sait maintenant que le freeze intervient plus tôt, pendant le destroy() de la feature obsolète.

État actuel du diagnostic

Le point de blocage reproductible est donc maintenant identifié assez précisément :

existingFeature.destroy({ transaction })

pour :

zigbee2mqtt:Station informatique:access-control:mode:child_lock

Il faudrait initier une tâche de destruction des états en asynchrone pour pouvoir continuer le travail.

Mise à jour : cause du blocage identifiée plus précisément

J’ai continué à investiguer le freeze lors de la mise à jour d’un périphérique Zigbee2MQTT.

Le périphérique concerné est :

  • Station informatique
  • external ID : zigbee2mqtt:Station informatique

J’ai ajouté temporairement des logs de debug autour du nettoyage des features dans device.create.js.

Lors de la mise à jour, Gladys arrive normalement jusqu’au nettoyage des features :

[DEBUG DEVICE CREATE] START zigbee2mqtt:Station informatique
[DEBUG DEVICE CREATE] BEFORE getDeviceInDb zigbee2mqtt:Station informatique
[DEBUG DEVICE CREATE] AFTER getDeviceInDb zigbee2mqtt:Station informatique
[DEBUG DEVICE CREATE] BEFORE device update zigbee2mqtt:Station informatique
[DEBUG DEVICE CREATE] AFTER device update zigbee2mqtt:Station informatique
[DEBUG DEVICE CREATE] BEFORE feature cleanup zigbee2mqtt:Station informatique
[DEBUG FEATURE CLEANUP] zigbee2mqtt:Station informatique:switch:binary:state matched= true
[DEBUG FEATURE CLEANUP] zigbee2mqtt:Station informatique:switch:power:power matched= true
[DEBUG FEATURE CLEANUP] zigbee2mqtt:Station informatique:switch:current:current matched= true
[DEBUG FEATURE CLEANUP] zigbee2mqtt:Station informatique:switch:voltage:voltage matched= true
[DEBUG FEATURE CLEANUP] zigbee2mqtt:Station informatique:switch:energy:energy matched= true
[DEBUG FEATURE CLEANUP] zigbee2mqtt:Station informatique:access-control:mode:child_lock matched= false
[DEBUG FEATURE DESTROY] BEFORE zigbee2mqtt:Station informatique:access-control:mode:child_lock
[DEBUG FEATURE CLEANUP] zigbee2mqtt:Station informatique:signal:integer:linkquality matched= true

Il n’y a jamais de log AFTER après la tentative de suppression de child_lock.

Le blocage se produit donc ici dans device.create.js :

await existingFeature.destroy({ transaction });

Pourquoi cette feature est très longue à supprimer

J’ai vérifié les lignes faisant référence à cette feature précise :

feature id:
557c3bba-bcc2-47e7-9f7f-f39614093509

energy_children    0
states             0
aggregates         509363
supported_options  0

La feature n’a donc plus d’états classiques dans SQLite, mais elle possède encore 509 363 lignes dans :

t_device_feature_state_aggregate

La clé étrangère est configurée ainsi :

t_device_feature_state_aggregate.device_feature_id
    -> t_device_feature.id
    ON DELETE CASCADE

La table des agrégats possède bien des index, notamment :

t_device_feature_state_aggregate_device_feature_id
t_device_feature_state_aggregate_device_feature_id_type_created_at

La suppression de la feature obsolète child_lock entraîne donc la suppression en cascade de plus de 500 000 agrégats SQLite, directement dans la transaction de mise à jour du périphérique.

Pendant cette opération, les autres écritures SQLite commencent à échouer avec :

SequelizeTimeoutError
SQLITE_BUSY: database is locked

Par exemple, les mises à jour de t_service.failure_count effectuées par les intégrations externes finissent par expirer.

La requête HTTP de Gladys finit également par dépasser son délai, ce qui donne l’impression que toute l’interface est figée.

Gladys gère déjà ce problème dans un autre chemin de code

Il est intéressant de constater que device.destroy.js contient déjà une protection explicite contre ce type de situation lors de la suppression complète d’un périphérique.

Le code compte les états et les agrégats avant de supprimer le périphérique. Lorsqu’il y en a trop, il évite la suppression en cascade immédiate et déclenche à la place :

this.eventManager.emit(
  EVENTS.DEVICE.PURGE_STATES_SINGLE_FEATURE,
  deviceFeature.id
);

La suppression du périphérique est alors interrompue afin que les historiques puissent être nettoyés auparavant.

De son côté, device.purgeStatesByFeatureId.js supprime volontairement les agrégats SQLite par lots :

DELETE FROM t_device_feature_state_aggregate WHERE id IN (
  SELECT id FROM t_device_feature_state_aggregate
  WHERE device_feature_id = :id
  LIMIT :limit
);

avec une temporisation entre les lots.

Les commentaires présents dans le code indiquent explicitement que ce fonctionnement sert notamment à éviter de bloquer la base pendant une longue période.

Incohérence possible entre les deux chemins de suppression

Il semble donc y avoir une différence de comportement entre la suppression complète d’un périphérique et la suppression d’une feature devenue obsolète pendant une mise à jour.

Pour la suppression d’un périphérique :

device.destroy()
    -> compte les états
    -> s’il y en a trop :
       PURGE_STATES_SINGLE_FEATURE
       -> nettoyage progressif

Alors que lors de la mise à jour d’un périphérique existant :

device.create()
    -> détecte une feature qui n’existe plus
    -> existingFeature.destroy({ transaction })
    -> ON DELETE CASCADE SQLite
    -> suppression synchrone d’environ 509k agrégats
    -> verrou SQLite prolongé

Dans mon cas, la feature devenue obsolète est child_lock.

Pourquoi le problème semblait initialement lié à l’énergie

Le problème a été découvert lorsque j’essayais de mettre à jour ma prise Station informatique.

La prise était déjà présente dans Gladys, mais il lui manquait la partie liée à la consommation d’énergie que je souhaitais récupérer lors de la mise à jour depuis Zigbee2MQTT.

Au départ, le freeze semblait donc lié à l’ajout des nouvelles features d’énergie.

Les traces montrent finalement que le blocage intervient plus tôt : pendant le nettoyage des anciennes features, lorsque Gladys tente de supprimer child_lock.

Ce n’est donc vraisemblablement pas directement la création des nouvelles features d’énergie qui bloque, mais la suppression d’une ancienne feature possédant un très grand nombre d’agrégats historiques.

Piste de correction

Remplacer simplement :

await existingFeature.destroy({ transaction });

par un événement PURGE_STATES_SINGLE_FEATURE ne semble pas suffisant.

Cela permettrait de purger progressivement l’historique, mais la feature obsolète elle-même resterait en base.

Il faudrait probablement un mécanisme permettant de :

  1. détecter qu’une feature devenue obsolète possède beaucoup d’historique ;
  2. éviter son DELETE CASCADE dans la transaction de mise à jour ;
  3. purger ses états et agrégats progressivement en arrière-plan ;
  4. supprimer ensuite la feature devenue obsolète ;
  5. vérifier éventuellement qu’elle est toujours obsolète au moment de la suppression, au cas où l’intégration l’aurait exposée à nouveau entre-temps.

J’arrête mes investigations ici. J’espere que ça vous aidera :slight_smile:

Parfait. Tu fais une PR ? ou tu veux qu’on le fasse ?

euh je préfère que ce soit fait par vous stp :slight_smile:

C’est a mon sens une brique un peu trop technique pour que j’y touche :confused:

En continuant l’analyse, je me rends compte que tu as beaucoup d’états aggrégés dans t_device_feature_state_aggregate qui n’est plus utilisée. Tout est dans DuckDB (états et aggrégats calculés à la volée).

Je pense que la purge ici ne purge que les états et pas cette table :

Autant supprimer les données et ne pas implémenter le fix sur une table qui est censée être vide. Je continue l’investigation :detective:

Je soupçonne qu’il te reste des états aggrégés non purgés. J’ai ouvert une PR pour les afficher ici Show the remaining SQLite aggregates in the DuckDB migration card by cicoub13 · Pull Request #2952 · GladysAssistant/Gladys · GitHub

Mais si tu veux pas attendre, je pense que tu peux cliquer sur le bouton Purger, puis Nettoyer la base de données SQLite. Ton problème de mise à jour Zigbee devrait disparaître

je comprend pas j’ai déjà fais a l’époque de l’arrivé de duckdb

Je vien de cliquer sur purgé et je re freeze a nouveau
J’ai l’impression que je suis bloqué..
Pour me debloquer il faut je purge mais pour purger je bloque ahah help :face_with_spiral_eyes:

Je pense que tu n’es jamais allé jusqu’au bout. Clique sur Purger à un moment où tu n’as pas besoin de Gladys et laisse tourner. Même si tu n’as pas de retour visuel, la purge se fait.

@cicoub13 pour toi la table ne sert plus ?

La table ne sert plus (sont contenu en tout cas). Il ne faut pas supprimer la table car des migrations y font toujours référence et je ne suis pas sûr du comportement si la table disparaît complètement.

Si tu es à l’aise, tu peux

  • éteindre Gladys proprement
  • Sauvegarder les fichiers de base de données
  • TRUNCATE la table
  • Démarrer Gladys

Si tu veux plus d’assurance sur tout ça, tu peux attendre que @pierre-gilles donne son avis.

C’est ce que je pensais faire.
Merci de ton retour je vais attendre son avis, peut être qu’il aura une meilleure idée :slight_smile:

tu peux partir sur ça déjà, ça suffit amplement pour un cache RW : Lot SSD NVME 256GB X2 - Accessoires informatique

Après sur mon synology, j’avais mis les 2 ssd en stockage pour les vm (pas possible pour les docker directement) mais pas de sauvegarde par contre. Attention car méthode non officielle.

Retour après nettoyage de la base

J’ai finalement réussi à corriger le problème (trop impatient que je suis).

Ma base SQLite gladys-production.db avait atteint environ 12 Go.

1. Vérification de l’intégrité gladys arrêté

J’ai commencé par créé un backup de ma base puis j’ai lancer PRAGMA quick_check;.

Sur mon NAS, l’opération a été extrêmement longue à cause des performances I/O.

Le quick_check a mis environ 3 heures à se terminer, mais a finalement retourné ok.

La base SQLite n’était donc pas corrompue.

Pendant le contrôle, le processus sqlite3 était régulièrement en état D (disk sleep) et les compteurs /proc/<pid>/io montraient que les lectures continuaient bien à progresser.

2. Identification de la table problématique

Le problème venait principalement de la table t_device_feature_state_aggregate.

La base faisait environ 12 Go, et cette table avec ses index semblait représenter l’immense majorité de l’espace utilisé.

J’ai donc arrêté Gladys avant d’intervenir sur la base.

3. Suppression et recréation de la table

Plutôt que de faire un énorme DELETE FROM t_device_feature_state_aggregate;, j’ai choisi de supprimer complètement la table puis de la recréer avec son schéma et ses index d’origine.

Là encore, l’opération a été très longue à cause des I/O du NAS.

Une première tentative a été interrompue après environ 1 h 30, à cause d’une coupure de ma session SSH.

À ce moment-là, SQLite avait déjà effectué environ 4,2 Go de lectures (read_bytes: 4 197 961 728).

Comme le COMMIT n’avait pas eu lieu, SQLite a correctement annulé la transaction et l’ancienne table était toujours présente.

4. Deuxième tentative avec nohup

J’ai donc relancé l’opération avec nohup afin qu’elle survive à une éventuelle déconnexion SSH.

Cette fois, l’opération est allée jusqu’au bout.

Quelques mesures pendant le DROP TABLE :

Temps écoulé Données lues
~22 min 0,73 Go
~1 h 04 2,75 Go
~1 h 32 4,20 Go
~1 h 50 5,02 Go
~2 h 20 6,02 Go
~2 h 30 9,44 Go
~2 h 39 10,81 Go

L’opération complète a finalement pris environ 2 h 45.

Le processus était presque constamment en état D (disk sleep), avec très peu de CPU utilisé. Le facteur limitant était donc clairement les I/O du NAS.

5. Vérification après recréation

Après la fin de l’opération :

SELECT COUNT(*) FROM t_device_feature_state_aggregate;

retourne 0.

La table a donc bien été recréée vide.

J’ai ensuite effectué PRAGMA wal_checkpoint(TRUNCATE);.

Résultat : 0|0|0.

Le WAL a donc été correctement vidé.

6. Espace récupéré

Le fichier SQLite fait toujours physiquement environ 12 Go, ce qui est normal sans VACUUM.

En revanche :

  • page_count = 3 118 963
  • freelist_count = 3 098 841

Cela signifie qu’environ 99,35 % des pages de la base sont maintenant libres.

L’espace n’a donc pas encore été rendu au système de fichiers, mais SQLite peut désormais le réutiliser.

Je n’ai volontairement pas encore lancé de VACUUM.

7. Résultat après redémarrage de Gladys

J’ai ensuite redémarré Gladys.

Tout fonctionne normalement.

Et surtout, j’ai refait le test qui posait problème : la modification d’un device qui était auparavant extrêmement lente/bloquée.

Cette fois, la modification du device s’est faite en quelques secondes.

Conclusion

  • PRAGMA quick_check : OK, mais environ 3 h
  • base SQLite : environ 12 Go
  • t_device_feature_state_aggregate représentait pratiquement toute la base
  • première tentative de suppression interrompue après ~1 h 30
  • deuxième tentative complète : ~2 h 45
  • freelist_count : 3 098 841 pages libres sur 3 118 963, soit ~99,35 %
  • après redémarrage, Gladys fonctionne normalement
  • la modification du device problématique se fait maintenant en quelques secondes

Le souci était donc bien lié à l’explosion de t_device_feature_state_aggregate et aux performances I/O nécessaires pour travailler sur cette énorme table.

Reste a faire

  • Supprimer mon bckp
  • faire un vacuum
  • mettre des ssd nvme :sweat_smile:

Merci @cicoub13 @mutmut