2013-06-19 111 views
11

私はpsycopg2(バージョン2.5にアップグレード)を使用している私のpostgresデータベースに対してPythonスクリプトで大きなクエリを実行しています。クエリが終了した後、私はカーソルと接続を閉じ、gcを実行しますが、プロセスは依然として大量のメモリを消費します(正確には7.3GBです)。クリーンアップの手順が欠けていますか?psycopg2大きなクエリの後にメモリがリークする

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

答えて

8

よりよい解決策を@joeblogによって次の解答を参照してください。


最初に、すべてのRAMが最初に必要なわけではありません。ここで行うべきことは、結果セットのチャンクをフェッチすることです。 fetchall()はしないでください。代わりに、はるかに効率的なcursor.fetchmanyメソッドを使用してください。 the psycopg2 documentationを参照してください。

ここでは、なぜそれが解放されないのか、なぜその言葉を正式に正しく使用するにあたってメモリリークではないのかについての説明です。

ほとんどのプロセスは、解放された時点でメモリをOSに戻さないため、プログラム内の他の場所で再利用できるようにしています。

メモリは、メモリに散在している残りのオブジェクトを圧縮することができる場合にのみ、OSに解放されます。これは間接ハンドル参照が使用されている場合にのみ可能です。それ以外の方法でオブジェクトを移動すると、オブジェクトへの既存のポインタが無効になるためです。間接参照はむしろ非効率的です。特に、ポインターを追いかけていくことで性能が低下する現代のCPUでは、非効率的です。

プログラムによって余分な注意が払われない限り、通常、使用されているいくつかの小さなピースを使用して、brk()で割り当てられた大きなメモリの各チャンクが上がります。

OSは、このメモリがまだ使用中であるとみなしているかどうかをプログラムが判断しているかどうかを判断することはできません。プログラムはメモリにアクセスする傾向がないので、OSは通常、時間の経過とともにそれを交換し、他の用途のために物理メモリを解放する。これは、スワップ領域が必要な理由の1つです。

メモリを手渡すプログラムをOSに書き込むことは可能ですが、私はあなたがPythonでそれを行うことができるとは確信していません。

参照:

ので:これは実際にはメモリリークではありません。たくさんのメモリを使用する何か他のことをすれば、前回の大きな割り当てから解放されたメモリを再利用することになります。

+0

ありがとうございます!同じプロセスで上記のコードを2回実行することによってメモリが再利用されることを確認しました。 2回目の実行中にメモリが増加しませんでした。 –

+3

ここで述べたことはすべて正しいものですが、通常はクエリ結果はクライアント側で完全に転送されます( 'fetch *()'ではなく 'execute()')。したがって、 'fetchall()'の代わりに 'fetchmany()'を使うと、Pythonオブジェクトの作成という点でメモリが節約されるかもしれません。 – piro

27

私は同様の問題に遭遇し、数時間の血液、汗や涙の後、その答えがただ一つのパラメータの追加を必要とすることがわかりました。

代わりの

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

で発見され、私は命令を見つけました私はちょっと混乱していますが、 "DECLARE my_cursor_name ...."と "FETCH count 2000 FROM my_cursor_name"を含めるようにSQLを書き直す必要がありますが、それはpsycopgが皆さんのためにカーソルを作成するときには、単に "name = None"のデフォルト・パラメーターを上書きします。

上記のfetchoneまたはfetchmanyを使用しても、名前パラメータを設定しないと、psycopgはデフォルトでRAMにクエリ全体をロードしようとするため、問題は解決しません。あなたがまだ余りにもメモリを持っていない場合、(名前パラメータを宣言する以外に)あなたが必要とする可能性がある唯一の他のものは、デフォルトの2000からcursor.itersize属性を1000に変更することです。

+0

私はOOMの問題を避けるために役立つ 'sqlalchemy'に何も見つかりませんでしたが、この解決策は私のために働いていました。ありがとうございました! –

7

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

あなたは、パフォーマンスのためにfetchmanyを使用する必要はありませんので、ドットは、休止が行(itersize)のバッファを取得するよりも、急速にグループで来ました。 /usr/bin/time -vでこれを実行すると、1000万行で200MBのRAM(クライアント側のカーソルでは60GBではなく)のみを使用して3分以内で結果が得られます。テンポラリテーブルを使用しているため、サーバーにはさらにRAMが必要ありません。

関連する問題