Thin, unix sockets, Rails i Merb

Opublikowane przez Jarosław Zabiełło Mon, 03 Mar 2008 02:36:00 GMT

Thin to szybki serwer HTTP służący do uruchamiania webowych frameworków napisanych w języku Ruby. Najbardziej z nich popularny jest oczywiście Rails. Choć istnieje wiele sposobów uruchomienia Railsów na serwerze produkcyjnym, użycie Thin wydaje się aktualnie najlepszym rozwiązaniem z przynajmniej dwóch powodów: zajmuje mniej pamięci i jest szybszy od popularnego Mongrela. Thin wykorzystuje dwa bardzo szybkie moduły: Mongel parser, Event Machine oraz (wzorowaną na pythonowym WSGI) bibliotekę Rack.

Czytaj dalej...

Tagi , , , ,  | 8 comments

Nginx i problem z wydłużonym timeoutem dla FastCGI

Opublikowane przez Jarosław Zabiełło Mon, 30 Jul 2007 10:10:00 GMT

Domyślne timeouty Nginxa są dosyć krótkie (60 s.) i czasami trzeba je wydłużyć. Np. podczas aktualizacji dużego forum w PHP gdzie tego typu operację robi się zwykle z poziomu przeglądarki. Przeszukując stronę domową Nginxa i internet znalazłem tylko opis wydłużenia timeoutu dla połączeń proxy (proxy_read_timeout 15m;) Działa to dobrze w wypadku typowych połaczeń proxy, np. Mongrela i Railsów, ale nie działa dla PHP, który używa FastCGI i procesu spawn-fcgi będącego częścią serwera Lighttpd.

Czytaj dalej...

Tagi  | brak comments

Nginx - Apache killer

Opublikowane przez Jarosław Zabiełło Tue, 07 Nov 2006 23:47:00 GMT

W ostatnim artykule (Railsy: Lighttpd czy Apache 2.2.x?) porównywałem najbardziej popularne serwery HTTP dla Railsów. Zaintrygowany paroma wpisami w blogu postanowiłem przyjrzeć się dosyć mało znanemu serwerowi HTTP który zaczyna zdobywać coraz więcej uwagi na Zachodzie. Chodzi o ultraszybki serwer nginx napisany przez rosyjskiego programistę Igora Sysojewa.

Na pierwszy bój poszedł prosty test wyświetlenia “Hello World!” Na używanym przeze mnie serwerze dedykowanym (Athlon 64 3000+, 1GB RAM, Linux Ubuntu 64bit) dla 100 tys. zapytań (musiałem użyć aż tyle, bo serwer jest za szybki na mniejszą liczbę zapytań) uzyskałem następujące wyniki (dla polecenia “ab -n 100000 -c 1 http://localhost”):

  • Apache 2.2.3 = 4439 req/s
  • Lighttpd 1.4.11 = 7150 req/s
  • Nginx 0.4.12 = 8700 req/s

Co prawda Nginx wykazuje miażdzącą przewagę wydajności nad Apachem ale, z racji tego, że używam Lighttpd, który co prawda odstaje od Nginxa ale nie aż tak, postanowiłem na razie zaczekać z ewentualną migracją.

Okazało się jednak, że będę musiał przeprowadzić taką migrację szybciej niż bym chciał. Coś złego zaczęło się dziać z Ligthttpd. Po paru godzinach pracy, przestawał odpowiadać na zapytania a nawet w ogóle proces znikał z pamięci. Ki diabeł? Trudno powiedzieć, nie mam czasu aby analizować dokładniej problem. Jedyne co pomagało to regularny restart Lighttpd. Trochę głupie rozwiązanie. Postanowiłem zatem zrobić wcześniejszą migrację do Nginxa. Wg statystyk Netcraftu z Nginxa korzysta już ponad 90 tys. domen. Wydaje się to wystarczającą ilością aby można było uznać ten serwer za stabilny.

Jednakże moja migracja ma pewną trudność. Używam bowiem równocześnie PHP, Django, Rails i Zope (ściślej: Plone). Czyli całkiem niezła mieszanka aplikacji. Z PHP i Railsami było najmniej problemów, bo przykłady konfiguracji są z grubsza podane w Wiki.

Z Plone było troszkę gorzej. Musiałem bowiem znaleźć odpowiednik mniej więcej takiego kodu w Apache:

RewriteRule "^/(.*)$" 
"http://88.198.15.160:6001/VirtualHostBase/http/apologetyka.com:80/app/VirtualHostRoot/$1"  [P,L]

To nie jest zwykły rewrite, to jest połączenie proxy z rewrite.

W Lighttpd (też się swego czasu namęczyłem aby to uzyskać) uzyskuje się to tak:

url.rewrite-once = (
  "^/(.*)$" => "/VirtualHostBase/http/apologetyka.com:80/plone/VirtualHostRoot/$1"
)
proxy.server = (
   "/VirtualHostBase/" => (
     ( "host" => "88.198.15.160" , "port" => 6001 ) )
)
Trochę prób i się udało. Nginx potrzebował takiego wpisu:
location / {
  rewrite ^/(.*)$ /VirtualHostBase/http/apologetyka.com:80/plone/VirtualHostRoot/$1;
}
location /VirtualHostBase/ {
  include /opt/nginx/conf/proxy.conf;
  proxy_pass http://88.198.15.160:6001;
}

Najtrudniej było z konfiguracją Django bo opisu do Django na Nginx nie ma ani w dokumentacji do Django, ani w dokumentacji do NGinxa. Zmęczony eksperymentowaniem napisałem na listę dyskusyjną Django i dostałem całkiem pożyteczną wskazówkę odnośnie strony http://www.python.rk.edu.pl/w/p/django-pod-serwerem-nginx/. Niestety miałem pecha, bo akurat trafiłem na zmianę wpisów w DNS i artykuł był niedostępny. Udało mi się na szczęście wyłuskać jego kopię z cache Googli. Autor międzyczasie podesłał mi też pliki z artykułami. Zauważyłem że napotkał pewne problemy ze zmuszeniem Django do wyświetlania statycznej treści. Trochę podłubałem w kodzie i problem rozwiązałem. :)

Zauważyłem że Django wyświetlał mi pliki statyczne w trybie debug. Wynikało to pewnie z tego, że w urls.py stosuję zawsze taki wpis:

...
if DEBUG:
  urlpatterns += patterns('',
    (r'^images/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/images', 'show_indexes': True}),
    (r'^stylesheets/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/stylesheets', 'show_indexes': True}),
    (r'^javascripts/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/javascripts', 'show_indexes': True}),
    )

Dla trybu produkcyjnego (settings.DEBUG=False) trzeba zmusić serwer HTTP aby się zajmował plikami statycznymi. Django ma tylko przetwarzać Pythona. Mozna to zrobić np. tak:

...
location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|mov) {
  access_log   off; # po co mi logi obrazków :)
  expires      30d; 
}
location / {
  include /opt/nginx/conf/fastcgi.conf;
  fastcgi_pass 127.0.0.1:6002;
  fastcgi_pass_header Authorization;
  fastcgi_intercept_errors off;
}

Gdzie plik fastcgi.conf:

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx;

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

fastcgi_param  PATH_INFO          $fastcgi_script_name;

Plik startowy napisałem sobie już w Pythonie:

#!/usr/bin/env python

import os, sys, time
DEBUG = True
# All Django project are inside /home/app/django/

path = '/home/app/django'

projects = {
    'biblia' : {
        'project': 'searchers',
        'port':6002,
        'pidfile': '/var/run/django_searchers.pid',
        'children': 4,
        },
    'koran': {
        'project': 'django_project',
        'port':6003,
        'pidfile': '/var/run/django_koran.pid',
        'children': 4
        },
    }

def start(name):
    project = projects[name]['project']
    os.chdir('%s/%s/' % (path, project))
    appl = './manage.py runfcgi host=127.0.0.1'
    cmd = '%s port=%s minspare=1 maxspare=%s pidfile=%s --settings=%s.settings' \
          % (appl, projects[name]['port'], projects[name]['children'], projects[name]['pidfile'], project)
    if DEBUG: print cmd
    os.system(cmd)

def stop(name):
    pidfile = projects[name]['pidfile']
    if os.path.exists(pidfile):
        cmd = '/bin/kill -TERM %s' % open(pidfile).read()
        if DEBUG: print cmd
        os.system(cmd)
        os.unlink(pidfile)

def restart(name):
    stop(name)
    time.sleep(1)
    start(name)

if __name__ == '__main__':
    try:
        action, project = sys.argv[1], sys.argv[2]
        if action in ['start','stop', 'restart'] and project in projects:
            if action == 'start':
                start(project)
            elif action == 'stop':
                stop(project)
            elif action == 'restart':
                restart(project)
            else:
                raise IndexError
        else:
            raise IndexError
    except IndexError:
        print "Usage: %s {start|stop|restart} {%s}" % (sys.argv[0], '|'.join(projects.keys()))

Migracja się udała. Plone, PHP, Django i Rails śmigają mi teraz na ultraszybkim (i zajmującym mało pamięci!) serwerze Nginx. Acha, zapomniałem dodać: Nginx to nie tylko duża wydajność i oszczędność pamięci. Nginx ma dużo modułów. Może nie tyle, co Apache, ale znacznie lepiej niż Lighttpd.

BTW, ciekawie wygląda także serwer Cherokee. Nginx działa tylko pod systemami POSIX (Unix, MacOS-X, Linux, FreeBSD). Cherokee natomiast posiada… binarną instalację pod Windows! Ale o tym może napiszę coś innym razem. :)

Posted in ,  | Tagi , , , ,  | 16 comments