2011-03-31 6 views
22

En primer lugar, DEBUG = False en settings.py, entonces no, connections['default'].queries no está creciendo y creciendo hasta que consume toda la memoria.¿Por qué la memoria no se libera al sistema después de grandes consultas (o series de consultas) en django?

Comencemos con el hecho de que he cargado la tabla User de django.contrib.auth.models.User con 10000 usuarios (cada uno llamado 'test #' donde # es un número entre 1 y 10000).

Aquí está la vista:

from django.contrib.auth.models import User 
from django.http import HttpResponse 

import time 

def leak(request): 
    print "loading users" 

    users = [] 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 
    users += list(User.objects.all()) 

    print "sleeping" 
    time.sleep(10) 

    return HttpResponse('') 

He adjuntado la vista por encima de la /leak/ url e iniciar el servidor de desarrollo (con DEBUG = False, y he probado y no tiene nada a hacer con ejecutar un servidor de desarrollo frente a otras instancias).

después de correr:

% curl http://localhost:8000/leak/ 

El proceso de la memoria de ejecución del servidor crece a alrededor del tamaño visto desde ps aux de salida por debajo y luego se mantiene en ese nivel.

USER  PID %CPU %MEM VSZ RSS TTY  STAT START TIME COMMAND 
dlamotte 25694 11.5 34.8 861384 705668 pts/3 Sl+ 19:11 2:52 /home/dlamotte/tmp/django-mem-leak/env/bin/python ./manage.py runserver 

A continuación, ejecutar el comando anterior curl anterior no parecen crecer el uso de memoria de la instancia (que yo esperaba de una verdadera pérdida de memoria?), Debe ser re-uso de la memoria? Sin embargo, creo que hay algo mal aquí que la memoria no se libera en el sistema (sin embargo, entiendo que puede ser un mejor rendimiento que Python NO libere la memoria).

Después de esto, ingenuamente intenté ver si python liberaría grandes trozos de memoria que asignó. Así que intento lo siguiente de una sesión de pitón:

>>> a = '' 
>>> a += 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' * 10000000 
>>> del a 

La memoria se asigna en la línea a += ... como se esperaba, pero cuando del a sucede, se libera la memoria. ¿Por qué el comportamiento es diferente para los conjuntos de consulta django? ¿Es algo que django tiene la intención de hacer? ¿Hay alguna forma de cambiar este comportamiento?

Literalmente he pasado 2 días depurando este comportamiento sin saber a dónde ir ahora (aprendí a usar guppy Y objgraph que parecen no señalar nada interesante que pueda entender).

ACTUALIZACIÓN: Esta podría ser la gestión de memoria, simplemente pitón en el trabajo y no tienen nada que ver con Django (django sugerido en la lista de distribución), pero me gustaría la confirmación de alguna manera replicar esto en pitón exterior de Django.

ACTUALIZACIÓN: Al usar la versión Python 2.6.5

+0

+1. Muy interesado en las respuestas de esta pregunta. También reconocí pérdidas de memoria extrañas con Django durante algún tiempo en la producción (DEBUG = False también e incluso con aplicaciones/proyectos muy simples). –

+0

¿Qué sucede si "del usuario"? –

+0

¿Qué versión de Python está usando (tanto para el servidor como para la prueba de línea de comando)? Las versiones anteriores no liberan la memoria asignada para los objetos al sistema, por lo que si su versión local es 2.5 o posterior, pero su servidor está ejecutando 2.4, ese puede ser su problema. Las grandes asignaciones individuales (como su secuencia grande) también pueden omitir el asignador: en su lugar, vea lo que sucede con algo como '([[]] * 10 ** 6)'. – ncoghlan

Respuesta

24

Decidí convertir mis comentarios en una respuesta para aclarar las cosas.

Desde Python 2.5, la asignación de memoria CPython hace un seguimiento del uso de la memoria interna por el pequeño asignador de objetos, e intenta devolver arenas completamente libres al sistema operativo subyacente. Esto funciona la mayor parte del tiempo, pero el hecho de que los objetos no puedan moverse en la memoria significa que la fragmentación puede ser un problema grave.

Pruebe el siguiente experimento (utilicé 3.2, pero 2.5+ debería ser similar si se utiliza xrange):

# Create the big lists in advance to avoid skewing the memory counts 
seq1 = [None] * 10**6 # Big list of references to None 
seq2 = seq1[::10] 

# Create and reference a lot of smaller lists 
seq1[:] = [[] for x in range(10**6)] # References all the new lists 
seq2[:] = seq1[::10] # Grab a second reference to 10% of the new lists 

# Memory fragmentation in action 
seq1[:] = [None] * 10**6 # 90% of the lists are no longer referenced here 
seq2[:] = seq1[::10] # But memory freed only after last 10% are dropped 

nota, incluso si se le cae las referencias a seq1 y seq2, es probable que la secuencia anterior deja tu proceso de Python con mucha memoria extra.

Cuando la gente habla de que PyPy usa menos memoria que CPython, esta es una parte importante de lo que están hablando. Debido a que PyPy no utiliza referencias directas de punteros bajo el capó, es capaz de usar un GC compactador, evitando así gran parte del problema de fragmentación y devolviendo la memoria al sistema operativo de manera más confiable.

+0

El equipo de Dropbox en realidad dio una muy buena descripción de este problema en su charla de Pycon (mencionada por primera vez en la diapositiva mostrada a las 18:00, más detalles en ~ 26: 00): http://pycon.blip.tv/file/4878722/ – ncoghlan

+0

Buena respuesta. Muy buena explicación. ¡Gracias! – dlamotte

+0

¿Podría vincular a las partes relevantes de la fuente? –

5

Un montón de aplicaciones, tiempos de ejecución de lenguaje, y tal vez incluso algunos asignadores de memoria del sistema mantendrá la memoria desasignado en el lugar durante el mayor tiempo posible con el fin de volver a utilizar es solo por propósitos de rendimiento. En un sistema complejo como Django podría ser cualquier cantidad de extensiones, posiblemente implementadas en C, que muestren este comportamiento, o podría ser Python con algún tipo de agrupación de memoria o recolección de basura perezosa.

Incluso podría ser la implementación de malloc subyacente que hace esto, o su sistema operativo mantiene una cierta cantidad de espacio de memoria asignada a su proceso aunque el proceso no lo esté usando explícitamente.Sin embargo, no me cites sobre esto, ha pasado un tiempo desde que investigué esas cosas.

En general, si repite el proceso de asignación después de que el alloc inicial y dealloc no dupliquen la cantidad de memoria utilizada, lo que está viendo no es una pérdida de memoria sino un agrupamiento de memoria. Es probable que sea un problema si tiene muchos procesos que compiten por una cantidad limitada de memoria en esa máquina.

+0

Buena respuesta, simplemente no emocionado con mucho de la" mano agitando "explicación mágicamente ... Me gusta que ncoghlan dio un ejemplo de cómo repetir el problema, gracias por su entrada a través – dlamotte

+0

Lamentablemente, es necesario un poco de handwaving, porque es muy raro que obtenga una asignación completamente determinista entre la asignación de recursos en un lenguaje de alto nivel y el uso de memoria de proceso en el nivel del sistema operativo en estos días - los administradores de memoria tienen demasiadas optimizaciones para eso. Es por eso que no se puede diagnosticar una pérdida de memoria de 'ps' o del Administrador de tareas solo. – Kylotan

+0

ncoghlan ve haberlo explicado de una manera que tenga sentido La memoria se fragmenta y, por lo tanto, no puede ser liberada por el intérprete. Estoy seguro de que los gerentes de memoria entran en juego en algún momento ... pero creo que te has echado a perder fuera de proporcion. El hecho es que la memoria estaba fragmentada y no tiene nada que ver con algo más profundo que eso. – dlamotte

Cuestiones relacionadas