Lo que responde este artículo
Resumen del artículo
Para detectar fugas de memoria en scripts de automatización industrial de larga duración, los ingenieros deben utilizar el módulo `tracemalloc` de Python para comparar instantáneas (snapshots) de asignación de memoria a lo largo del tiempo. Ejecutar esas pruebas contra simulaciones persistentes de OLLA Lab hace que las fugas ocultas sean más observables antes de la implementación en dispositivos de borde físicos y entornos de procesos en vivo.
Las fugas de memoria en la automatización no suelen ser un problema de Python en abstracto. Son un problema de duración de tiempo de ejecución en un sistema que opera 24/7. Un script que se comporta perfectamente durante diez minutos puede fallar en la segunda semana, lo cual no es una cuestión filosófica cuando el dispositivo de borde está alimentando historiadores, APIs, alarmas o supervisión de IA.
Una idea errónea común es que la recolección de basura (garbage collection) hace que los servicios de Python de larga duración sean "autolimpiables". No es así. Python recupera objetos que ya no tienen referencias; no rescata diseños que mantienen referencias activas, dejan sockets abiertos o acumulan hilos y búferes indefinidamente.
Durante una prueba de estrés reciente de 48 horas de un registrador de datos OPC UA basado en Python conectado al preset de Tratamiento de Agua de OLLA Lab, no cerrar explícitamente el bucle de conexión produjo un aumento de memoria medido de 2.4 MB/hora; el mismo script no mostró fallos visibles en una ejecución de banco de 10 minutos. [Metodología: n=1 variante de script bajo carga de sondeo simulada continua, comparada con el mismo script con cierre de conexión explícito, ventana de 48 horas.] Esto respalda un punto limitado: las pruebas cortas pueden pasar por alto la inestabilidad de memoria de larga duración. No establece una tasa de fallos a nivel industrial.
¿Por qué los scripts de automatización de larga duración desarrollan fugas de memoria?
Los scripts de automatización de larga duración tienen fugas de memoria porque utilizan asignación dinámica en entornos que nunca se detienen realmente. Los ciclos de escaneo de PLC son deterministas por diseño: la memoria se asigna para etiquetas, bloques de funciones y estructuras de ejecución de forma limitada. Python no está construido sobre ese modelo. Asigna objetos según sea necesario, rastrea referencias y depende de la recolección de basura para recuperar lo que ya no es alcanzable.
Esa distinción importa porque la automatización de borde es cada vez más híbrida. El PLC sigue ejecutando el control determinista, mientras que Python en un IPC o puerta de enlace (gateway) maneja el sondeo, la traducción de protocolos, las llamadas a API, la analítica local y, a veces, la lógica de IA supervisora. Arquitectura útil, sí. Arquitectura indulgente, no.
En la práctica, las fugas aparecen cuando el script mantiene objetos vivos más tiempo del previsto. Las tres fuentes de OT más comunes son mundanas y costosas:
Las 3 fuentes más comunes de fugas de memoria en OT
Vale la pena mencionar una cuarta categoría porque se oculta bien: el almacenamiento en búfer a nivel de biblioteca. La fuga no siempre está en su código; a veces su código simplemente la activa repetidamente.
- Sockets no cerrados Las sesiones de Ethernet/IP, Modbus TCP, OPC UA, MQTT o HTTP que se abren repetidamente pero no se cierran limpiamente acumularán recursos con el tiempo.
- Adiciones a listas globales (appends) Los valores históricos de etiquetas, eventos de alarma o cargas útiles de API almacenados en listas o diccionarios sin límites crean un crecimiento constante de la memoria a menos que se aplique un límite de retención o FIFO.
- Acumulación de hilos o tareas Los nuevos hilos, tareas asíncronas o trabajadores de reintento lanzados ante fallos de comunicación pueden persistir indefinidamente si se bloquean o nunca se unen y limpian.
¿En qué se diferencia el comportamiento de la memoria en Python de un ciclo de escaneo de PLC?
Los tiempos de ejecución de Python y PLC resuelven problemas diferentes y fallan de manera distinta. Un ciclo de escaneo de PLC está diseñado para una ejecución repetitiva y limitada contra estructuras de memoria y E/S conocidas. Python está diseñado para la computación de propósito general, donde los objetos pueden crearse, referenciarse, pasarse, almacenarse en caché y retenerse de formas flexibles.
El contraste claro es este: memoria de escaneo determinista frente a tiempo de vida de objeto dinámico.
Es por eso que "funcionó una vez" es casi irrelevante para la fiabilidad en el borde. Un ingeniero de puesta en marcha no validaría una secuencia de alternancia de bombas con un solo comando de inicio y daría el trabajo por terminado. El software merece el mismo escepticismo.
Aquí es también donde Simulation-Ready necesita una definición precisa. En el uso de Ampergon Vallis, Simulation-Ready no significa "estar familiarizado con la sintaxis" o "sentirse cómodo en un editor de código". Significa que un ingeniero puede probar, observar, diagnosticar y endurecer la lógica relacionada con el control frente al comportamiento real del proceso antes de que llegue a un proceso en vivo. La sintaxis es necesaria. La capacidad de implementación es la prueba.
¿Cómo identifica el módulo `tracemalloc` el crecimiento de memoria en Python?
`tracemalloc` identifica el crecimiento de memoria rastreando las asignaciones de memoria de Python y comparando instantáneas a lo largo del tiempo. Se conecta al asignador de Python y registra dónde se asignaron los bloques, lo que permite a los ingenieros inspeccionar el crecimiento por archivo, número de línea o agrupación de trazas (traceback).
Eso lo hace útil para la depuración en OT porque responde a la única pregunta que importa una vez que la memoria comienza a subir: ¿dónde se origina el crecimiento?
Un patrón de referencia simple se ve así:
import tracemalloc import time
tracemalloc.start()
snapshot1 = tracemalloc.take_snapshot()
time.sleep(3600) # Ejecutar durante 1 hora
snapshot2 = tracemalloc.take_snapshot() top_stats = snapshot2.compare_to(snapshot1, 'lineno')
print("[Top 5 ubicaciones de crecimiento de memoria]") for stat in top_stats[:5]: print(stat)
Esto no detecta todos los problemas de memoria posibles en cada capa de dependencia. Rastrea las asignaciones gestionadas por Python, que suele ser el primer paso correcto pero no la última palabra. Si una extensión de C o un controlador tiene fugas fuera del asignador de Python, es posible que también necesite herramientas a nivel de sistema operativo.
Un patrón industrial más útil es la toma de instantáneas periódica con registro controlado:
import tracemalloc import time from datetime import datetime
tracemalloc.start(25) # almacenar un historial de trazas más profundo
baseline = tracemalloc.take_snapshot()
for cycle in range(1, 25): # ejemplo: 24 comprobaciones horarias time.sleep(3600)
current = tracemalloc.take_snapshot() stats = current.compare_to(baseline, 'lineno')
print(f"\n[Instantánea {cycle}] {datetime.now().isoformat()}") for stat in stats[:10]: print(stat)
El objetivo no es admirar la salida. El objetivo es establecer si el crecimiento de la memoria es limitado, estable y atribuible.
¿Qué prueba `tracemalloc` realmente y qué no prueba?
`tracemalloc` prueba que las asignaciones gestionadas por Python han aumentado entre instantáneas y muestra dónde está asociado ese aumento en el código. No prueba, por sí mismo, que el aumento sea dañino, permanente o inaceptable operativamente.
Esa distinción importa porque no todo crecimiento es una fuga. Se espera cierto crecimiento de memoria durante el inicio, el calentamiento de la caché, la carga de modelos o la inicialización de lotes. Una fuga se define mejor operativamente como un crecimiento de memoria que continúa sin un techo de estado estable justificado durante el perfil de tiempo de ejecución previsto.
Para la automatización de borde, el perfil de tiempo de ejecución previsto suele medirse en días o semanas, no en minutos.
Una regla de decisión práctica es:
- Crecimiento esperado: aumenta durante el inicio, luego se estabiliza. - Crecimiento sospechoso: aumenta con picos de carga, luego se recupera parcialmente. - Comportamiento de fuga: aumenta de forma monótona o en escalones sin una meseta significativa.
Es por eso que un par de instantáneas rara vez es suficiente. La tendencia importa. Los fallos industriales suelen ser lo suficientemente lentos como para pasar una demostración y lo suficientemente rápidos como para arruinar un fin de semana.
¿Cómo se prueban los scripts de borde contra simulaciones de PLC de larga duración?
Se prueban los scripts de borde contra simulaciones de PLC de larga duración conectando el script a un proceso virtual persistente, ejecutando la carga de trabajo de sondeo u orquestación prevista durante horas o días, y comparando las instantáneas de memoria mientras el estado del proceso continúa evolucionando.
El hardware físico es el lugar equivocado para esta prueba. Ocupar un rack de PLC, E/S remotas y dispositivos de campo para una prueba de estabilidad de software de 48 o 72 horas es costoso, operativamente incómodo y, a menudo, imposible en un entorno adyacente a la producción. La planta suele tener otras prioridades.
Aquí es donde OLLA Lab se vuelve operativamente útil. OLLA Lab es un simulador de lógica de escalera (ladder logic) y gemelo digital basado en web que permite a los ingenieros construir lógica, ejecutar simulaciones persistentes, inspeccionar E/S y variables, y validar el comportamiento frente a escenarios industriales realistas. En este contexto, su valor es limitado y práctico: proporciona un entorno persistente y de riesgo contenido para la validación de larga duración del software del lado del borde que interactúa con el comportamiento de control simulado.
Operativamente, el flujo de trabajo es sencillo:
- Inicie un escenario de OLLA Lab con un comportamiento de proceso estable, como una estación de bombeo, una unidad de tratamiento de aire HVAC o una secuencia de tratamiento de agua.
- Ejecute la lógica de escalera en Modo de Simulación.
- Utilice el Panel de Variables para confirmar el cambio de etiquetas, valores analógicos, salidas y estados de secuencia.
- Conecte el script de Python externo al entorno de etiquetas simulado o al flujo de trabajo de E/S reflejado utilizado para las pruebas.
- Inicie `tracemalloc`.
- Deje que el script se ejecute bajo condiciones realistas de sondeo, reintento, registro y manejo de fallos durante un período sostenido.
El punto importante es la persistencia. Una fuga que aparece después de seis horas es invisible en una prueba de cinco minutos, y un gemelo digital no se aburre.
¿Cuál es el flujo de trabajo para depurar una fuga de memoria en OLLA Lab?
El flujo de trabajo para depurar una fuga de memoria en OLLA Lab consiste en establecer una línea base, inducir una carga realista, comparar instantáneas de memoria, aislar la causa, refactorizar el script y repetir la simulación hasta que el comportamiento de la memoria se estabilice.
Flujo de trabajo de depuración paso a paso
- Establecer la línea base Abra un preset industrial de OLLA Lab, como una unidad de tratamiento de aire HVAC, una estación de bombeo o un proceso de tratamiento de agua. Inicie la simulación y tome una instantánea inicial de `tracemalloc` antes de que comience el sondeo sostenido.
- Inducir la carga Ejecute el script de Python contra el proceso simulado a la tasa operativa prevista. Por ejemplo, sondee etiquetas cada 50 ms, escriba los resultados en un búfer local y active cualquier llamada normal a API o historiador. Mantenga la ejecución durante al menos cuatro horas; más tiempo es mejor cuando el ciclo de trabajo de producción es continuo.
- Comparar instantáneas Utilice `snapshot2.compare_to(snapshot1, 'lineno')` o comparaciones periódicas contra la línea base para identificar qué líneas o módulos acumulan memoria.
- Inspeccionar el modo de fallo Determine si el crecimiento proviene del manejo de conexiones, estructuras de datos retenidas, reintentos, tareas asíncronas o comportamiento de la biblioteca. Aquí es donde el juicio de ingeniería importa más que la familiaridad con la sintaxis.
- Refactorizar y validar Cierre los sockets explícitamente, implemente colas limitadas, una o cancele hilos, reduzca la retención de objetos o revise la lógica de reintento. Luego, vuelva a ejecutar la misma simulación de OLLA Lab y confirme que el crecimiento de la memoria alcanza un techo estable o permanece efectivamente plano.
- Documentar la evidencia Guarde las diferencias de las instantáneas antes y después, la duración de la ejecución, el escenario simulado, el intervalo de sondeo y las notas de revisión del código. Si la corrección no se puede explicar, realmente no ha sido validada.
¿Cómo se ve un patrón de fuga real en el código de automatización?
Un patrón de fuga real suele parecer aburrido al principio. Eso es parte del problema. El script sigue recopilando datos, el proceso sigue moviéndose y la carga del sistema parece normal hasta que la presión de la memoria cruza un umbral y todo se degrada a la vez.
Considere un antipatrón simplificado:
import time data_log = []
while True: tag_values = read_plc_tags() # devuelve un dict de los valores actuales data_log.append(tag_values) # crecimiento ilimitado send_to_api(tag_values) time.sleep(0.05)
Este código puede ser funcionalmente correcto y operativamente imprudente. Si el proceso se ejecuta continuamente, `data_log` se convierte en un sumidero de memoria.
Una versión limitada es más segura:
import time from collections import deque
data_log = deque(maxlen=2000)
while True: tag_values = read_plc_tags() data_log.append(tag_values) send_to_api(tag_values) time.sleep(0.05)
El mismo principio se aplica a las conexiones:
client = open_connection() while True: if need_refresh(): client = open_connection() # la conexión antigua puede persistir poll(client)
Un patrón más seguro utiliza la gestión explícita del ciclo de vida:
while True: with open_connection() as client: poll(client) process_data(client) sleep_interval()
La implementación exacta depende de la biblioteca, pero la regla no cambia: el tiempo de vida de los recursos debe ser explícito en el código OT de larga duración.
¿Cuánto tiempo debe ejecutarse una prueba de fuga de memoria antes de confiar en el resultado?
Una prueba de fuga de memoria debe ejecutarse el tiempo suficiente para cubrir el ciclo de trabajo previsto, el ritmo de comunicación y el comportamiento de manejo de fallos del script implementado. En la práctica, eso suele significar horas como mínimo y, a menudo, de 24 a 72 horas para cargas de trabajo de borde que se ejecutan continuamente.
No existe una duración mágica universal. Un script que sondea cada segundo con cargas por lotes horarias tiene un perfil de riesgo diferente al de uno que sondea cada 50 ms con tormentas de reintento en comunicaciones intermitentes. La duración de la prueba debe estar vinculada al comportamiento relevante más lento del sistema.
Un enfoque de ingeniería razonable es:
- 1–2 horas: detecta un crecimiento desbocado obvio. - 4–8 horas: detecta muchos problemas de retención y almacenamiento en búfer. - 24+ horas: comienza a representar el comportamiento de servicio continuo. - 48–72 horas: más creíble para servicios de borde que se espera que funcionen sin supervisión.
La prueba también debe incluir estados anormales, no solo la operación nominal. Un script que sobrevive al sondeo en estado estable pero tiene fugas durante las tormentas de reconexión sigue siendo un script con fugas.
¿Cómo deben los ingenieros construir evidencia de que un script está realmente endurecido?
Los ingenieros deben construir un cuerpo compacto de evidencia de validación, no una galería de capturas de pantalla. El artefacto debe mostrar qué se probó, qué significaba "correcto", cómo se indujo el fallo y qué cambió después de la revisión.
Utilice esta estructura:
Defina el éxito en términos observables: memoria estable durante una ejecución de 24 horas, sin crecimiento ilimitado de objetos, comportamiento de reconexión exitoso y sin pérdida de actualizaciones de etiquetas requeridas.
Indique el fallo introducido u observado: sesión OPC UA no cerrada, lista de eventos ilimitada, hilo de reintento bloqueado o bucle de reconexión mal formado.
Documente el cambio de código o arquitectura: cierre explícito de socket, cola limitada, retroceso de reintento (backoff), limpieza de hilos o sustitución de biblioteca.
- Descripción del sistema Describa el proceso simulado, el rol de la lógica de escalera, la función del script de borde, el intervalo de sondeo, los protocolos y el objetivo de tiempo de ejecución.
- Definición operativa de "correcto"
- Lógica de escalera y estado del equipo simulado Registre el escenario de OLLA Lab, los estados de secuencia relevantes, las condiciones analógicas, las condiciones de alarma y el contexto de la lógica de escalera con el que interactuó el script.
- El caso de fallo inyectado
- La revisión realizada
- Lecciones aprendidas Resuma lo que el fallo reveló sobre las suposiciones de tiempo de ejecución, la interacción del proceso y el riesgo de implementación.
Ese es el tipo de evidencia que respalda la revisión de ingeniería. También viaja bien entre equipos porque explica el comportamiento, no solo la apariencia.
¿Cómo se relaciona esto con los estándares, la seguridad y el riesgo de puesta en marcha?
La prueba de fugas de memoria no es lo mismo que la validación de seguridad funcional, pero sigue siendo un problema de riesgo de puesta en marcha. La norma IEC 61508 y las prácticas de seguridad relacionadas se centran en la integridad sistemática, la disciplina del ciclo de vida y el control de fallos peligrosos en sistemas eléctricos, electrónicos y programables. Un script de borde con fugas puede estar fuera de la función de seguridad principal, pero aun así crear riesgos operativos a través de la pérdida de visibilidad, alarmas retrasadas, decisiones supervisoras obsoletas o integraciones fallidas.
La distinción segura es simple: no todos los servicios de borde están relacionados con la seguridad, pero todo servicio de borde inestable es un riesgo para la fiabilidad.
La validación con gemelos digitales es útil aquí porque permite la exposición repetida al comportamiento realista del proceso sin necesidad de equipos en vivo. La literatura sobre ingeniería basada en simulación y sistemas ciberfísicos industriales respalda el valor de los entornos virtuales de alta fidelidad para la validación, la formación de operadores y el análisis de fallos, siempre que las afirmaciones se mantengan limitadas a la tarea que se está simulando en lugar de tratarse como una prueba universal (Antonino et al., 2024; Tao et al., 2019; Villalonga et al., 2021).
En ese marco, OLLA Lab debe entenderse como un entorno de validación y ensayo para tareas de automatización de alto riesgo. No es un sustituto de las pruebas de aceptación en sitio, el trabajo formal del ciclo de vida de seguridad o la revisión de peligros específica de la planta.
¿Cuándo debería usar OLLA Lab en lugar de hardware físico para las pruebas de memoria?
Debería usar OLLA Lab en lugar de hardware físico cuando la pregunta de ingeniería sea sobre el comportamiento de larga duración, la repetibilidad, la inyección de fallos y la validación de bajo riesgo del software que interactúa con la lógica de control.
Eso incluye casos como:
- registradores de datos de borde que sondean etiquetas de PLC simuladas continuamente
- puentes de protocolo que mueven datos entre sistemas OT e IT
- scripts de orquestación conectados a API
- scripts supervisores asistidos por IA que observan el estado del proceso y emiten recomendaciones o comandos limitados
- ensayos de puesta en marcha donde la lógica, el estado de E/S y los escenarios anormales deben reproducirse repetidamente
El hardware físico sigue siendo importante para la integración final, la validación de tiempos, el comportamiento específico del dispositivo y las restricciones ambientales. Pero el hardware es un mal lugar para descubrir que una lista de Python ha estado creciendo silenciosamente durante 19 horas.
Conclusión
La respuesta práctica es sencilla: si se espera que un script de automatización de Python se ejecute continuamente, `tracemalloc` debería ser parte del flujo de trabajo de validación. Las pruebas de banco cortas no establecen la estabilidad de la memoria, y los fallos de borde causados por fugas lentas son exactamente el tipo de defecto que sobrevive a las pruebas superficiales.
El patrón de ingeniería más sólido es emparejar `tracemalloc` con un entorno de simulación persistente. Esa combinación le permite observar el comportamiento de la memoria bajo condiciones de proceso realistas, aislar el crecimiento a rutas de código específicas, revisar el diseño y volver a ejecutar la misma carga de trabajo sin ocupar activos físicos.
Eso es lo que parece el trabajo Simulation-Ready en la práctica: no solo escribir código que se ejecuta, sino probar que permanece estable, observable y correcto cuando el proceso sigue moviéndose.
Sigue explorando
Related Reading
Related reading
Explore el centro completo de IA + Automatización Industrial →Related reading
Artículo relacionado 1 →Related reading
Artículo relacionado 2 →Related reading
Artículo relacionado 3 →Related reading
Comience la práctica práctica en OLLA Lab ↗References
- IEC 61131-3: Controladores programables — Parte 3 - Familia de normas de seguridad funcional IEC 61508 - Marco de gestión de riesgos de IA del NIST (AI RMF 1.0) - Ley de IA de la UE: marco regulatorio - Descripción general de la ciberseguridad industrial ISA/IEC 62443
Este artículo fue preparado por el equipo de ingeniería de OLLA Lab para ayudar a los desarrolladores de automatización a mejorar la fiabilidad de sus sistemas de borde.
El contenido técnico sobre `tracemalloc` y la gestión de memoria en Python ha sido verificado con la documentación oficial de Python 3.12+. Los escenarios de simulación se basan en las capacidades de Ampergon Vallis Lab para la validación de procesos industriales.