Deduplicación de episodios de Sentinel — un insight por avería (v1.66.15)
Resumen
Hasta la v1.66.15, cada equipo caído generaba un nuevo insight cada pocas horas mientras durase la avería — el mismo diagnóstico repetido ~14 veces al día, acumulando ~220 insights diarios que nadie podía atender y que caducaban solos sin cierre real. Desde esa versión, una avería es UN único insight que permanece abierto mientras el problema persista: los reportes repetidos del Agente lo mantienen vivo renovando su caducidad, y cuando el equipo se recupera, la auto-resolución lo cierra automáticamente. Desde la v1.87.2, esa caducidad ya no es fija: depende de la categoría del insight (ver más abajo).
Problema anterior
En PROD (medición 30 días previa a v1.66.15):
- Total de insights creados: 6.682
- Duplicados de equipos ya-caídos: 6.617 (~99% del caudal)
- Caso crítico (AU-Camerino): un único equipo generó 431 insights en 30 días
Raíz: create_insight no validaba si ya existía un insight abierto del mismo equipo y causa. El insight expiraba a las 4h; el siguiente sondeo del Agente encontraba el equipo aún caído y creaba un clon.
Solución (v1.66.15)
Cambio en create_insight
Antes de crear un nuevo insight, ahora se ejecuta una búsqueda:
open_episode = (
await AIInsight.objects.filter(
target=target,
anomaly_trigger=anomaly_description,
status=AIInsight.Status.PENDING,
)
.order_by("-created_at")
.afirst()
)
Si existe un insight PENDING del mismo equipo + trigger:
- Se renueva su caducidad (
expires_at += episode_ttl(), ver v1.87.2 abajo) en lugar de crear un clon. - Se registra en
AIInsightAuditLogcon acciónepisode_refreshedy detallesreason: "duplicate_anomaly_report". - Se lanza
ValueError("suppressed: open episode refreshed"), que la API traduce a HTTP 429 — contrato existente que la flota (Local Agent) trata como benigno. - Se ahorra la llamada de IA (costo + latencia).
Ciclo de vida del episodio:
- Creación: primer reporte de anomalía →
AIInsight.Status.PENDING - Renovación: reportes posteriores durante la avería →
expires_atse extiendeepisode_ttl()más - Auto-resolución: equipo se recupera →
auto_resolve_insights()/auto_resolve_connectivity_insights()cierra el insight - Caducidad natural: si el Agente deja de reportar →
episode_ttl()sin renovación → expira (4h por defecto, 48h sicategory=connectivitydesde v1.87.2)
Actualización v1.87.2 — TTL de conectividad a 48h (task #239, opción A)
La apuesta #11 (medición 21-08-2026) confirmó que el dedup por episodio bajaba el caudal, pero destapó un patrón nuevo: un equipo caído reporta cada 5-25 horas, y con la caducidad fija de 4h el episodio expiraba entre reportes — el siguiente sondeo abría OTRO insight. El 78% del caudal restante (429 de 551 en 7 días) eran re-aperturas del mismo (equipo + causa), no averías nuevas.
Fix: AIInsight.episode_ttl() (monitoring/models_insight.py) sustituye el timedelta(hours=4) hardcodeado por un cálculo según categoría:
EPISODE_TTL_HOURS = 4
CONNECTIVITY_EPISODE_TTL_HOURS = 48
def episode_ttl(self) -> "timezone.timedelta":
hours = (
self.CONNECTIVITY_EPISODE_TTL_HOURS
if self.category == self.Category.CONNECTIVITY
else self.EPISODE_TTL_HOURS
)
return timezone.timedelta(hours=hours)
Se usa tanto en AIInsight.save() (al crear) como en create_insight() (al renovar un episodio abierto) — antes ambos sitios tenían la misma constante duplicada. El cierre normal de una avería de conectividad sigue siendo la recuperación (auto_resolve_connectivity_insights, sin cambios en este commit): la caducidad de 48h es solo red de seguridad para equipos que el Agente deja de reportar del todo. El resto de categorías (security, performance, other) sigue en 4h.
Límite honesto (del propio CHANGELOG): la task sugería medir esto DESPUÉS de la iniciativa “Dos casas” (#235) sobre un escenario más limpio; se aplicó antes por decisión de Edu.
Impacto de usuario
✅ Pestaña de insights: ahora muestra solo un item por avería activa, no 14 duplicados ✅ Métricas ITSM: MTTR ahora mide la duración real de la avería, no el tiempo de creación del insight ✅ Performance: reducción esperada del caudal de 220 insights/día a ≤20 insights/día (v1.66.15); desde v1.87.2, sin re-aperturas de equipos caídos con reportes espaciados 5-25h ✅ Sin cambio de UI: la ayuda de CNS Sentinel no cambia (menos duplicados, mismo comportamiento documentado)
Validación
tests/api/test_insight_episode_dedup.py:
test_pending_episode_suppresses_and_refreshes: verifica que un episodio PENDING se renueva sin crear clontest_closed_episode_lets_new_one_through: verifica que un insight ACKNOWLEDGED o EXPIRED permite crear uno nuevotest_expired_episode_lets_new_one_through: ídem para EXPIREDtest_different_trigger_passes: verifica que una causa distinta crea un insight nuevotest_connectivity_episode_lives_48h_by_default(v1.87.2): un insight nuevo de categoríaconnectivitynace con 48h de margen; el resto sigue a 4htest_connectivity_refresh_extends_48h(v1.87.2): renovar un episodio de conectividad lo extiende 48h, no 4h
Todos usan async + django_db(transaction=True) para validar contra el ORM asíncrono.
Apuesta en WAGERS
Preregistrada como apuesta #11 en el libro de decisiones:
| Métrica | Target | Fecha de evaluación | Resultado |
|---|---|---|---|
| Caudal diario de insights de Sentinel | ≤ 20/día | 2026-08-21 | Parcial: el caudal bajó, pero el 78% de lo restante eran re-aperturas de equipos caídos con reportes espaciados (patrón no contemplado en la apuesta original) — fix en v1.87.2 (task #239) |
La medición previa a v1.66.15 fue ~223/día.
Notas de implementación
- Contrato de supresión: el
ValueErrorlanzado en dedup sigue el contrato existente de supresiones (ej. maintenance windows). La API lo traduce a 429 automáticamente. - Auditoría: cada renovación queda registrada; no es silenciosa.
- Histórico: los insights duplicados ya caducados desde hace años se quedan como están — el cambio aplica a partir de ahora.
- Deriva de: task #222 (decidido con Edu el 2026-08-14).
- v1.87.2:
episode_ttl()centraliza el cálculo del TTL por categoría (task #239, opción A, 2026-08-29); ya no haytimedelta(hours=4)repetido ensave()y encreate_insight().
Véase también
- [[entity—monitoring—model—aiinsight]]
- [[entity—monitoring—model—aiinsightauditlog]]
- [[entity—monitoring—model—monitoringtarget]]
- [[entity—monitoring—service—create-insight]]
- [[concept—monitoring—cns]]