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

28 lipca 2012



blog comments powered by Disqus