2017-04-04 119 views
0

Heroku在Heroku上遇到內存問題。運行Django應用程序(使用gunicorn)時,Heroku在運行Django應用程序時沒有釋放內存

我有以下代碼需要用戶上傳的圖像,刪除所有的EXIF數據,並返回圖像準備上傳到S3。這既用作表單數據清理器,也用於將base64數據讀入內存。

def sanitise_image(img): # img is InMemoryUploadedFile 
    try: 
     image = Image.open(img) 
    except IOError: 
     return None 
    # Move all pixel data into a new PIL image 
    data = list(image.getdata()) 
    image_without_exif = Image.new(image.mode, image.size) 
    image_without_exif.putdata(data) 

    # Create new file with image_without_exif instead of input image. 
    thumb_io = StringIO.StringIO() 
    image_without_exif.save(thumb_io, format=image.format) 
    io_len = thumb_io.len 
    thumb_file = InMemoryUploadedFile(thumb_io, None, strip_tags(img.name), img.content_type, 
            io_len, None) 
    # DEL AND CLOSE EVERYTHING 
    thumb_file.seek(0) 
    img.close() 
    del img 
    thumb_io.close() 
    image_without_exif.close() 
    del image_without_exif 
    image.close() 
    del image 
    return thumb_file 

我基本上採取一個InMemoryUploadedFile並返回一個新的只有像素數據。

delclose S可以是多餘的,但它們代表我試圖解決其中的Heroku內存使用率不斷增長,並沒有公佈每到這個函數結束時,即使剩餘通宵的情況: Heroku metrics

上運行此本地主機與Guppy和繼教程,沒有剩餘的InMemoryUploadedFile s,StringIO s和PIL Image留在堆中,讓我感到困惑。

我懷疑Python不會將內存釋放回操作系統,因爲我已經閱讀過SO上的多個線程。有沒有人玩過InMemoryUploadedFile,可以給我一個解釋,爲什麼這個內存不被釋放?

當我不執行這個sanitisation,問題不會發生。

非常感謝!

回答

0

最終我找到了自己的答案。非常感謝瑞安Tran指出我正確的方向。 list()確實會導致泄漏。

使用等效split()merge()方法(docs),這是更新的代碼:

with Image.open(img) as image: 
     comp = image.split() 
     image_without_exif = Image.merge(image.mode, comp) 
     thumb_io = StringIO.StringIO() 
     image_without_exif.save(thumb_io, format=image.format) 
     io_len = thumb_io.len 
     clean_img = InMemoryUploadedFile(thumb_io, None, strip_tags(img.name), img.content_type, 
             io_len, None) 
     clean_img.seek(0) 
return clean_img 
1

我認爲這個問題是創建臨時表對象:

data = list(image.getdata()) 

嘗試:

image_without_exif.putdata(image.getdata()) 


這就是爲什麼我認爲這是問題:

>>> images = [Image.new('RGBA', (100, 100)) for _ in range(100)] 

Python內存使用增加了〜4Mb。

>>> get_datas = [image.getdata() for image in images] 

無記憶增加。

>>> pixel_lists = [list(image.getdata()) for image in images] 

Python內存使用增加了〜85Mb。


你可能不希望做的GetData()到一個列表,除非你需要的數量作了明確規定。從枕頭docs

請注意,此方法返回的序列對象是內部PIL數據類型,它只支持某些序列操作。要將其轉換爲普通序列(例如,用於打印),請使用list(im.getdata())。

+0

嘿!謝謝你的幫助。不幸的是,'putdata'需要一個RGB元組列表:'這種方法將數據從一個 序列對象拷貝到圖像中,從左上角的 開始(0,0),並且一直持續到圖像或者序列結束'我嘗試了你說的確切的一行,但是'putdata'不能接受那種對象,並且'putdata([image.getdata()])'但是無濟於事。 –