Подтвердить что ты не робот

Psycopg2 утечка памяти после большого запроса

Я запускаю большой запрос в python script для моей базы данных postgres, используя psycopg2 (я обновлен до версии 2.5). По завершении запроса я закрываю курсор и соединение и даже запускаю gc, но процесс все еще потребляет тонну памяти (точнее, 7.3gb). Мне не хватает шага очистки?

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()
4b9b3361

Ответ 1

Пожалуйста, см. следующий ответ от @joeblog для лучшего решения.


Во-первых, вам не нужна вся эта оперативная память. То, что вы должны делать здесь, - это выборку набора результатов. Не выполняйте fetchall(). Вместо этого используйте гораздо более эффективный метод cursor.fetchmany. См. документация psycopg2.

Теперь объяснение того, почему оно не освобождено, и почему это не утечка памяти при формальном правильном использовании этого термина.

Большинство процессов не освобождают память обратно в ОС при ее освобождении, они просто делают ее доступной для повторного использования в другом месте программы.

Память может быть освобождена только ОС, если программа может сжать оставшиеся объекты, разбросанные по памяти. Это возможно только в том случае, если используются ссылки косвенного обращения, поскольку в противном случае перемещение объекта приведет к аннулированию существующих указателей на объект. Косвенные ссылки довольно неэффективны, особенно на современных процессорах, где ходовые указатели делают ужасные вещи для производительности.

Что обычно происходит, если особая осторожность не проявляется программой, так это то, что каждый большой кусок памяти, выделенный с помощью brk(), приземляется с несколькими маленькими кусочками, которые все еще используются.

ОС не может сказать, использует ли программа эту память, которая еще используется или нет, поэтому она не может просто потребовать ее обратно. Поскольку программа не имеет доступа к памяти, операционная система обычно заменяет ее со временем, освобождая физическую память для других целей. Это одна из причин того, что у вас должно быть место подкачки.

Возможно записать программы, которые возвращают оперативную память ОС, но я не уверен, что вы можете сделать это с помощью Python.

См. также:

Итак: на самом деле это не утечка памяти. Если вы делаете что-то еще, использующее много памяти, этот процесс не должен сильно увеличиваться, если он вообще будет использовать ранее освобожденную память из последнего большого выделения.

Ответ 2

Я столкнулся с подобной проблемой, и через пару часов крови, пота и слез нашел ответ просто требует добавления одного параметра.

Вместо

cursor = conn.cursor()

записи

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

или еще проще

cursor = conn.cursor("my_cursor_name")

Подробности можно найти на http://initd.org/psycopg/docs/usage.html#server-side-cursors

Я нашел инструкции немного запутанными в том, что я, хотя мне нужно будет переписать мой SQL, чтобы включить "DECLARE my_cursor_name....", а затем "FETCH count 2000 FROM my_cursor_name", но оказывается, что psycopg делает это для вас под капотом, если вы просто перезаписываете параметр "name = None" по умолчанию при создании курсора.

Вышеупомянутое предложение об использовании fetchone или fetchmany не решает проблему, поскольку, если оставить параметр имени unset, psycopg по умолчанию попытается загрузить весь запрос в ram. Единственное, что вам может понадобиться (помимо объявления параметра имени), это изменить атрибут cursor.itersize из 2000 по умолчанию, чтобы сказать 1000, если у вас по-прежнему слишком мало памяти.

Ответ 3

У Joeblog есть правильный ответ. Способ, которым вы имеете дело с извлечением, важен, но гораздо более очевидным, чем способ определения курсора. Вот простой пример, чтобы проиллюстрировать это и дать вам что-то скопировать-вставить для начала.

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()

Как вы увидите, точки быстро попали в группу, чем пауза, чтобы получить буфер строк (itersize), поэтому вам не нужно использовать fetchmany для производительности. Когда я запускаю это с помощью /usr/bin/time -v, я получаю результат менее чем за 3 минуты, используя только 200 МБ ОЗУ (вместо 60 ГБ с курсором на стороне клиента) для 10 миллионов строк. Серверу не требуется больше бара, так как он использует временную таблицу.