Incidentedraftcreado Fri Sep 04#monitoring#network#performance#reliability#observability#valkey#django#audit-suprema
Cuándo
04-09-2026 · commit 7b1025166 (PR #499, v1.107.0). Task #286 — cola MEDIA de la Auditoría Suprema 2 en monitoring, bloque D: cambia de foco de sondas/validación (ver [[incident—20260904—auditoria-suprema-2-cola-monitoring-a-sondas-y-targets]]) a rendimiento del deep-discovery y de las páginas Wireless/UPS. Cuatro hallazgos MEDIA, 15 tests nuevos.
Síntomas visibles
Cuatro hallazgos MEDIA en el mismo commit:
- D-#10 — El progreso del job de deep-discovery (
done/failed/statusen Valkey) se actualizaba con uncache.get+cache.setsin bloqueo. El Agente Local manda hasta 5 resultados en paralelo (max_concurrent); dos escrituras concurrentes se pisaban y el job se quedaba clavado sin llegar nunca acompleted. - D-#11 — El enlace automático de un perfil descubierto a su
Device(Rack Editor) recorría TODOS los devices de la organización, deserializandomanagement_configuno a uno en Python — sin límite y sin excluir los racks en papelera. - D-#40 — Un SAI con el ping filtrado, o con
ping_enabled=Falsey solo SNMP, salíaunknown/offliney sin métricas de batería/carga aunque estuviera publicando datos SNMP frescos ahora mismo: UPS solo mirabamt.last_status, sin el mismo salvavidas de “evidencia viva” que Wireless ya tenía desde la auditoría #261. - D-#41 —
get_ups_summary/get_ups_status_listy sus gemelos de Wireless cargabandeep_snmp_data(un blob JSON de 20-60 KB por equipo real) de TODA la flota en cada refresco, aunque el dato vivo de VictoriaMetrics ya bastara para responder casi siempre — refrescos cada 2 minutos por pestaña, la mayoría del blob descartado sin usar.
Causa raíz
- Contador sin exclusión mutua: un read-modify-write sobre un valor compartido en Valkey sin
SETNX/lock, bajo escritura concurrente real (el Agente en paralelo). - Enlace por recorrido completo en vez de prefiltro: sin filtro SQL sobre el campo que de verdad se compara, cualquier alta de dispositivos escala linealmente con el inventario de la organización.
- Una sola fuente de “está vivo” (ping) para dos protocolos con comportamiento distinto: Wireless ya había resuelto esto para APs (auditoría #261); UPS heredó el mismo hueco por no compartir el motor.
- Carga siempre-completa donde el caso común es parcial: pedir el blob entero cuando la mayoría de peticiones solo necesita el resumen agregado.
Fix aplicado
Commit 7b1025166 (PR #499):
- D-#10 —
monitoring/services/deep_discover_service.py: nuevaadvance_deep_discover_job(job_key, success)toma un lock corto víacache.add(SET NX atómico, mismo patrón quecore/ratelimit.py) antes de leer/incrementar/escribir el job; sin lock en ~1s actualiza igual, degradado, para no perder el resultado del Agente.terminal/api/sentinel_ingest.py::receive_deep_discovery_resultpasa a llamarla en vez de tocarcachedirectamente. - D-#11 —
terminal/api/sentinel_ingest.py: nueva_reverse_link_device(profile, tenant_id)prefiltra conDevice.objects.filter(rack__organization_id=..., rack__deleted_at__isnull=True, management_config__contains=ip)(LIKE, por eso la comparación exacta contraDevice.management_ipsigue en Python) antes de iterar. - D-#40 — nuevo módulo
monitoring/services/live_evidence.py:LiveSnmpEvidenceextrae el motor genérico que antes vivía solo dentro deWirelessLive(una consulta a VictoriaMetrics, N métricas, por target);WirelessLivepasa a heredar de él yUpsLive(nuevo, enmonitoring/services/ups_metrics.py) lo reutiliza fijandoMETRICS = "snmp_extras_battery_charge|snmp_extras_output_load". - D-#41 —
_get_ap_profilesy su equivalente en UPS aceptanwith_deep=False(.defer("deep_snmp_data"));hydrate_deep_snmp_data(profiles, predicate)hidrata en una sola consultaid IN (...)solo los perfiles que de verdad lo necesitan (target sin estado o sin dato vivo), evitando el lazy-load fila a fila de Django. - Refactor de acompañamiento:
monitoring/api/ups.pybaja de 621 a 401 LOC extrayendomonitoring/services/ups_metrics.py(lógica deUpsLive/métricas) ymonitoring/api/ups_reports.py(reportes), manteniendo la Regla 5 (máx. 500 LOC/módulo). - 15 tests nuevos en
tests/monitoring/test_monitoring_bloque_d.py.
Lecciones
- Un contador compartido bajo escritura concurrente necesita exclusión mutua explícita desde el primer día del endpoint que lo escribe más de una vez por request — el patrón de lock corto de
core/ratelimit.pyes reutilizable, no hace falta reinventarlo cada vez. - Cuando dos features resuelven el mismo problema (“¿este equipo está realmente vivo?”) por caminos separados, la segunda implementación hereda el hueco que la primera ya cerró — merece la pena extraer el motor compartido en cuanto aparece el segundo caso, no esperar al tercero.
- Diferir un campo pesado (
.defer(...)) sin un hidratador explícito no ahorra nada: en cuanto algo lee el atributo, Django dispara un lazy-load por fila — el ahorro real exige decidir explícitamente quién lo necesita y traerlo en una sola consulta.
Preventivos futuros
- Sigue abierta la cola de bloques anunciada en la cola A (CNS e insights; ITSM y notificaciones) — este bloque D nace de una re-priorización hacia rendimiento antes de cerrarlos.
- Ayuda de usuario sin cambios: el comportamiento visible (SAI vivo por SNMP, refrescos de summary) coincide con lo que la documentación de usuario ya describía.
Véase también
- [[incident—20260904—auditoria-suprema-2-cola-monitoring-a-sondas-y-targets]]
- [[entity—monitoring—service—target-lifecycle]]
- [[entity—monitoring—service—group-service]]
- [[entity—monitoring—service—snmp-service]]
- [[feature—wireless—deep-discovery-timestamp]]
- [[entity—monitoring—model—monitoringtarget]]
- [[entity—monitoring—service—metric-names]]
- [[concept—saas—multi-tenancy]]
Referenciado desde
- Auditoría Suprema 2 · Cola config-ia: IA del workspace, drivers CNS y saneado de prompts
- Auditoría Suprema 2 · Cola frontend: CSP del portal de cliente, escapes y CSRF same-origin
- Auditoría Suprema 2 · Cola network: DeviceTask sin agente que no caduca y comandos de la whitelist por vendor inalcanzables
- Liquidación de las rondas 30-08/06-09 · Cuarto PR: el descubrimiento de red deja de mentir (network)