Kiedy Django zawodzi

Słoneczny dzień

Django jest świetnym rozwiązaniem należącym do kategorii full stack framework. Spójna budowa, setki aplikacji, rozwiązania generyczne (content types framework, comments framework) - batteries included. Świetne narzędzie do realizacji małych i średnich projektów, pozwala zaoszczędzić wiele czasu, nawet gdy istnieje potrzeba napisania od podstaw własnych rozszerzeń. Ostatnio jednak natrafiłem na mur.

Nadciągają cumulusy, czy rozumiesz co to znaczy?

Realizuję zupełnie nową funkcjonalność do portalu górskiego , która generuje sporo żądań XHR. Z rozpędu dodałem widoki w Django i podpiąłem je do szablonów. Rezultat powalił mnie... powolnością odpowiedzi. I to nie chodzi już o benchmarki, tylko o zwykłe odczucia użytkownika.

Szukając rozwiązania udałem się w kierunku Twisted i Tornado. Twisted jest dojrzałym frameworkiem sieciowym sterowanym zdarzeniami, a Tornado młodym web serverem napisanym specjalnie na potrzeby FriendFeed. Obydwa produkty cechuje wysoka wydajność, lecz z racji prostoty Tornado zająłem się nim na pierwszym miejscu. Niestety polubiłem go i mam nadzieję, że kiedyś wrócę do Twisted przy realizacji jakiegoś projektu. Do rzeczy.

Tornado

Tornado wieje z siłą urywającą nie łeb, ale setki głów. Niech poniższy kod (zacytowany ze strony Tornado) posłuży za prosty benchmark i przykład jednocześnie:

import tornado.httpserver
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

application = tornado.web.Application([
    (r"/", MainHandler),
])

if __name__ == "__main__":
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

Wyniki prostego pomiaru na mojej niespecjalnej maszynie:

$ ab -c 1000 -n 10000 http://127.0.0.1:8888/
>> 3208.29 [#/sec]

Zainteresowanych dokładniejszymi testami odsyłam do porównania serwerów asynchronicznych.

Oczywiście docelowo nie będzie tak różowo. Zapytania bazodanowe i kosztowne algorytmy osłabią ten wynik. Krótkie podsumowanie wydajności tej samej funkcji REST API:

  • Django/ORM: 2.47 rq/sec
  • Django/plain db queries (podsystem jako osobny pakiet): 66 rq/sec
  • Tornado/plain db queries (dokladnie to samo, co wyżej): 215 rq/sec (-c100 -n10000, 0 failed)
  • Tornado/plain db queries/in memory cache: 2183 rq/sec (-c100 -n10000, 0 failed)

Django server zapychał się już przy 400 requestach przy concurrency 10.

Jak przetrwać burzę

Jeśli projekt oparłeś w całości na Django, to skazany będziesz albo na refaktoryzację, albo na kombinatorykę pozwalającą importować moduły z aplikacji Django. Ja popełniłem ten błąd i umieściłem API oraz algorytmy w pakiecie zależnym od Django. Na szczęście był to dopiero prototyp i przeniosłem całość do odrębnego pakietu.

Prawidłowe rozwarstwienie podsystemu uczyni go łatwym do integracji. Dobrze jest wydzielić core do oddzielnego pakietu. Nie stosować w nim rozwiązań opierających się na elementach frameworków typu full-stack, szczególnie nie przywiązywać się do warstwy dostępu do danych. Zastosować wzorzec proxy, który ułatwi również opracowanie unit testów. Nie ulegać magii ani kucom na każdym kroku.

Krótko o mnie

Jestem programistą i architektem systemów IT. Specjalizuję się w aplikacjach intra- oraz internetowyh. Zajmuję się wsparciem istniejących systemów oraz projektowaniem i produkcją.

Zainteresowanych moimi usługami zapraszam do wysłania zapytania.

Javascript logo PostgreSQL logo Cassandra logo Redis logo ElasticSearch logo Ansible logo HTML5 logo CSS3 logo NGINX logo Docker logo

Komentarze

2010-11-02 22:35:59 slafs:

Witam! Wydaje mi się, że pisanie, iż Django jest wolne bez podawania konkretnej konfiguracji użytej do testów jest lekkim nadużyciem. Skąd takie wyniki ? Czy to na działającym serwerze developerskim czy na jakimś Apache'u z mod_wsgi ? Próbowałeś konfiguracji gunicorn (z kilkoma workerami) + nginx? Pozdrawiam

2010-11-15 21:24:00 marcin:

Dzięki za cenny hint. Przetestuję i uaktualnię wpis.