pl  |  en

Optymalizacja pamięci w Django

Pamięć na serwerach Megiteam nie jest nieskończona. Wybierając dany plan hostingowy wybieramy limit pamięci RAM jaki będzie przysługiwał na nasze aplikacje. Aplikacje np. Django nie potrzebują wiele pamięci by działać. Jeżeli jednak nasza aplikacja zawiera pewne specyficzne drobne błędy w kodzie to może sobie przypisywać więcej RAMu niż potrzebuje. Ograniczy to ilość aplikacji jakie bylibyśmy w stanie uruchomić na serwerze.

W tym artykule przedstawię kilka metod optymalizacji i wychwytywania nadmiernego wykorzystywania pamięci przez aplikacje Django.

Memstat

Logując się na serwer poprzez SSH mamy do dyspozycji narzędzie memstat, za pomocą którego możemy sprawdzić ile pamięci zużywają poszczególne aplikacje na naszym koncie, np:

memstat -v

00000 *     19.43MB python /usr/local/bin/django.fcgi /home/jakaś/aplikacja

00000 *     19.21MB python /usr/local/bin/django.fcgi /home/jakaś/aplikacja

00000 *     13.77MB python /usr/local/bin/django.fcgi /home/jakaś/aplikacja

00000 *     13.31MB python /usr/local/bin/django.fcgi /home/jakaś/aplikacja

Zazwyczaj aplikacja Django używa nieco ponad dwadzieścia MB RAM. Jeżeli nie musi przetwarzać dużych porcji danych nie powinna przekraczać takich wartości. Istnieje jednak kilka przypadków, w przypadku których aplikacja zaalokuje sobie więcej pamięci RAM – wtedy w memstacie wartość będzie znacznie wyższa i stała. W przypadku wycieków pamięci ilość przypisanego RAMu będzie stale rosła. O wycieki znacznie trudniej – chyba że mamy do czynienia np. z aplikacją wielowątkową, czy przeciekającym binarnym module.

Przypadki nadmiernego przypisania pamięci

Aplikacja przypisze sobie więcej RAMu gdy będzie go potrzebować. Do tego może dojść gdy:

  • W widoku doprowadzamy do wykonania się zapytania pobierającego dużą ilość danych. Próbując robić coś na QuerySet przekształcamy go w ResultSet i wszystko ląduje w pamięci RAM. Jeżeli chcemy coś zmodyfikować w danych przed przekazaniem do szablonu to powinniśmy zastanowić się jak to zrobić efektywniej niż np iterując i modyfikując obiekty (można użyć metody modelu, tagi szablonów, czy pobieranie z bazy tylko potrzebnych pól – jeżeli te niepotrzebne zawierają sporo zbędnych danych jak np. długi tekst).
  • Pobieramy z bazy zbędne dane. Mniej szkodliwy przypadek (choć zależy od modeli). Gdy np. chcemy stworzyć listę ostatnio dodanych artykułów to warto wtedy pobrać tylko potrzebne pola (np. tytuł, slug, id) z pominięciem tych zbędnych (treść).
  • Do pamięci wczytywane są pliki z danymi. Dość szeroka kategoria przypadków. Jeżeli dany widok wykorzystuje z jakiś dodatkowych plików to mogę one trafić do RAMu. Np. geolokalizacja IP za pomocą geoip-python z bazą IP/miast od maxmind może doprowadzić do wczytania tej bazy (zawartej w pliku) do RAMu – kilkadziesiąt MB wskakuje do RAMu. W tym konkretnym przypadku użycie API oferowanego przez Django a nie „surowego” powinno rozwiązać problem. Z innych przypadków – można wpaść np. na obsłudzeplików – używając metody read() wczytujemy całą zawartość pliku. Jeżeli to możliwe warto zastosować iterator.
f = open('myfile.txt', 'r')
for line in f:

Takie problemy dość łatwo wykryć. Wystarczy zrestartować aplikację, odświeżyć w przeglądarce daną podstronę i sprawdzić zużycie RAMu. Jeżeli zaalokowane zostanie więcej niż wynosi przeciętna wartość dla aplikacji to coś z tej ilości pamięci korzystało. Django-debug-toolbar może pomóc z zapytaniami. O rozpoznanie problemów z plikami musimy postarać się sami.

Jeżeli ilość zaalokowanej pamięci stale i szybko rośnie to w jakimś module następuje wyciek pamięci (memory leak). Łatwo o to w aplikacjach wielowątkowych, czy w kod
zie stosującym binarne rozszerzenia (nie każde jest „thread-safe” czy nawet samo z siebie wolne od wycieków pamięci). Jeżeli mamy taki kod to w przypadku widocznego szy
bkiego wzrostu zużycia pamięci powinniśmy zacząć od binarnych modułów a następnie od wielowątkowego kodu. Sama aplikacja Django w przeciągu dni może powoli alokować nie
co więcej RAMu, ale naprawdę powinno to zajmować wiele dni (wtedy można robić restart co tydzień, czy kilka dni).

Nadmierna ilość aplikacji Django

Każda dodatkowa aplikacja to kolejna porcja RAMu będąca w użyciu. W niektórych przypadkach zamiast uruchamiać dwie oddzielne aplikacje (np. dwie wersje językowe dane
go serwisu) można uruchomić jedną aplikację przypiętą pod obie domeny. Następnie w aplikacji dodajemy middleware, które sprawdza dla której domeny wysyłane jest żądanie
i w zależności od wartości – ustawiający np. odpowiednie ID strony potrzebne do pobrania danych z tabeli danej wersji językowej.

#!/usr/bin/python
# -*- coding: utf-8 -*-
from django.conf import settings
from django.utils import translation

from myapp.models import Content

class domainMiddleware(object):
    """
    Get the current sub/domain and try to find config for it
    """
    def process_request(self, request):
        domain = request.get_host()
        if domain == 'foo.bar.pl':
            Content._meta.db_table = 'content_table_en'
            settings.SITE_ID = 1
            translation.activate('en')

Mam nadzieję że ten artykuł pozwoli Ci wyciągnąć maksimów efektywności dla aplikacji na hostingu megiteam.pl 😉

  • tomkeG

    Dzięki za wskazówki!

  • Świetne. W moim przypadku najwięcej pamięci zużywa sorl.thumbnail, szczególnie kiedy np na stronie głównej znajduje się pokaz slajdów, dostosowujących rozmiar do rozdzielczości ekranu usera.
    Próbowałem ograniczyć liczbę generowanych grafik poprzez:       
    width = int(request.GET.get(‚w’))
            height = int(request.GET.get(‚h’))

            width = width – width % 100
            height = height – height % 100
    i teraz generuje grafiki różniące się o 100px, a nie o 1px, co znacznie zmniejsza nie tylko liczbę plików w cache’u, ale i samych operacji.

    Jednak problem pozostał, jeśli wyczyszczę cache thumbnail’a i wejdę na stronę z kilku różnych komputerów to aplikacja będzie zajmowała nawet ponad 80MB. Podobnie będzie jeśli klient zmieni grafiki slajdów korzystając z panelu admina.

    Jeśli komukolwiek udało się ustawić limit pamięci jakiej potrzebuje thumbnail do przeskalowania pliku to będę wdzięczny za wskazówkę lub być może jest sposób, aby zmusić funkcję get_thumbnail() do zwolnienia pamięci po zakończeniu pracy?

    PS w komentarzach nie zapisuje się po dodaniu formatowanie tekstu, trzeba edytować wpis, aby to naprawić:)

    • meme

      @darnok:disqus 
      Generuj miniaturki podczas uploadu. Jeśli jest ich dużo (za dużo), pomyśl o przerzuceniu tego zadania na kolejkę (np. asynchroniczną – celery).

      > Jeśli komukolwiek udało się ustawić limit pamięci jakiej potrzebuje thumbnail do przeskalowania pliku

      Nie ma czegoś takiego. Poza tym to nie sorl.thumbnail zjada pamięć tylko PIL, który jest „fatalnie nieoptymalny”. Alternatywą może być np. ImageMagick (PythonMagick)