Alto consumo de memoria y cursores de datos

Published Thu, Mar 20 2008 22:22

Sorpresas te llevas en la vida, siempre. A pesar de lo que parezca, hoy no ando sermoneador ni nada por el estilo. Es sólo que no se me ocurre como comenzar este post así que escribo lo primero que se me ocurre Smile. Total, lo interesante viene ahora.

Viaje de emergencia, aplicación ASP con excepciones por falta de memoria (Out Of Memory), servicio interrumpido.
Resumen: problemas... un poco de entretención para unos meses muy aburridos.

Síntomas

Como mencionaba, tenemos una aplicación ASP que de vez en cuando lanza excepciones por falta de memoria, mas conocidos como error 500 ASP 147. Obviamente con el reinicio del proceso, todo vuelve a la normalidad, pero luego de la calma, llega la tormenta.

Como ya es costumbre, se capturaron algunos dumps de memoria cuando se estaba produciendo el error y se analizaron. Los resultados fueron sorprendentes, los que pasan a ser mostrados ahora.

Lo primero muy interesante es que el dump apenas sobrepasaba los 100 megabytes. Un dump contiene, sin entrar en grandes detalles, los datos privados del proceso y las librerías cargadas entre otras cosas. Si son un poco más de 100 megabytes, ¿cómo es posible que haya falta de memoria?. El administrador de tareas confirmaba que el proceso estaba utilizando algo más de 100 megabytes en working set y un poco menos en memoria privada

¿Entonces?

Debugging Tool For Windows entra a ayudarnos. Revisando el estado de cada heap, nos encontramos con lo que muestra el bloque de más abajo. Hay muchas columnas y muchos datos, pero fijemos la atención en las columnas que hacen mención a la memoria reservada y comprometida.

Recordemos que en un sistema operativo Windows, la memoria puede estar en tres estados: libre, reservada y comprometida. Para que una aplicación la pueda utilizar necesita primero reservarla, y luego comprometerla. Después de usarla, la debe des-comprometer (fea palabra, lo sé) y luego liberar (des-reservar, también es fea).

  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                    (k)     (k)    (k)     (k) length      blocks cont. heap 
-----------------------------------------------------------------------------
00080000 00000002   15360  13424  14512   2362   514   137    0     9d   L  
    External fragmentation  17 % (514 free blocks)
00180000 00008000      64     12     12     10     1     1    0      0      
002b0000 00001002   22080   9560  17536   1629   231   124   25     5b   L  
    External fragmentation  17 % (231 free blocks)
    Virtual address fragmentation  45 % (124 uncommited ranges)
00550000 00000002    1024     20     20      2     1     1    0      0   L  
00690000 00001002     256     32     32      2     1     1    0      0   L  
01bb0000 00001002     256     12     12      4     1     1    0      0   L  
01bf0000 00001002   39872  11200  29608    722    62    60    0      7   LFH
01c30000 00001002     256     12     12      4     1     1    0      0   L  
01c70000 00001002     256     12     12      4     1     1    0      0   L  
<recortado>
02630000 00001002     256     12     12      2     1     1    0      0   L  
02670000 00001002     256     12     12      4     1     1    0      0   L  
02730000 00001002      64     32     32      4     1     1    0      0   L  
02a20000 00001002    3328   2084   2396    110    23    16    0      0   LFH
02a60000 00001002   19968   7164   8076     35     6    10   21      0   LFH
02f60000 00001003    1280   1152   1152      2     1     1    0    bad      
03470000 00001003    1280    512    512      1     1     1    0    bad      
034b0000 00001003    1280    524    524      2     1     1    0    bad      
034f0000 00001003     256     96     96      0     0     1    0    bad      
03730000 00001003    1280    356    356      1     1     1    0    bad      
03970000 00001003    1280    264    264      0     0     1    0    bad      
049b0000 00001003     256    204    204      3     1     1    0    bad      
049f0000 00001003  130304    128    300     63    11    12    0    bad      
04a30000 00001003  441600    112    188    101    11    12    0    bad  <-éste
04a70000 00001003  167204    352    424    264    49    67    0    bad      
04ab0000 00001003  465716    128  36532    103    20    27    0    bad      
04af0000 00001003  469708    164   1696     92    13    32    0    bad      
04b30000 00001003   46312    324    328    254    47    65    0    bad      
04b70000 00001003    9700    372    372    348    62    64    0    bad      
039c0000 00001002      64     16     16      2     1     1    0      0   L  
04c30000 00001003     256    148    148     92    36     1    0    bad      
<recortado>

En el listado anterior, vemos que hay un par de heaps que han reservado (memoria en estado reservado) más de 400 megabytes, pero que sólo están utilizando (memoria en estado comprometido) un poco más de 100 kilobytes. Entre varios, el heap 04a30000, indicado más arriba en negrilla y con la palabra "<- éste", es uno de los más grandes.

Veamos el detalle de este heap y sus segmentos, listados a continuación.

Index   Address  Name      Debugging options enabled
111:   04a30000 
    Segment at 04a30000 to 04a70000 (00010000 bytes committed)
    Segment at 0f940000 to 0fa40000 (00003000 bytes committed)
    Segment at 0fa40000 to 0fc40000 (00001000 bytes committed)
    Segment at 100d0000 to 104d0000 (00001000 bytes committed)
    Segment at 104d0000 to 10cd0000 (00001000 bytes committed)
    Segment at 10cd0000 to 11cd0000 (00001000 bytes committed)
    Segment at 11cd0000 to 13cd0000 (00001000 bytes committed)
    Segment at 13cd0000 to 17cd0000 (00001000 bytes committed)
    Segment at 17ed0000 to 1fed0000 (00001000 bytes committed)
    Segment at 4dbd0000 to 55bd0000 (00001000 bytes committed)
    Segment at 5bb60000 to 5eb60000 (00001000 bytes committed)

Mmm... mmm...mmm...mmm (esto me recuerda una canción de hace unos años), la mayoría de ellos no tiene más de 4 kilobytes usados para bloques de varios megabytes reservados. Si las matemáticas no te ayudan ahora, 1000 en hexadecimal es equivalente a 4096 en decimal.

Análisis de la situación

Recordemos que el manejo de la memoria lo realiza generalmente el sistema operativo aunque algunas aplicaciones pueden utilizar sus propios manejadores de memoria. Desde código ASP (VBScript) o Visual Basic 6.0, como también desde código manejado NO es posible trabajar a este nivel con la memoria. Lo anterior es un problema en un manejador de memoria.

Si no es ASP, VB. 6.0, ¿qué puede ser? (considerando que no hay componentes desarrollados por el cliente en C o C++)

La respuesta la da Debug Diagnostics. Quien creo el heap es "Microsoft Data Access Runtime", es decir, MDAC. Revisando la versión instalada, comprobamos que es la última con Windows Server 2003 SP2. El camino se pone difícil.

Investigación y resolución

Involucrando a las personas adecuadas, aprendimos que este comportamiento es considerado "esperado" cuando se cumplen las siguientes condiciones:

  • Se utilizan recordset del lado del cliente (client-side cursor)
  • Se obtienen muchos datos, muchos datos de una tabla

Ok ¿client-side cursor?¿que significa "muchos datos"?

"Cliente" es quien consulta la base de datos, que para este caso es IIS/ASP. En ese caso, los datos se llevan al cliente para ser luego procesados.

Después de investigar en el código, se encontró que una consulta estaba retornando más de 2 millones de registros. Eso es mucho Smile

Reproducción

Decidido a demostrarlo, procedí a hacer unas pruebas con el siguiente código en mi "servidor."

Y le agregué a mi tabla algo así como 4 millones de registros.

Después de varias ejecuciones, tanto en paralelo como en serie, los contadores de memoria reservada, comprometida y utilización de procesador mostraron esto:

Se puede ver que la memoria comprometida (verde) llegó como mucho hasta 300 megabytes, pero la memoria reservada (roja) aumentó sin mostrar intención de disminuir, llegando casi hasta 900 megabytes.

¿Cuál es la explicación a que no reutilice la memoria reservada y siga reservando más? Al menos yo no tengo la respuesta.

¿Que sucede cuando llegue a 2 gigabytes? excepciones por falta de memoria (Out Of Memory)

Conclusiones

1.- Nunca desplegar "muchos" registros en una página. Mejor aún, nunca pedir muchos registros a la base de datos.

2.- Utilicen server-side cursors. Hagan la prueba con el mismo código y comparen los resultados. Wink

Saludos,
Patrick

by pmackay
Filed under: ,

Comments

# elmago24 said on Monday, July 21, 2008 11:13 AM

Es impresionante este blog de alto contenido técnico, el 90% de las cosas no las entiendo pero aún asi me parece un trabajo bastante interesante, al autor del blog solo comentarle cual fue su formación técnica para tener los conocimientos q tiene sobre el analisis de aplicaciones? Enhorabuena por el blog!!

# pmackay said on Monday, July 21, 2008 6:00 PM

Hola,

gracias por tus palabras. Si tuviese que definir cual ha sido uno de los mejores libros que te pueden ayudar a realizar este trabajo, elegiría "Windows Internals" de Mark Russinovich y David Solomon.

Entender el funcionamiento de Windows es la base para el resto. IIS, SQL, ASP, ASPNET y otros productos usan las funcionalidades que el sistema operativo expone.

Para este caso en especial, está involucrado el memory manager de windows, y ojo que no significa que funcione mal, pero entendiendo como funciona la memoria en windows, sabes adonde apuntar cuando estás buscando los soluciones para las aplicaciones con problemas.

Hay otros libros muy útiles también, pero lo mas importante es entender "el ambiente" (el sistema operativo) donde corren las aplicaciones.

Saludos

Patrick

# elmago24 said on Tuesday, July 22, 2008 10:45 AM

Supongo que sera fundamental como dices y que te habrán hecho falta conocimientos muy sólidos de sistemas operativos para poder ver como funciona todo el tinglado.

Como sugerencia te animo a que en alguna ocasión y ya que esta tan de moda el J2EE y Java escribas algun post con algún análisis sobre rendimiento y monitoreo de las aplicaciones en la JVM.

Un saludo desde España y felicitaciones de nuevo por el blog, francamente interesante.

Leave a Comment

(required) 
(required) 
(optional)
(required)