Limpieza de estados: error de 2 años corregido + limpieza automática + página Tareas mejorada (PRs #2650, #2651, #2652)

Al probar la nueva vista de Actividad de la 4.82, nos encontramos con algo inesperado — y eso dio lugar a tres PRs complementarias. ¡Enorme agradecimiento a @Will_71 por sus pruebas en su propia base! :folded_hands:

Episodio 1 — el bug invisible desde hace 2 años (PR #2650)

Al querer purgar los estados de un sensor parloteador (desmarcar « Conservar el historial »), la tarea respondía instantáneamente: 0 estados para eliminar… mientras que la vista de Actividad mostraba miles de estados para esta función.

Verdict después de arqueología git:

Pregunta Respuesta
Roto desde v4.45.0 (agosto 2024)
Causa La migración DuckDB (#2104) movió los estados, pero se olvidaron 2 consultas SQLite
Lugar 1 purgeStatesByFeatureId (el toggle « Conservar el historial ») contaba y eliminaba en SQLite, ahora vacío → no-op total
Lugar 2 El guardián de device.destroy (« too much states ») también contaba en SQLite → protección anti-bloqueo muerta
¿Por qué invisible? Nada mostraba el historial por función… hasta la vista de Actividad

La #2650 migra las dos a DuckDB (misma consulta que la que destroy ya usaba) y mantiene la limpieza de los restos SQLite para las instalaciones no migradas.

Episodio 2 — ¿cuántos estados huérfanos tienes? (PR #2651)

Consecuencia del bug: cada dispositivo/función eliminado en los últimos 2 años pudo dejar sus estados « huérfanos » en DuckDB — invisibles, pero escaneados por cada consulta y ocupando disco.

Sobre propuesta de @pierre-gilles: una tarea automática one-shot al inicio (patrón de la migración DuckDB: variable de sistema puesta solo al final del run → si Gladys se reinicia en medio, se reanuda sola en el siguiente arranque — probado en vivo cortando el contenedor en medio :scissors:).

Y « la más lenta posible »: sin conteo previo (que mantenía la conexión de lectura 15-20 min en mi base), recorrido en tranches semanales con una pausa de 5× la duración de cada tranche — la purga nunca consume más que una fracción de los recursos y se adapta sola a la máquina.

Prueba de campo Base Huérfanos encontrados Duración Impacto sentido
Yo (servidor compartido con HA + 2ª Gladys) 448 M de estados 20 :grinning_face_with_smiling_eyes: 1 h 49 (cache frío) / 29 min (caliente) Vista de Actividad: 125 ms durante la purga (frente a 44 s con la 1ª versión no limitada)
@Will_71 (portátil Fedora) 228 M de estados 45 400 000 (~20 % de su base !) ~29 min « Sin ralentización, quizás +2 s en la Actividad »

Los 45 M de Will confirman que esta limpieza automática era necesaria para todos.


Antes/Después :

¡Enorme agradecimiento a @Will_71 por las pruebas completas de las 2 PRs

Episodio 3 — la página de Tareas que habría detectado el bug en un día (PR #2652)

Si la página de Tareas hubiera mostrado « 0 estado encontrado » frente a una Actividad llena, el bug de purga no habría durado 2 años. Ahora es el caso — las tareas llevan datos estructurados (traducidos del lado del front, por lo tanto en todos los idiomas):

  • Objetivo: Detector de presencia cocina › Detección de movimiento
  • Pasos en directo: Esperando la base de datos… / Cálculo del número de estados… / Eliminación de los estados… / Eliminación de los agregados… (dos purgas simultáneas muestran honestamente una que trabaja y otra que espera)
  • Conteos: Estados para eliminar: N DuckDB + N SQLite — N agregados, luego Estados eliminados: … en informe persistente
  • Contador en vivo para la purga de huérfanos, % por tranches, duración que avanza para las tareas en curso

Diseño validado por los intercambios en la #2528: ninguna modificación del wrapper de jobs — las tareas se lanzan por el patrón de evento existente, y cada job adjunta sus datos a través de un parámetro opcional de updateProgress.

Para la revisión

@pierre-gilles las 3 PRs están listas (pruebas + cobertura 100 % en las líneas modificadas, CI verde, validadas en vivo en dos instalaciones). Orden de merge: #2650 → #2651 → #2652 (la última está apilada sobre las dos primeras, su diff se reducirá después de sus merges).

Qué chulo cuando un desarrollo actualiza un bug oculto :grin:

¡Qué chulo @Terdious gracias por los desarrollos y gracias @Will_71 por las pruebas!! :smiley:

¡Voy a echar un vistazo!

@Terdious ¿Podrías lanzar Fable 5 sobre los comentarios de CodeRabbit?

Hay cosas realmente críticas :smiley:

¡Listo!

CodeRabbit tenía razón en el punto crítico: el último lote sin límite evaluaba los estados escritos durante la purga contra una instantánea fija de las características — un dispositivo emparejado durante las ~30 min de purga podía perder sus primeros estados. Corregido con un corte capturado antes de la instantánea, aplicado a todos los lotes, + prueba de regresión. La prueba faltante de la rama « cero características » también se ha añadido; la advertencia de inyección SQL es un falso positivo (solo placeholders), respondida en la PR.

Mea culpa compartido con Fable 5 en este caso: dos decisiones razonables por separado (lote final abierto para cubrir los estados escritos durante la ejecución + instantánea de las características al inicio) se volvieron peligrosas combinadas — y no había lanzado una nueva ronda de revisión dedicada al final del desarrollo con Fable. Buena ilustración de que la revisión cruzada bot + agente + humano captura ángulos muertos diferentes :grinning_face_with_smiling_eyes:

@pierre-gilles,

He visto que has fusionado #2651, así que he resuelto los conflictos en la PR #2652

@Terdious gracias por la PR, está bien pero creo que estamos acoplando demasiado los datos específicos de cada tipo de trabajo con el objeto de trabajo genérico.

Propongo una PR corregida:

Dime si te parece bien :slight_smile:

@pierre-gilles,

Lo he leído todo, estoy totalmente de acuerdo con el análisis y con la corrección, es más limpio que un catálogo « de todo un poco ». ¡Gracias a ti!!

Hice la prueba en casa antes del lanzamiento en producción (esta tarde):

Tengo 40 millones de estados, pero nunca he caído en el caso del error ^^

Al final, mejor ^^
Lo mismo con 400 millones, yo solo tenía 20 ^^ Pero nunca elimino equipos / funciones :wink: :sweat_smile:

Pero @Will_71 tenía muchísimos !!