La métrica de memoria de AWS Lambda oculta el consumo real del proceso

Fuentes: Your Lambda isn't leaking memory — your metrics are lying to you
Imagen generada por IA con el prompt: Abstract editorial illustration of memory metrics with bar charts and rising data lines, serverless cloud theme, minimalist style, dark blue and amber tones, technical aesthetic
Imagen generada con IA

Un equipo de ingeniería detectó que una función de AWS Lambda que alojaba 40 modelos ONNX de unos 250 MB cada uno sufría errores OOM puntuales (1 de cada 100.000 solicitudes). Al reducir el tamaño de la caché LRU de 16 a 8, el problema se agravó: en tres horas se registraron más de 270 cierres SIGKILL, con el consumo de cada entorno de ejecución subiendo de 400 MB a 9.000 MB antes de reiniciarse.

Las primeras optimizaciones (cargar los modelos desde un archivo temporal en lugar de mantener dos copias en memoria y desactivar el asignador interno de ONNX Runtime) redujeron el uso de memoria p50 de unos 7,5 GB a 5 GB y mejoraron la latencia p99. Aun así, un modelo de 19 MB seguía generando 120 MB de RSS, lo que apuntaba a una fuga.

La clave llegó al analizar la métrica @maxMemoryUsed, que AWS Lambda publica en cada línea REPORT y expone en CloudWatch Logs Insights. Tras examinar 5.949 invocaciones de tres clientes en tres regiones, observaron que el valor solo subía, nunca bajaba. AWS confirmó que se trata de un high-water mark del entorno de ejecución (similar a VmHWM en /proc/<pid>/status o getrusage(RUSAGE_SELF).ru_maxrss), no del consumo de una invocación concreta.

El verdadero culpable era la retención de memoria en las arenas de glibc. Con mallinfo2() comprobaron que, de 120 MB, solo 40 MB correspondían a bloques activos; 188 MB estaban liberados pero retenidos en arenas de hilo. El umbral por defecto de 128 KB mantenía las asignaciones pequeñas dentro de las arenas, y el uso de múltiples hilos de inferencia en ONNX Runtime multiplicaba el número de arenas. Al reducir el umbral de mmap a 32 KB mediante mallopt(M_MMAP_THRESHOLD, 32768), la retención cayó de 188 MB a 4 MB, una reducción del 97%. Combinado con el ajuste del asignador de ONNX Runtime, el RSS en estado estable pasó de unos 625 MB a 415 MB, a cambio de unos 40 ms adicionales en la latencia p50.

El equipo concluye que @maxMemoryUsed es un indicador engañoso: picos transitorios elevan el valor de forma permanente, por lo que no sirve para diagnosticar fugas. Recomiendan recurrir a Lambda Insights o instrumentar el handler con mallinfo2() para conocer la memoria realmente utilizada.