sharpek.net

moje trzy grosze

„Wysokowydajne” django

Do popełnienia tego wpisu zainspirowało mnie opublikowanie książki highperformancedjango. Cena 40$ za 150 stron to według mnie dość dużo. Dlatego postanowiłem napisać coś od siebie na temat django i tego w jaki sposób przyśpieszyć jego wydajność.

W internecie można znaleźć bardzo dużo benchmarków frameworków webowych. Niestety robiący je ludzie nie mają dużego doświadczenie ze wszystkimi testowanymi frameworkami. W wyniku tego powstają potworki w których ktoś do testowania wydajności pyramid/flask/django używa przestarzałego i wolnego mod_python. Według mnie porównywanie wydajności django/pyramid/flask/ror/symfony jest bez sensu, ponieważ gdy tylko się uprzemy to z każdego wyciągniemy porównywalne wartości (pytanie jakim kosztem). W poniższym wpisie opiszę kilka rzeczy dotyczących samego środowiska django.

Serwery aplikacyjne

Aplikacje napisane w pythonie można serwować w różnoraki sposób. Niektórzy do tego użyją apache + mod_wsgi inni zaś gunicorn + nginx jako reverse proxy. Można też odpalić uwsgi bezpośrednio na porcie 80, jak kto lubi. Według mnie najbardziej wydajnym i zarazem nie rodzącym dużych problemów jest użycie nginx + uwsgi jako upstream.

Na temat wydajności wyżej wymienionych sposobów można poczytać w tym dość ciekawym artykule.

Baza danych

Obecnie najlepszym rozwiązaniem jest stosowaniem postgresa jako serwera baz danych. Oprócz różnych specyficznych pól, możemy wykorzystać narzędzia typu pgBouncer i pgPool co dość znacznie może przyśpieszyć naszą aplikację. Oczywiście jeśli nie mamy możliwości korzystania z takich narzędzi warto rozważyć włączenie persistent connections w naszej aplikacji.

Szablony

Wbrew temu co można przeczytać django posiada bardzo prymitywny, niedopracowany i ogólnie beznadziejny system szablonów. W dodatku działa on on dość wolno. Na szczęście można przyśpieszyć jego działanie włączając cache poprzez modyfikację TEMPLATE_LOADERS.

TEMPLATE_LOADERS = (
    ('django.template.loaders.cached.Loader', (
        'django.template.loaders.filesystem.Loader',
        'django.template.loaders.app_directories.Loader',
    )),
)

Takie rozwiązanie ma jedną dość dużą wadę – za każdym razem gdy dokonamy zmian w szablonach, wymagany będzie restart aplikacji (dlatego nie zalecam stosować tego na środowisku innym niż produkcyjne).

Jeśli zaczynam pisać aplikację od podstaw warto zastanowić się nad używaniem innego systemu szablonów JINJA2. Szablony jinja2 prócz tego że są o wiele szybsze, rozwiązują też wiele problemów składniowych.

Sesje

Domyślnie system sesji  w django oparty jest na bazie danych, przez co jest to dość wolne rozwiązanie. Dość popularna jest zmiana zmiana ENGINE dla tabeli django_session na MEMORY. Należy jednak pamiętać że sesje stare sesje nie są automatyczne usuwane, przez co co jakiś czas nasza aplikacja uraczy nas błędem: „TABLE IS FULL”.

Prócz sesji opartych o bazę danych możemy korzystać z dwóch innych rozwiązań:

  • django.contrib.sessions.backends.cache
  • django.contrib.sessions.backends.cached_db

Główną różnica jest taka że cached_db trzyma sesje również w bazie danych, dzięki czemu nawet gdy wyczyścimy cały cache naszej aplikacji, zalogowani użytkownicy nie zostaną wylogowani. Dopiero od wersji 1.7 django umożliwia zdefiniowania SESSION_CACHE_ALIAS.

SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"

Najlepszym rozwiązaniem dla sesji w django jest IMO używanie serwera REDISa jako bazy danych dla sesji. Rozwiązanie to dwóch rzeczy, serwera redis oraz zainstalowania biblioteki django-redis-session. Z tego rozwiązania korzystam już od dłuższego czasu i w przeciwieństwie do poprzednich rozwiązań, nie miałem z tym żądnego problemu.

CACHE

Odpowiedni backend

Django wspiera różne mechanizmy do cachowania. Najlepszym rozwiązaniem jest oczywiście używanie memcache. Django „natywnie” wspiera dwie biblioteki dla memcache:

  • django.core.cache.backends.memcached.PyLibMCCach
  • django.core.cache.backends.memcached.MemcachedCache

Lepszym rozwiązaniem jest używanie PylibMC (biblioteka napisane w C). Warto też zapoznać się z tym co oferuje zewnętrzna biblioteka django-pylibmc (pozwala na większą konfiguracje klienta pylibmc która według zapewnień autora jest o wiele szybsza).

Jeśli nasza aplikacja korzysta w bardzo dużym stopniu z cache, warto zastanowić się nad wykorzystaniem twemproxy, w szczególności gdy serwery memcache/redis są na innych maszynach.

Cache na poziomie serwera www (nginx)

Mimo tego że korzystamy z memcache, nasza aplikacja w dalszym ciągu będzie dostawać zapytania (przez co procesy uwsgi będą blokowane).
Jeśli mamy widoki które są bardzo popularne należy rozważyć korzystanie z dwu poziomowego cache. Dzięki takiemu zabiegowi będziemy mogli obsłużyć więcej requestów na poziomie naszej aplikacji (reszta będzie leciała bezpośrednio z serwera www), tak właśnie robimy w internetowykantor.pl.

W django ustawiamy klucz który jest taki sam jak URL widoku, natomiast serwer nginx konfigurujemy tak by przy każdym zapytaniu sprawdzał czy znajduje się jakiś klucz w memcache, jeśli klucz istnieje to serwer zwraca bezpośrednio jego wartość.

Przykładowa konfiguracja serwera nginx:

location /api {
 
	default_type       text/html;
 
	set $memcached_key :1:$uri;
	memcached_pass unix:/tmp/memcache-1;
	error_page 404 502 504  = @app;
}

Kiedy nie używać cache ?

Korzystanie z cache nie zawsze przyśpieszy naszą aplikację. Niekiedy możemy uzyskać przeciwny efekt, tak jak na załączonym poniżej obrazku:

cache_fail

Cóż, czasem lepiej zrobić select_related, czasem też nie – o tym napiszę w następnym wpisie w którym skupię się na wydajności ORM oraz zapytań jakie generuje i dlaczego czasem nie warto używać ORM.

 

Comments

No comments so far.

Leave a Reply

 
(will not be published)
 
 
 
 

Preview: