tracemalloc registra dónde se crearon los bloques gestionados por el asignador de memoria de Python. Responde una pregunta práctica: ¿qué líneas retienen más memoria entre dos puntos de una ejecución? Esa evidencia es mucho más útil que saber únicamente que el proceso aumentó de tamaño.

El módulo pertenece a la biblioteca estándar y no necesita dependencias. Sin embargo, no mide todo. El RSS del sistema operativo incluye el intérprete, bibliotecas nativas, páginas compartidas y memoria que los asignadores conservan para reutilizar. Por eso un snapshot y el RSS pueden cambiar de manera diferente sin que una medición sea incorrecta.

Iniciar el rastreo en el momento correcto

Activa el rastreo antes de importar o ejecutar el código que quieres investigar. Los bloques creados antes no tendrán un traceback registrado. Un servicio puede arrancar con PYTHONTRACEMALLOC=10 o -X tracemalloc=10; en un script o test, usa la API:

import tracemalloc


tracemalloc.start(10)
antes = tracemalloc.take_snapshot()

datos = [{"id": i, "valor": str(i)} for i in range(50_000)]

despues = tracemalloc.take_snapshot()
for estadistica in despues.compare_to(antes, "lineno")[:10]:
    print(estadistica)

El argumento de start() es la profundidad máxima del traceback guardado para cada bloque. Más frames ayudan cuando la línea asignadora es genérica y el origen importante está más arriba, pero aumentan el coste de CPU y memoria del diagnóstico. Diez frames son un punto de partida para investigar, no una configuración permanente.

take_snapshot() captura los bloques que siguen asignados en ese instante. El ejemplo conserva datos deliberadamente, así que el segundo snapshot debe mostrar crecimiento. En un servicio real, captura estados después de puntos equivalentes, por ejemplo tras el primer, décimo y centésimo lote terminado.

Leer estadísticas y diferencias

Un snapshot puede agrupar trazas por lineno, filename o traceback. lineno suele ser la vista inicial más clara porque identifica archivo y línea. traceback separa asignaciones de una misma línea según la pila que llegó hasta ella, algo útil para funciones compartidas.

Los elementos de compare_to() contienen size_diff, count_diff, size y count. Muchos bloques pequeños pueden revelar una colección creciente. Pocos bloques con una diferencia grande pueden indicar buffers retenidos. Examina ambos valores y no conviertas la primera fila en un diagnóstico automático.

Conviene convertir la comparación en un procedimiento repetible:

import tracemalloc


def ejecutar_ciclos(cantidad: int) -> None:
    for _ in range(cantidad):
        procesar_lote()


tracemalloc.start(15)
ejecutar_ciclos(5)  # calentamiento
base = tracemalloc.take_snapshot()

ejecutar_ciclos(50)
final = tracemalloc.take_snapshot()

for elemento in final.compare_to(base, "filename"):
    if elemento.size_diff > 100_000:
        print(elemento.traceback, elemento.size_diff, elemento.count_diff)

El calentamiento reduce el ruido de imports tardíos, inicialización de pools y primer llenado de caches. El límite de 100.000 bytes es solo una regla de selección, no una definición universal de fuga. Adáptalo a la carga y comprueba si el aumento continúa.

Filtrar ruido sin borrar evidencias

Snapshot.filter_traces() acepta objetos Filter y DomainFilter. Los filtros pueden quitar detalles internos y enfocar el informe en la aplicación:

filtros = [
    tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
    tracemalloc.Filter(False, "*/site-packages/*"),
    tracemalloc.Filter(True, "*/mi_aplicacion/*"),
]

enfocado = final.filter_traces(filtros)
for elemento in enfocado.statistics("lineno")[:10]:
    print(elemento)

Las exclusiones requieren criterio. Una dependencia puede retener objetos por la forma en que la aplicación la utiliza; ocultar todo site-packages puede eliminar una pista importante. Guarda o examina primero el resultado completo y crea después vistas filtradas.

Medir consumo actual y pico

get_traced_memory() devuelve los bytes rastreados actualmente y el pico desde que comenzó el rastreo. reset_peak() reinicia solo el pico. Así es posible aislar una operación:

tracemalloc.start()

cargar_configuracion()
tracemalloc.reset_peak()
generar_informe()

actual, pico = tracemalloc.get_traced_memory()
print(f"actual={actual / 1024:.1f} KiB")
print(f"pico={pico / 1024:.1f} KiB")

El pico sirve para tareas que liberan los datos al finalizar, pero superan temporalmente un límite. Los snapshots son mejores para localizar asignaciones que permanecen. Son preguntas complementarias.

Interpretar correctamente

Una comparación no demuestra por sí sola una fuga. Imports, caches limitadas, pools de conexiones, expresiones regulares compiladas y estructuras internas pueden crecer una vez y estabilizarse. Ejecuta rondas idénticas, descarta el calentamiento y busca una tendencia. En un diagnóstico puedes llamar a gc.collect() antes de cada snapshot para reducir ciclos recolectables, pero eso cambia el comportamiento normal y no debe convertirse en una corrección cosmética.

Comprueba qué referencias deberían permanecer. Una lista global, un callback registrado, una tarea asíncrona inconclusa o una cache sin límite pueden mantener objetos alcanzables. tracemalloc muestra dónde se asignó la memoria, no la cadena de referencias que conserva el objeto. El módulo gc, la inspección de referencias o un profiler de objetos responden esa segunda pregunta.

No declares una fuga únicamente porque el RSS no baje tras del y la recolección. Python puede devolver bloques a sus pools sin devolver inmediatamente las páginas al sistema. A la inversa, una extensión en C puede aumentar el RSS sin aparecer con detalle suficiente en los snapshots.

Guardar snapshots para analizarlos después

Los snapshots admiten dump() y Snapshot.load(). Esto ayuda cuando el entorno afectado no tiene herramientas interactivas:

snapshot = tracemalloc.take_snapshot()
snapshot.dump("/tmp/despues-carga.snapshot")

## Más tarde, en un proceso de análisis:
cargado = tracemalloc.Snapshot.load("/tmp/despues-carga.snapshot")
for elemento in cargado.statistics("traceback")[:5]:
    print(elemento)

Estos archivos pueden revelar rutas, módulos y detalles internos. Trátalos como artefactos de diagnóstico, restringe el acceso y no los publiques. Una comparación entre procesos solo es válida con versiones, código, dependencias, configuración y carga suficientemente parecidos.

Secuencia práctica de investigación

Primero reproduce el crecimiento con entradas controladas. Inicia el rastreo antes del calentamiento, captura una base y repite unidades equivalentes de trabajo. Compara por línea y traceback, examina las mayores diferencias positivas y verifica si siguen creciendo. Finalmente, relaciona los resultados con RSS, volumen procesado, colas y límites de caches.

Cuidados en tests y producción

Una prueba de regresión de memoria debe tolerar pequeñas variaciones del intérprete. No exijas un número exacto de bytes. Repite la operación, establece un margen a partir de mediciones y comprueba una tendencia claramente anormal. Aísla plugins, logs y pruebas paralelas. No compares versiones distintas de Python como si el comportamiento del asignador fuera idéntico.

En producción, prefiere una ventana breve y controlada. Capturar pilas profundas durante horas añade coste a un proceso que quizá ya esté bajo presión. Registra versión de la aplicación, volumen de entrada, configuración y horarios. Sin ese contexto, una diferencia grande puede deberse a cargas distintas.

Llama a tracemalloc.stop() al terminar. clear_traces() elimina las trazas actuales y permite una nueva base, pero impide comparar de forma válida los snapshots anteriores con el estado limpio. Conserva únicamente el artefacto necesario en un lugar protegido.

Al comunicar resultados, indica si cada cifra es tamaño actual, pico, diferencia de snapshots o RSS. Presenta las líneas sospechosas como pistas, no como culpables demostrados. La validación final debe mostrar que la misma carga se estabiliza en rondas sucesivas después de corregir la retención y que el comportamiento funcional sigue correcto.

Combina el análisis con cProfile para CPU solo si también investigas el tiempo de ejecución. CPU y memoria son dimensiones distintas. Para retención intencional, revisa límites y TTL con cachetools.

La documentación oficial de tracemalloc, consultada el 28 de julio de 2026, explica inicio, snapshots, filtros, dominios y medición de picos. Consulta la documentación correspondiente a la versión desplegada. Con puntos de medición equivalentes y una distinción clara entre heap rastreado y memoria del proceso, tracemalloc convierte un síntoma ambiguo en hipótesis verificables.