2013-06-19 6 views
11

Używam dużej kwerendy w skrypcie Pythona przeciwko mojej bazy danych Postgres przy użyciu psycopg2 (zaktualizowałem do wersji 2.5). Po zakończeniu zapytania zamykam kursor i połączenie, a nawet uruchamiam gc, ale proces nadal zużywa masę pamięci (dokładnie 7,3 gb). Czy brakuje mi etapu czyszczenia?psycopg2 wycieka pamięć po dużym zapytaniu

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

Odpowiedz

8

proszę zobaczyć następną odpowiedź przez @joeblog dla lepszego rozwiązania.


Po pierwsze, nie powinieneś potrzebować całej tej pamięci RAM. To, co powinieneś tutaj zrobić, to pobranie zestawu fragmentów. Nie rób fetchall(). Zamiast tego użyj znacznie wydajniejszej metody. Zobacz the psycopg2 documentation.

Teraz wyjaśnienie, dlaczego nie jest ona uwalniana i dlaczego nie jest to przeciek pamięci w formalnie poprawnym użyciu tego terminu.

Większość procesów nie zwalnia pamięci z powrotem do systemu operacyjnego po zwolnieniu, po prostu udostępnia ją do ponownego użycia w innym miejscu w programie.

Pamięć może zostać zwolniona do systemu operacyjnego tylko wtedy, gdy program może zagęścić pozostałe obiekty rozproszone w pamięci. Jest to możliwe tylko w przypadku użycia referencji pośrednich uchwytów, ponieważ w przeciwnym razie przeniesienie obiektu unieważniłoby istniejące wskaźniki do obiektu. Pośrednie referencje są raczej nieefektywne, zwłaszcza w nowoczesnych procesorach, w których pogoń za wskaźnikami powoduje, że straszne rzeczy są osiągane.

To, co zwykle zdarza się, chyba że program wykona dodatkową ostrożność, polega na tym, że każda duża porcja pamięci przydzielona z brk() wyląduje z kilkoma małymi kawałkami, które wciąż są w użyciu.

System operacyjny nie może stwierdzić, czy program nadal używa tej pamięci, czy nie, więc nie może po prostu odebrać jej. Ponieważ program nie ma dostępu do pamięci, system operacyjny zazwyczaj wymienia go z czasem, uwalniając pamięć fizyczną do innych zastosowań. Jest to jeden z powodów, dla których powinieneś mieć przestrzeń wymiany.

Możliwe jest pisanie programów z pamięcią podręczną do systemu operacyjnego, ale nie jestem pewien, czy można to zrobić za pomocą Pythona.

Zobacz także:

Więc: nie jest to faktycznie pamięć wyciek. Jeśli zrobisz coś, co wymaga dużej ilości pamięci, proces ten nie powinien zbytnio wzrosnąć, jeśli w ogóle, użyje ponownie zwolnionej pamięci z ostatniej dużej alokacji.

+0

Dzięki! Potwierdzono, że pamięć jest ponownie wykorzystywana, uruchamiając powyższy kod dwa razy w tym samym procesie. Pamięć nie wzrosła podczas drugiego uruchomienia. –

+3

Chociaż wszystko, co tu powiedziano, jest poprawne, wynik zapytania zwykle zostanie przeniesiony całkowicie po stronie klienta (nie przez 'fetch *()', lecz przez 'execute()'). Tak więc podczas używania 'fetchmany()' zamiast 'fetchall()' może zaoszczędzić trochę pamięci w kategoriach tworzenia obiektów Pythona, przy użyciu kursora po stronie serwera sugerowanego przez @joeblog jest właściwym rozwiązaniem. – piro

27

Wpadłem na podobny problem i po kilku godzinach krwi, potu i łez, odpowiedź na to pytanie wymagała jedynie dodania jednego parametru.

Zamiast

cursor = conn.cursor() 

zapisu

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

lub prostszy jeszcze

cursor = conn.cursor("my_cursor_name") 

Szczegóły znajdują się na http://initd.org/psycopg/docs/usage.html#server-side-cursors

znalazłem instrukcję jest trochę mylące, ponieważ musiałem przepisać mój SQL, aby dołączyć "DECLARE my_cursor_name ....", a następnie "FETCH count 2000 FROM my_cursor_name", ale okazało się, że psycopg robi to wszystko dla ciebie pod maską, jeśli po prostu nadpisujesz parametr domyślny "name = None" podczas tworzenia kursora.

Powyższa sugestia użycia fetchone lub fetchmany nie rozwiązuje problemu, ponieważ jeśli nie ustawisz parametru nazwy, psycopg domyślnie spróbuje załadować całe zapytanie do pamięci RAM. Jedyną inną rzeczą, którą możesz potrzebować (oprócz deklarowania parametru nazwy) jest zmiana atrybutu cursor.itersize z domyślnego 2000 na 1000, jeśli wciąż masz za mało pamięci.

+0

Nie mogłem znaleźć niczego w 'sqlalchemy', co pomogło mi uniknąć problemu OOM, ale to rozwiązanie sprawdziło się. Dziękuję Ci! –

7

Joeblog ma poprawną odpowiedź. Sposób radzenia sobie z pobieraniem jest ważny, ale znacznie bardziej oczywisty niż sposób definiowania kursora. Oto prosty przykład, aby to zilustrować i dać ci coś do skopiowania - wklej na początek.

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

Jak widać, kropki przyszedł grupy szybko, niż przerwa uzyskać buforu wierszy (itersize), dzięki czemu nie trzeba używać fetchmany dla wydajności. Kiedy uruchomię to z /usr/bin/time -v, otrzymam wynik w mniej niż 3 minuty, używając tylko 200 MB pamięci RAM (zamiast 60 GB z kursorem po stronie klienta) dla 10 milionów wierszy. Serwer nie potrzebuje więcej pamięci RAM, ponieważ korzysta z tabeli tymczasowej.