Congelación durante la actualización de un dispositivo Z2M

Hola a todos,

¡ESTO ES UNA LLAMADA DE AYUDA XD

Estoy intentando actualizar mis dispositivos, pero en algunos Gladys se congela, supongo que porque tienen muchos estados.
Tengo que comprarme dos SSD NVME para mi NAS a final de año para mejorar mi RAM (que los precios están caros ahora :eyes:)

  • ¿Qué puedo hacer mientras tanto?
  • ¿Qué debo buscar en los registros para verificar si Gladys está atascada o si está trabajando?

Gracias de antemano (mientras tanto, mi domótica está en el limbo :smiley:

¿Tienes fácil acceso a la base de datos? ¿Estás listo para eliminar estados antiguos para estos dispositivos?
Si es así, podemos guiarte para ejecutar consultas manualmente.

De lo contrario, podemos considerar que es un error y mejorar la migración para que se realice por partes o en segundo plano.

¿Estás intentando actualizar en la interfaz de Gladys para z2m? ¿Para añadir una funcionalidad?

Sí, sobre eso no hay problema :slight_smile:

Intentaba actualizar mi toma para añadir las funciones de consumo de energía desde la interfaz de Gladys en descubrimiento y luego MAJ

Lo mejor sería no borrar :smiley:

Continué el diagnóstico con ChatGPT, aprovechando que Gladys aún estaba en el estado « freeze ».

El problema aparece durante la actualización/sincronización de un dispositivo Zigbee2MQTT desde Gladys. En mi caso, se trataba de un enchufe que debía recuperar nuevas funcionalidades relacionadas con la energía/consumo.

Varias verificaciones permitieron descartar algunas pistas:

  • El contenedor Gladys no está saturado: aproximadamente 3 % de CPU y 2,4 GB de RAM sobre 19 GB disponibles durante el freeze.
  • Zigbee2MQTT tampoco parece saturado.
  • El proceso Node de Gladys sigue vivo.
  • GET / en Gladys responde inmediatamente con HTTP 200, por lo que Express sigue siendo capaz de servir el frontend estático.
  • En cambio, las llamadas a la API dinámica, por ejemplo, la recuperación de dispositivos a través de mi proxy, terminan en tiempo de espera.
  • SQLite sigue accesible desde otro proceso durante el freeze: las consultas simples responden en aproximadamente 0,5 segundos.
  • El WAL de SQLite tenía aproximadamente 1 GB, pero PRAGMA wal_checkpoint(PASSIVE) devuelve 0|227|227, por lo que no hay indicación de un checkpoint bloqueado.
  • t_device_feature_state contiene 0 filas (los historiales se han migrado a DuckDB) y t_device_feature solo 446 filas.

Con ChatGPT, luego inspeccionamos directamente el código presente en mi contenedor Gladys.

La parte Zigbee2MQTT getDiscoveredDevices.js contiene los parches recientes relacionados con las características de energía: conservación de las características consumption/cost, fusión con el dispositivo existente, luego llamada a addEnergyFeatures().

En cambio, en mi device.create.js, después de la actualización de una característica, actualmente tengo:

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

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

ChatGPT comparó este comportamiento con el código actual de Gladys e identificó una diferencia importante. El código corregido memoriza el valor anterior:

const keepHistoryBeforeUpdate = deviceFeature.keep_history;

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

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

La diferencia es que en mi versión, cada actualización de un dispositivo puede solicitar una purga para todas las características que ya tienen keep_history=false, incluso si este valor no ha cambiado.

Después de la transacción, Gladys emite luego:

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

La hipótesis actual propuesta por ChatGPT es que las actualizaciones Z2M provocan una acumulación de tareas de purga innecesarias. Estos tratamientos pasan en particular por DuckDB y podrían acumularse hasta hacer que las API dinámicas de Gladys no sean reactivas, mientras que el proceso Node en sí sigue funcionando.

Esta hipótesis coincide particularmente bien con el comportamiento observado: CPU/RAM normales + frontend estático accesible + SQLite accesible + API Gladys que se queda sin tiempo.

No es la misma integración, pero se parece mucho a lo que tuve y que abrí en Issue: Overkiz integration: assigning a room to a device takes several minutes (freezes the interface) · Issue #2900 · GladysAssistant/Gladys · GitHub

@pierre-gilles @cicoub13 pequeño avance

Resumen por chatGPT:

Congelamiento completo de Gladys al actualizar un dispositivo Zigbee2MQTT

Encuentro un bloqueo reproducible de Gladys cuando actualizo desde la interfaz Zigbee2MQTT un enchufe llamado Estación informática.

Originalmente, solo quería actualizar este dispositivo porque algunas funciones relacionadas con la energía/consumo estaban ausentes.

Síntomas

Al actualizar el dispositivo desde Gladys:

  • la solicitud termina en tiempo de espera;

  • la interfaz se vuelve parcialmente o totalmente inutilizable;

  • las escrituras SQLite comienzan a fallar con:

    SequelizeTimeoutError
    SQLITE_BUSY: database is locked

Por ejemplo:

No se puede restablecer el contador de fallos de la integración ...
SQLITE_BUSY: database is locked

El servidor HTTP en sí sigue respondiendo en el puerto 8455.

Configuración DB

La base de datos SQLite es bastante grande:

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

SQLite funciona en WAL:

PRAGMA journal_mode;
wal

Un checkpoint pasivo funciona:

PRAGMA wal_checkpoint(PASSIVE);
0|227|227

Las consultas simples en SQLite siguen siendo rápidas:

SELECT COUNT(*) FROM t_device_feature;
446

real 0m0.090s

Y:

SELECT COUNT(*) FROM t_device_feature_state;
0

Los estados parecen haber sido migrados correctamente a DuckDB.

Dispositivo afectado

El dispositivo es:

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

external_id:
zigbee2mqtt:Estación informática

Antes del intento de actualización, sus características registradas son, entre otras:

Interruptor
Potencia consumida
Intensidad consumida
Tensión media
Energía consumida
Intensidad de la señal
Modo de control de acceso

La característica « Modo de control de acceso » corresponde a:

zigbee2mqtt:Estación informática:access-control:mode:child_lock

Investigación en device.create.js

He instrumentado:

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

a fin de determinar exactamente dónde se bloquea la transacción.

El inicio de la actualización funciona normalmente:

[DEBUG DEVICE CREATE] START zigbee2mqtt:Estación informática
[DEBUG DEVICE CREATE] BEFORE getDeviceInDb zigbee2mqtt:Estación informática
[DEBUG DEVICE CREATE] AFTER getDeviceInDb zigbee2mqtt:Estación informática
[DEBUG DEVICE CREATE] BEFORE device update zigbee2mqtt:Estación informática
[DEBUG DEVICE CREATE] AFTER device update zigbee2mqtt:Estación informática
[DEBUG DEVICE CREATE] BEFORE feature cleanup zigbee2mqtt:Estación informática

El bloqueo ocurre durante esta parte de device.create.js:

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

Luego instrumenté cada característica.

Resultado:

[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

Luego:

[DEBUG FEATURE CLEANUP] zigbee2mqtt:Estación informática:access-control:mode:child_lock matched= false
[DEBUG FEATURE DESTROY] BEFORE zigbee2mqtt:Estación informática:access-control:mode:child_lock

Y no aparece ningún DEBUG FEATURE DESTROY AFTER.

La siguiente característica aún es inspeccionada por el Promise.map:

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

pero el Promise.map nunca termina porque el destroy() de child_lock sigue bloqueado.

Lo que parece estar ocurriendo

La nueva definición proporcionada por Zigbee2MQTT ya no contiene la característica:

access-control:mode:child_lock

Gladys considera, por lo tanto, lógicamente esta antigua característica como eliminada y ejecuta:

await existingFeature.destroy({ transaction });

Es precisamente esta llamada la que no devuelve nada.

La transacción device.create() permanece entonces abierta.

Poco después, otros componentes de Gladys intentan escribir en SQLite y comienzan a producir:

SQLITE_BUSY: database is locked

Por ejemplo, las actualizaciones de t_service:

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

terminan en SequelizeTimeoutError.

Esto da, por lo tanto, en este punto, la siguiente secuencia:

Actualización dispositivo Z2M
    ↓
device.create()
    ↓
deviceInDb.update()                  OK
    ↓
limpieza de las antiguas características
    ↓
child_lock ausente del nuevo dispositivo
    ↓
existingFeature.destroy({transaction})
    ↓
BLOQUEO
    ↓
transacción SQLite permaneciendo abierta
    ↓
otras escrituras
    ↓
SQLITE_BUSY / database is locked

Sobre la energía

El problema apareció mientras intentaba recuperar las funciones de consumo de energía de este enchufe, lo que me había orientado inicialmente hacia el nuevo código de monitoreo de energía.

Sin embargo, los registros muestran ahora que el bloqueo ocurre antes de la creación/actualización de las características de energía.

La característica energy existente es correctamente reconocida:

zigbee2mqtt:Estación informática:switch:energy:energy matched=true

El bloqueo es desencadenado por el intento de eliminación de la antigua característica child_lock.

Otra observación

Durante el congelamiento, el servicio Energy Monitoring continúa iniciando sus procesos e indica, entre otras cosas:

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

Luego aparecen los SQLITE_BUSY en diferentes partes de Gladys.

También verifiqué t_job: los trabajos de migración SQLite → DuckDB y de purga de los estados huérfanos DuckDB están indicados como success.

Parche probado sin éxito

También había probado una modificación de device.create.js para desencadenar la purga de los estados solo cuando keep_history realmente cambia de true a false:

const keepHistoryBeforeUpdate = deviceFeature.keep_history;

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

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

Esto no corrige este problema: gracias a los registros adicionales, ahora sabemos que el congelamiento ocurre antes, durante el destroy() de la característica obsoleta.

Estado actual del diagnóstico

El punto de bloqueo reproducible está ahora identificado con bastante precisión:

existingFeature.destroy({ transaction })

para:

zigbee2mqtt:Estación informática:access-control:mode:child_lock

Sería necesario iniciar una tarea de destrucción de estados de forma asíncrona para poder continuar el trabajo.

Actualización: causa del bloqueo identificada con mayor precisión

He seguido investigando el bloqueo durante la actualización de un dispositivo Zigbee2MQTT.

El dispositivo afectado es:

  • Station informatique
  • ID externa: zigbee2mqtt:Station informatique

He añadido temporalmente registros de depuración alrededor de la limpieza de las características en device.create.js.

Durante la actualización, Gladys llega normalmente hasta la limpieza de las características:

[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

Nunca hay un registro AFTER después del intento de eliminación de child_lock.

El bloqueo ocurre aquí en device.create.js:

await existingFeature.destroy({ transaction });

Por qué esta característica tarda tanto en eliminarse

He verificado las líneas que hacen referencia a esta característica específica:

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

energy_children    0
states             0
aggregates         509363
supported_options  0

La característica ya no tiene estados clásicos en SQLite, pero aún posee 509,363 líneas en:

t_device_feature_state_aggregate

La clave externa está configurada así:

t_device_feature_state_aggregate.device_feature_id
    -> t_device_feature.id
    ON DELETE CASCADE

La tabla de agregados tiene índices, incluyendo:

t_device_feature_state_aggregate_device_feature_id
t_device_feature_state_aggregate_device_feature_id_type_created_at

La eliminación de la característica obsoleta child_lock provoca la eliminación en cascada de más de 500,000 agregados SQLite, directamente en la transacción de actualización del dispositivo.

Durante esta operación, otras escrituras en SQLite comienzan a fallar con:

SequelizeTimeoutError
SQLITE_BUSY: database is locked

Por ejemplo, las actualizaciones de t_service.failure_count realizadas por las integraciones externas terminan por expirar.

La solicitud HTTP de Gladys también termina por exceder su tiempo límite, lo que da la impresión de que toda la interfaz está congelada.

Gladys ya maneja este problema en otro camino de código

Es interesante notar que device.destroy.js ya contiene una protección explícita contra este tipo de situación durante la eliminación completa de un dispositivo.

El código cuenta los estados y los agregados antes de eliminar el dispositivo. Cuando hay demasiados, evita la eliminación en cascada inmediata y en su lugar desencadena:

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

La eliminación del dispositivo se interrumpe entonces para que los historiales puedan limpiarse antes.

Por su parte, device.purgeStatesByFeatureId.js elimina deliberadamente los agregados SQLite por lotes:

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
);

con una temporización entre los lotes.

Los comentarios presentes en el código indican explícitamente que esta funcionalidad sirve, entre otras cosas, para evitar bloquear la base de datos durante un período prolongado.

Posible incoherencia entre los dos caminos de eliminación

Parece haber una diferencia de comportamiento entre la eliminación completa de un dispositivo y la eliminación de una característica que se ha vuelto obsoleta durante una actualización.

Para la eliminación de un dispositivo:

device.destroy()
    -> cuenta los estados
    -> si hay demasiados:
       PURGE_STATES_SINGLE_FEATURE
       -> limpieza progresiva

Mientras que durante la actualización de un dispositivo existente:

device.create()
    -> detecta una característica que ya no existe
    -> existingFeature.destroy({ transaction })
    -> ON DELETE CASCADE SQLite
    -> eliminación síncrona de aproximadamente 509k agregados
    -> bloqueo prolongado de SQLite

En mi caso, la característica que se ha vuelto obsoleta es child_lock.

Por qué el problema parecía inicialmente relacionado con la energía

El problema se descubrió cuando intentaba actualizar mi toma Station informatique.

La toma ya estaba presente en Gladys, pero le faltaba la parte relacionada con el consumo de energía que quería recuperar durante la actualización desde Zigbee2MQTT.

Al principio, el bloqueo parecía relacionado con la adición de las nuevas características de energía.

Las trazas muestran finalmente que el bloqueo ocurre antes: durante la limpieza de las características antiguas, cuando Gladys intenta eliminar child_lock.

Por lo tanto, probablemente no sea la creación de las nuevas características de energía lo que bloquea, sino la eliminación de una característica antigua que posee un gran número de agregados históricos.

Posible corrección

Reemplazar simplemente:

await existingFeature.destroy({ transaction });

por un evento PURGE_STATES_SINGLE_FEATURE no parece suficiente.

Esto permitiría purgar progresivamente el historial, pero la característica obsoleta en sí permanecería en la base de datos.

Probablemente se necesite un mecanismo que permita:

  1. detectar que una característica que se ha vuelto obsoleta tiene mucho historial;
  2. evitar su DELETE CASCADE en la transacción de actualización;
  3. purgar sus estados y agregados progresivamente en segundo plano;
  4. eliminar luego la característica que se ha vuelto obsoleta;
  5. verificar eventualmente que sigue siendo obsoleta en el momento de la eliminación, en caso de que la integración la haya expuesto nuevamente entre tanto.

Detengo mis investigaciones aquí. Espero que esto les ayude :slight_smile:

Perfecto. ¿Haces una PR? ¿O quieres que lo hagamos nosotros?

eh, prefiero que lo hagas tú, por favor :slight_smile:

Creo que es un componente un poco demasiado técnico para que yo lo toque :confused:

Al continuar con el análisis, me doy cuenta de que tienes muchos estados agregados en t_device_feature_state_aggregate que ya no se utilizan. Todo está en DuckDB (estados y agregados calculados sobre la marcha).

Creo que la limpieza aquí solo elimina los estados y no esta tabla:

Más vale eliminar los datos y no implementar el parche en una tabla que debería estar vacía. Sigo investigando :detective:

Creo que aún tienes estados agregados no purgados. He abierto una PR para mostrarlos aquí Show the remaining SQLite aggregates in the DuckDB migration card by cicoub13 · Pull Request #2952 · GladysAssistant/Gladys · GitHub

Pero si no quieres esperar, creo que puedes hacer clic en el botón Purge (Purgar), luego Clean Database (Limpiar la base de datos SQLite). Tu problema de actualización Zigbee debería desaparecer.

no entiendo, ya lo hice en la época de la llegada de duckdb

acabo de hacer clic en purgar y vuelvo a congelarme
me da la impresión de que estoy atascado..
para desbloquearme tengo que purgar, pero para purgar me bloqueo jajaja help :face_with_spiral_eyes:

Creo que nunca has llegado hasta el final. Haz clic en Purge en un momento en el que no necesites Gladys y déjalo funcionar. Aunque no tengas retroalimentación visual, la purga se realiza.

@cicoub13 ¿para ti la mesa ya no sirve?

La tabla ya no se usa (o al menos su contenido). No hay que borrar la tabla porque las migraciones aún hacen referencia a ella y no estoy seguro del comportamiento si la tabla desaparece por completo.

Si te sientes cómodo, puedes

  • Apagar Gladys correctamente
  • Hacer una copia de seguridad de los archivos de la base de datos
  • TRUNCATE la tabla
  • Iniciar Gladys

Si quieres más seguridad sobre todo esto, puedes esperar a que @pierre-gilles dé su opinión.

Era lo que pensaba hacer.
Gracias por tu comentario, voy a esperar su opinión, quizá tenga una mejor idea :slight_smile:

puedes empezar con eso ya, es más que suficiente para una caché RW: https://www.leboncoin.fr/ad/accessoires_informatique/3247461279

Después, en mi Synology, había puesto los 2 SSD en almacenamiento para las VM (no es posible para los Docker directamente) pero sin copia de seguridad, eso sí. ¡Cuidado, método no oficial!

Retorno después de la limpieza de la base

Finalmente logré corregir el problema (demasiado impaciente que soy).

Mi base de datos SQLite gladys-production.db había alcanzado aproximadamente 12 Go.

1. Verificación de la integridad de gladys detenido

Comencé creando una copia de seguridad de mi base de datos y luego ejecuté PRAGMA quick_check;.

En mi NAS, la operación fue extremadamente larga debido al rendimiento de E/S.

El quick_check tardó aproximadamente 3 horas en completarse, pero finalmente devolvió ok.

La base de datos SQLite no estaba corrompida.

Durante la verificación, el proceso sqlite3 estaba regularmente en estado D (disk sleep) y los contadores /proc/<pid>/io mostraban que las lecturas seguían avanzando.

2. Identificación de la tabla problemática

El problema provenía principalmente de la tabla t_device_feature_state_aggregate.

La base de datos tenía aproximadamente 12 Go, y esta tabla con sus índices parecía representar la inmensa mayoría del espacio utilizado.

Por lo tanto, detuve Gladys antes de intervenir en la base de datos.

3. Eliminación y recreación de la tabla

En lugar de hacer un enorme DELETE FROM t_device_feature_state_aggregate;, opté por eliminar completamente la tabla y luego recrearla con su esquema e índices originales.

Una vez más, la operación fue muy larga debido a las E/S del NAS.

Un primer intento fue interrumpido después de aproximadamente 1 hora y 30 minutos, debido a una desconexión de mi sesión SSH.

En ese momento, SQLite ya había realizado aproximadamente 4,2 Go de lecturas (read_bytes: 4 197 961 728).

Como el COMMIT no se había producido, SQLite canceló correctamente la transacción y la tabla antigua seguía presente.

4. Segundo intento con nohup

Por lo tanto, volví a ejecutar la operación con nohup para que sobreviviera a una posible desconexión SSH.

Esta vez, la operación se completó.

Algunas mediciones durante el DROP TABLE:

Tiempo transcurrido Datos leídos
~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

La operación completa finalmente tomó aproximadamente 2 horas y 45 minutos.

El proceso estaba casi constantemente en estado D (disk sleep), con muy poca CPU utilizada. El factor limitante era claramente las E/S del NAS.

5. Verificación después de la recreación

Después de que finalizara la operación:

SELECT COUNT(*) FROM t_device_feature_state_aggregate;

devuelve 0.

Por lo tanto, la tabla se recreó vacía.

Luego ejecuté PRAGMA wal_checkpoint(TRUNCATE);.

Resultado: 0|0|0.

Por lo tanto, el WAL se vació correctamente.

6. Espacio recuperado

El archivo SQLite sigue teniendo físicamente aproximadamente 12 Go, lo cual es normal sin VACUUM.

Por otro lado:

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

Esto significa que aproximadamente 99,35 % de las páginas de la base de datos están ahora libres.

El espacio aún no se ha devuelto al sistema de archivos, pero SQLite ahora puede reutilizarlo.

No he lanzado intencionalmente un VACUUM aún.

7. Resultado después de reiniciar Gladys

Luego reinicié Gladys.

Todo funciona normalmente.

Y lo más importante, volví a realizar la prueba que tenía problemas: la modificación de un dispositivo que antes era extremadamente lenta/bloqueada.

Esta vez, la modificación del dispositivo se realizó en unos pocos segundos.

Conclusión

  • PRAGMA quick_check: OK, pero aproximadamente 3 horas
  • base de datos SQLite: aproximadamente 12 Go
  • t_device_feature_state_aggregate representaba prácticamente toda la base de datos
  • primer intento de eliminación interrumpido después de ~1 hora y 30 minutos
  • segundo intento completo: ~2 horas y 45 minutos
  • freelist_count: 3 098 841 páginas libres de 3 118 963, es decir, ~99,35 %
  • después del reinicio, Gladys funciona normalmente
  • la modificación del dispositivo problemático ahora se realiza en unos pocos segundos

El problema estaba relacionado con la explosión de t_device_feature_state_aggregate y el rendimiento de E/S necesario para trabajar en esta enorme tabla.

Queda por hacer

  • Eliminar mi copia de seguridad
  • hacer un vacuum
  • poner SSDs NVMe :sweat_smile:

Gracias @cicoub13 @mutmut