psycopg2 fuga de memoria después de una consulta grande

Estoy ejecutando una consulta grande en un script de python en mi base de datos de postgres usando psycopg2 (actualicé a la versión 2.5). Una vez finalizada la consulta, cierro el cursor y la conexión, e incluso ejecuto gc, pero el proceso aún consume una tonelada de memoria (7.3 gb para ser exactos). ¿Me estoy perdiendo un paso de limpieza?

import psycopg2 conn = psycopg2.connect("dbname='dbname' user='user' host='host'") cursor = conn.cursor() cursor.execute("""large query""") rows = cursor.fetchall() del rows cursor.close() conn.close() import gc gc.collect() 

Me encontré con un problema similar y después de un par de horas de sangre, sudor y lágrimas, encontré que la respuesta simplemente requiere la adición de un parámetro.

En lugar de

 cursor = conn.cursor() 

escribir

 cursor = conn.cursor(name="my_cursor_name") 

o mas simple aun

 cursor = conn.cursor("my_cursor_name") 

Los detalles se encuentran en http://initd.org/psycopg/docs/usage.html#server-side-cursors

Encontré las instrucciones un poco confusas porque pensé que tendría que volver a escribir mi SQL para incluir “DECLARE my_cursor_name ….” y luego un “FETCH count 2000 FROM my_cursor_name” pero resulta que psycopg lo hace todo por ti. la campana si simplemente sobrescribe el parámetro predeterminado “nombre = Ninguno” al crear un cursor.

La sugerencia anterior de usar fetchone o fetchmany no resuelve el problema ya que, si deja el parámetro de nombre sin establecer, psycopg intentará por defecto cargar la consulta completa en el ram. La única otra cosa que puede necesitar (además de declarar un parámetro de nombre) es cambiar el atributo cursor.itersize del valor predeterminado 2000 para que diga 1000 si todavía tiene muy poca memoria.

Por favor, vea la siguiente respuesta de @joeblog para la mejor solución.


Primero, no deberías necesitar toda esa memoria RAM en primer lugar. Lo que debería hacer aquí es obtener fragmentos del conjunto de resultados. No hagas un fetchall() . En su lugar, utilice el método cursor.fetchmany mucho más eficiente. Consulte la documentación de psycopg2 .

Ahora, la explicación de por qué no se libera y por qué no es una pérdida de memoria en el uso formalmente correcto de ese término.

La mayoría de los procesos no liberan la memoria al sistema operativo cuando se libera, solo la ponen a disposición para su reutilización en otro lugar del progtwig.

La memoria solo se puede liberar al sistema operativo si el progtwig puede compactar los objetos restantes dispersos a través de la memoria. Esto solo es posible si se utilizan referencias de control indirectas, ya que de lo contrario mover un objeto invalidaría los punteros existentes al objeto. Las referencias indirectas son bastante ineficientes, especialmente en las CPU modernas donde perseguir los punteros hace cosas horribles para el rendimiento.

Lo que generalmente sucede a menos que exista una precaución adicional por parte del progtwig es que cada gran parte de la memoria asignada con brk() termina con algunas piezas pequeñas aún en uso.

El sistema operativo no puede saber si el progtwig considera que esta memoria aún está en uso o no, por lo que no puede reclamarla de nuevo. Como el progtwig no tiende a acceder a la memoria, el sistema operativo generalmente lo intercambiará con el tiempo, liberando la memoria física para otros usos. Esta es una de las razones por las que debe tener espacio de intercambio.

Es posible escribir progtwigs que devuelvan la memoria al sistema operativo, pero no estoy seguro de que puedas hacerlo con Python.

Ver también:

  • python – la memoria no se devuelve al kernel
  • ¿Por qué no se libera la memoria al sistema después de grandes consultas (o series de consultas) en django?
  • Liberar memoria en Python

Entonces: esto no es realmente una pérdida de memoria. Si haces otra cosa que usa mucha memoria, el proceso no debería crecer mucho, si es que lo hace, reutilizará la memoria liberada previamente de la última gran asignación.

Joeblog tiene la respuesta correcta. La forma en que maneja la recuperación es importante pero mucho más obvia que la forma en que debe definir el cursor. Aquí hay un ejemplo simple para ilustrar esto y darle algo para comenzar a copiar y pegar.

 import datetime as dt import psycopg2 import sys import time conPG = psycopg2.connect("dbname='myDearDB'") curPG = conPG.cursor('testCursor') curPG.itersize = 100000 # Rows fetched at one time from the server curPG.execute("SELECT * FROM myBigTable LIMIT 10000000") # Warning: curPG.rowcount == -1 ALWAYS !! cptLigne = 0 for rec in curPG: cptLigne += 1 if cptLigne % 10000 == 0: print('.', end='') sys.stdout.flush() # To see the progression conPG.commit() # Also close the cursor conPG.close() 

Como verá, los puntos se agruparon rápidamente, en lugar de pausar para obtener un búfer de filas (tamaño de archivo), por lo que no es necesario usar fetchmany para el rendimiento. Cuando ejecuto esto con /usr/bin/time -v , obtengo el resultado en menos de 3 minutos, utilizando solo 200 MB de RAM (en lugar de 60 GB con el cursor del lado del cliente) para 10 millones de filas. El servidor no necesita más ram, ya que utiliza la tabla temporal.