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!
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 ).
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
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)
¡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).
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
@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.