IntroducciónTras desplegar una nueva versión del sistema en producción, los usuarios reportaron lentitud generalizada. Al inspeccionar el servidor, notamos un consumo inusual de memoria RAM (20-30 GB). Un sevricio específico (gestión de órdenes) consumía 13-18 GB de memoria, que disminuía temporalmente tras reiniciar el proceso pero crecía nuevamente.
Dado que el entorno era una red aislada con contenedores Docker minimalistas (sin herramientas de diagnóstico), implementamos un contenedor auxiliar dedicado a la depuración.
Contenido principal1. Configuración del contenedor de diagnóstico
Creamos una imagen Docker con las herramientas necesarias:
# Base en SDK .NET 5 para compatibilidad
FROM mcr.microsoft.com/dotnet/sdk:5.0
# Instalación de utilidades de diagnóstico
RUN dotnet tool install -g dotnet-dump --version 5.0.220101 \
&& dotnet tool install -g dotnet-stack --version 1.0.130701 \
&& dotnet tool install -g dotnet-counters --version 5.0.251802 \
&& dotnet tool install -g dotnet-trace --version 5.0.251802 \
&& apt-get update \
&& apt-get install -y unzip procps \
&& echo 'export PATH="$PATH:/root/.dotnet/tools"' >> /root/.bashrc
# Configuración del PATH
ENV PATH="${PATH}:/root/.dotnet/tools"
CMD ["/bin/bash"]
Estas herramientas permiten:
- dotnet-dump: Análisis de volcados de memoria con comandos SOS
- dotnet-stack: Captura de stacks de hilos
- dotnet-counters: Monitoreo de métricas de rendimiento
- dotnet-trace: Registro de trazas sin impacto en el proceso
- Integración con el contenedor de apliccaión
Ejecutamos el contenedor con permisos elevados:
docker run -d --name app-svc --privileged --cap-add=SYS_PTRACE \
-e ASPNETCORE_ENVIRONMENT=Development \
-e COMPlus_EnableDiagnostics=1 \
-v /host_tmp:/tmp \
order-app:5.0
Y conectamos el contenedor de diagnóstico:
docker run -it --rm --cap-add=SYS_PTRACE \
--pid=container:app-svc --privileged \
-v /host_tmp:/tmp \
dotnet-debug:5.0
- Análisis de métricas del proceso
Usamos dotnet-counters para obtener datos en tiempo real:
dotnet-counters monitor -p 1
Resultados críticos:
- GC Heap Size: 11,419 MB (heap muy grande)
- LOH Size: ~3.34 GB (muchos objetos grandes)
- Gen2 Size: ~8.8 GB (objetos de larga vida)
- Working Set: 14,910 MB (uso intensivo de memoria)
- Diagnóstico del volcado de memoria
Capturamos el volcado con:
dotnet-dump collect -p 1
Análisis del heap:
dumpheap -stat
Identificamos 290,000 instancias de Order.OperApply.ApplyVoucherDetail sin propósito claro. Usando:
dumpheap -mt 00007f117f67e590
gcroot 00007f0ca9b55818
Descubrimos una cadena de referencias:
RabbitMQ.Client.Impl.AsyncConsumerWorkServiceStockManage.Handler.ProductInOutStockStatDistributedHandlerMicrosoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry[]Order.OperApply.ApplyVoucher
- Resolución del problema
La causa era la serialización de AuditLogInfo en el módulo de auditoría de ABP, que incluía objetos de EF Core con contexto de base de datos y carga perezosa activada. Deshabilitar esta serialización resolvió el problema.
ConclusiónEste caso destaca la importancia de revisar las dependencias transitivas y las operaciones de serialización en sistemas con alto volumen de datos. La combinación de RabbitMQ, EF Core y ABP generó una fuga de memoria significativa.