Nginx : tips pour booster le service
Bonjour à tous et Bonne Année à ceux qui ont raté la première salve !
Je vous parlais ici de comment aborder calmement NGinx, cet outil russe jouant serveurs web et reverse proxy.
Je vous propose donc cette fois-ci quelques idées qui pourront améliorer d'un peu les performances de votre service.
On constate la répétition inutile -mais ici, elle coûte pas en performances, c'est déjà ça- des fichiers à rechercher, par ordre de préférence.
Le copier-coller est facile, mais pas très élégant, surtout lorsque l'on sait que NGinx utilise le fichier de conf -donc les blocs et sous-blocs- comme une hiérarchie. D'autant plus que si l'on définie un nouveau serveur, mais qu'on oublie notre joli paste, on peut s'attendre à ce que NGinx botte en touche.
Pourquoi dans ce cas, ne pas profiter de l'héritage ?
Comme je le souligne plus haut, cet exemple n'illustre pas une perte de performance sur l'évaluation d'une instruction.
Ici, à chaque requête, on doit analyser le Host Header pour voir s'il colle à notre if regexpé. Autant dire qu'on fait travailler le serveur pour pas grand' chose...
A ce moment, et comme on opère tout de même un rewrite si le if est validé, tant qu'à faire, autant fractionner au plus près des besoins !
En mieux, on tombe sur un truc du genre :
Le premier bloc s'occupera donc d'opérer le rewrite pour toute requête s'adressant à www.domain.com, et uniquement celles-là, et les renverra vers domain.com. Une fois renvoyées, elles ne passeront plus systématiquement par l'évaluation du Host Header. Ca, c'est fait.
Même principe pour ceux qui poussent tout sur PHP. Plutôt que d'envoyer tout et absolument tout au proxy, autant segmenter pour coller au plus près de ce que l'on doit fournir à PHP, quitte à passer par un try_file.
En mieux :
Le try_file permet de tester d'abord la présence de la donnée en locale, et de la servir. Sinon, on passe par une location factice appelée @proxy qui s'appliquera à pousser tout ce petit monde à PHP.
Bref vous l'avez compris, la décomposition des éléments joue un rôle très appréciable ! Non seulement votre configuration est claire, mais en plus ça a l'avantage d'améliorer de manière drastique la façon dont sont servies les données et donc les performances de la bête.
Pour expliquer un peu :
NGinx intègre également un module memc, qui est une version étendue de memcached. Memc offre des options de gestion et de manipulation de commande Memcached.
La configuration est nettement moins sympa, mais reste claire tout de même :
Factorisation, segmentation
Avant de parler fonctionnalité, mettons l'accent sur les grands fondamentaux : un code clair et surtout bien organisé. Bien sûr, il ne s'agit pas de faire la leçon en terme de lisibilité ou autre perche tendue à un utilisateur inexpérimenté, mais bien de réorganisation d'instructions, afin que NGinx ne perde pas son temps à évaluer et réévaluer une expression qui n'a pas forcément à l'être -du moins, pas autant de fois.Factorisation
Un exemple est très frappant, qui est donné par la Communauté :http { index index.php index.htm index.html; server { server_name www.domain.com; location / { index index.php index.htm index.html; [...] } } server { server_name domain.com; location / { index index.php index.htm index.html; [...] } location /foo { index index.php; [...] } } }
http { index index.php index.htm index.html; server { server_name www.domain.com; location / { [...] } } server { server_name domain.com; location / { [...] } location /foo { [...] } } }
En revanche, prenons dans le cas inverse : que se passerait-il si justement on stipulait très tôt un traitement déclenché par une regex ?
Segmentation
N'oublions pas que NGinx fonctionne non pas sur la base de session mais bien de requête. Une regex placée un peu trop haut impliquerait son évaluation pour toutes les requêtes devant passer par le bloc, et donc impacterait plus significativement les performances. Il en va de même pour n'importe quel type de test, y compris le fameux If. Exemple :server { server_name domain.com *.domain.com; if ($host ~* ^www\.(.+)) { set $raw_domain $1; rewrite ^/(.*)$ $raw_domain/$1 permanent; [...] } }
server { server_name www.domain.com; rewrite ^ $scheme://domain.com$request_uri? permanent; } server { server_name domain.com; [...] }
server { server_name _; root /var/www/site; location / { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/tmp/phpcgi.socket; } }
server { server_name _; root /var/www/site; location / { try_files $uri $uri/ @proxy; } location @proxy { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/tmp/phpcgi.socket; } }
Compression
Cette fois-ci, penchons-nous non pas sur le temps de process d'une requête mais sur le goulot d'étranglement classique : la bande passante. En effet, la richesse et la forte sollicitation d'un site peut facilement peser sur un serveur, surtout si celui-ci est tout seul et donc ne dispose pas de moyen de gérer la charge. NGinx dispose d'un ensemble de modules qui prennent en charge la compression et la réutilisation des éléments déjà compressés qui seront poussés vers le client. Il s'agit notamment des modules suivants :- HttpGzipModule, module mettant à disposition une fonctionnalité de compression à la volée,
- HttpGzipStaticModule, le module qui permet de rechercher et d'utiliser une version compressée d'un élément, plutôt que de recompresser une donnée déjà traitée. Par contre, pour disposer de ce module, il faut compiler NGinx avec le mot magique : ./configure --with-http_gzip_static_module
gzip on; gzip_http_version 1.0; gzip_comp_level 2; gzip_vary on; gzip_proxied any; gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;D'ailleurs, on remarque avec un intérêt le paramètre gzip_proxied : il s'agit de compresser la réponse faite par le proxy.
Plusieurs paramétrages sont possibles, et en plus cela permet d'introduire une autre fonctionnalité permettant de soulager votre serveur : le cache !
Cache
Par location
Il arrive que certaines -beaucoup parfois- des données qui sont sollicitées sont ce qu'on appelle des données statiques : elles ne changent jamais. En jouant sur la gestion de ce type de source, on peut donc facilement améliorer les performances de notre service. D'abord en isolant bien proprement le bloc associé, afin de ne pas rentrer dans des moulinets gourmands alors que ces données n'ont besoin d'aucun traitement. Mais surtout il y a le cache. Si elles ne changent que très rarement, pas besoin d'aller systématiquement les chercher, autant les mettre en cache et forcer une expiration relativement raisonnable. Bon, 10 ans, c'est quand même un peu beaucoup...location ~ ^/(images|javascripts|stylesheets)/ { expires 10y; }
En configuration Reverse Proxy
Possible également d'utiliser le cache dans une configuration de reverse proxy, comme en témoigne cet exemple sorti du wiki :http { proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=STATIC:10m inactive=24h max_size=1g; server { location / { proxy_pass http://proxy.domain.com; proxy_set_header Host $host; proxy_cache STATIC; proxy_cache_valid 200 1d; proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; } } }
- le proxy_cache_path stipule les paramètres de stockage et d'utilisation du cache : chemin(/data/nginx/cache), niveau de sous-répertoire à mettre en cache(1:2), le nom et la taille de la zone (STATIC:10m), le temps durant lequel le cache est gardé et à partir duquel il est flushé (24h)...
- proxy_pass explicite le serveur qui va jouer le proxy. Sa définition est à votre convenance : IP, IP:port, FQDN, socket...
- proxy_cache reprend la zone définie par proxy_cache_path, quant à proxy_cache_valid, il paramètre la durée du cache en fonction des réponses retournées : ici 1 jour pour un code 200 renvoyé. Il est d'ailleurs possible d'avoir de multiples instructions proxy_cache_valid, pour chaque code retour intéressant, afin de pouvoir stipuler les durées selon les besoins.
- proxy_cache_use_stale enfin spécifie quand servir les caches plutôt que de requêter les éléments, mais aussi de faire en sorte (option updating) que si plusieurs requêtes demandent un update de l'élement mis en cache, seule une traitera cet update tandis que les autres continueront à se servir du cache, le temps que l'update soit fait.
server { location / { set $memcached_key $uri; memcached_pass name:11211; default_type text/html; error_page 404 @fallback; } location @fallback { proxy_pass backend; } }
# GET /bar?cmd=get&key=cat # GET /bar?cmd=set&key=dog&val=animal&flags=1234&exptime=2 # GET /bar?cmd=delete&key=dog # GET /bar?cmd=flush_all location /bar { set $memc_cmd $arg_cmd; set $memc_key $arg_key; set $memc_value $arg_val; set $memc_flags $arg_flags; # defaults to 0 set $memc_exptime $arg_exptime; # defaults to 0 memc_cmds_allowed get set add delete flush_all; memc_pass 127.0.0.1:11211; }Je vous renvoie à la page du wiki pour plus explicite.