Comparatif caching : nginx/varnish/squid/apache

Par Faichelbaum @faichelbaum

Suite à la publication de mon précédent billet ( Créer un caching HTTP façon CDN), j'ai eu une petite discussion sur twitter avec Nicolas Hennion sur un comparatif des outils de mise en cache HTTP. Je me suis donc proposé de (re)faire un billet sur le sujet. Je m'attaque donc aux outils suivants : nginx, varnish, squid et apache (avec mod_cache/mod_proxy).

Avant propos

Le but n'étant pas de refaire une optimisation système et matérielle, je vais faire au plus simple pour le comparatif. Certains pourront considérer ma démarche simpliste mais bon, c'est la vie.

Architecture pour le comparatif

Ne voulant pas avoir trop de différences j'ai utilisé des machines virtuelles lancées sur des machines couillues. Une machine de créée en debian stable 64 bits, 2 vCPU pour 8 Go Ram et 1 Gbps en réseau, pour me faire un template et ensuite je l'ai clonée ainsi :

  • cache-apache : machine de cache en apache
  • cache-nginx : machine de cache en nginx
  • cache-squid : machine de cache en squid
  • cache-varnish : machine de cache en varnish
  • source-apache : machine qui distribue les fichiers de tests en http pour le serveur apache
  • source-nginx : machine qui distribue les fichiers de tests en http pour le serveur nginx
  • source-squid : machine qui distribue les fichiers de tests en http pour le serveur squid
  • source-varnish : machine qui distribue les fichiers de tests en http pour le serveur varnish
  • siege : la machine qui servira à générer les tests
  • temoin : machine qui distribue les fichiers de tests en http sans caching

Je mets un serveur "origin" (marque source-xxxxx) par serveur de cache afin qu'un test ne perturbe pas l'autre et ainsi pouvoir faire mes tests en parallèle.

Tous les serveurs seront sur le même réseau (pas de routage) afin de ne pas risquer d'avoir des ACL ou des problèmes de performance de routage entre elles.

Critères et méthodologie de comparaison

Les critères pris en compte sont les suivants

  • ergonomie pour un déploiement de masse
  • possibilité de configuration fine des vhosts/directory/...
  • optimisation de la consommation des ressources fournies
  • temps de réponses
  • débits
  • montée en charge

Afin de tester l'ensemble de ces critères, et parce que je n'ai pas envie de m'installer d'outils complémentaires (et potentiellement lourds) pour le report de la métrologie, je ferais un simple visuel sur le résultat de top sur chaque serveur ( cache-xxxx et source-xxxx) pendant le test.

Les tests se feront à coup d' ab et de siege sur des fichiers de 1 ko, 10 ko, 100 ko, 1 Mo, 10 Mo, 100 Mo et 1 Go. De plus, tous se feront sans keepalive d'activé.

Machine de siege

On lui installe le nécessaire :

# aptitude install siege apache2-utils

Afin de me simplifier la vie, les noms des machines sont écrites dans le fichier /etc/hosts.

Machine source et témoin

On installe le nécessaire. N'y cherchant pas la performance, on installe un basique apache.

# aptitude install apache2

Ni on le configure, ni on l'optimise.

On lui prépare ensuite les fichiers de tests dans /var/www :

# cd /var/www
# dd if=/dev/urandom of=1k bs=1k count=1
# dd if=/dev/urandom of=10k bs=1k count=10
# dd if=/dev/urandom of=100k bs=1k count=100
# dd if=/dev/urandom of=1m bs=1024k count=1
# dd if=/dev/urandom of=10m bs=1024k count=10
# dd if=/dev/urandom of=100m bs=1024k count=100
# dd if=/dev/urandom of=1g bs=1024k count=1000

On a donc bien nos fichiers de test :

# ls -lh
total 1.1G
-rw-r--r-- 1 root root 100K Sep 7 10:43 100k
-rw-r--r-- 1 root root 100M Sep 7 10:44 100m
-rw-r--r-- 1 root root 10K Sep 7 10:43 10k
-rw-r--r-- 1 root root 10M Sep 7 10:44 10m
-rw-r--r-- 1 root root 1000M Sep 7 10:48 1g
-rw-r--r-- 1 root root 1.0K Sep 7 10:43 1k
-rw-r--r-- 1 root root 1.0M Sep 7 10:44 1m
-rw-r--r-- 1 root root 177 Sep 7 10:41 index.html

Caching façon apache

On lui installe apache:

# aptitude install apache2

On jongle avec les modules utiles et non-utiles :

# a2dismod auth_basic authn_file authz_default authz_groupfile hostz_user autoindex cgid dir env reqtimeout status
# a2enmod  disk_cache proxy_http

On modifie les paramètres prefork dans /etc/apache2/apache2.conf :

<IfModule mpm_prefork_module>
 StartServers 16
 MinSpareServers 16
 MaxSpareServers 10
 MaxClients 250
 MaxRequestsPerChild 0
</IfModule>

On paramètre ensuite le proxy et le caching. Pour cela on se crée un vhost dédié /etc/apache2/sites-available/crash :

<VirtualHost *:80>
 NameServer crash
 CacheRoot /opt
 CacheMaxFileSize 1500000
 ProxyPass / http://crash/
 ProxyPassReverse / http://source-apache/
</VirtualHost>

On l'active :

# a2ensite crash
# /etc/init.d/apache2 restart

Il y a vraiment peu de paramètres que l'on peut configurer à ce niveau pour améliorer le fonctionnement. Une petite modification côté système est à faire dans /etc/security/limits.conf :

* - nofile 65535

Installer une usine à gaz pour une petite fonctionnalité qui est en plus ne dispose d'aucun paramétrage fin.

Caching façon nginx

On rajoute les mirroirs nginx :

deb http://nginx.org/packages/debian/ squeeze nginx
deb-src http://nginx.org/packages/debian/ squeeze nginx

Puis :

# wget http://nginx.org/packages/keys/nginx_signing.key -O - | apt-key add -
# aptitude update

On installe le nécessaire :

# aptitude install nginx

On configure le vhost en créant un fichier /etc/nginx/sites-available/crash :

server {
 listen 80;
 server_name crash;
 proxy_cache_key $scheme://$host$uri;
 location ~* / {
  proxy_hide_header "Vary";
  add_header "Vary" "Accept-Encoding";
  proxy_cache big;
  proxy_pass http://source-nginx;
 }
}

Puis en l'activant :

# ln -s /etc/nginx/sites-available/crash /etc/nginx/sites-enable/
# /etc/init.d/nginx restart

Ensuite on remplace le fichier /etc/nginx/nginx.conf par le suivant :

user www-data;
worker_processes 4; 
worker_rlimit_nofile 10000;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
timer_resolution 1ms;
events {
 worker_connections 10000;
 multi_accept on;
 use epoll;
 accept_mutex_delay 1ms;
}
http {
 include /etc/nginx/mime.types;
 client_body_temp_path /tmp 1 2;
 client_header_timeout 5s;
 client_body_timeout 5s;
 send_timeout 10m;
 connection_pool_size 128k;
 client_header_buffer_size 16k;
 large_client_header_buffers 1024 128k; 
 request_pool_size 128k; 
 keepalive_requests 1000;
 keepalive_timeout 10;
 client_max_body_size 10g;
 client_body_buffer_size 1m;
 client_body_in_single_buffer on;
 open_file_cache max=10000 inactive=300s; 
 reset_timedout_connection on;
 gzip on;
 gzip_static on;
 gzip_min_length 1100;
 gzip_buffers 16 8k;
 gzip_comp_level 9;
 gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
 gzip_vary on;
 gzip_proxied any;
 output_buffers 1000 128k;
 postpone_output 1460;
 sendfile on; 
 sendfile_max_chunk 256k;
 tcp_nopush on;
 tcp_nodelay on;
 server_tokens off;
 resolver 127.0.0.1;
 ignore_invalid_headers on;
 index index.html;
 add_header X-CDN "Served by myself";
 proxy_cache_path /opt/disk/ levels=1:2 keys_zone=big:10m max_size=2G;
 proxy_temp_path /opt/temp/ 1 2;
 proxy_cache_valid 404 10m;
 proxy_cache_valid 400 501 502 503 504 1m;
 proxy_cache_valid any 4320m;
 proxy_cache_use_stale updating invalid_header error timeout http_404 http_500 http_502 http_503 http_504;
 proxy_next_upstream error timeout invalid_header http_404 http_500 http_502 http_503 http_504;
 proxy_redirect off;
 proxy_set_header Host $http_host;
 proxy_set_header Server Apache;
 proxy_set_header Connection Close;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_pass_header Set-Cookie;
 proxy_pass_header User-Agent;
 proxy_set_header X-Accel-Buffering on;
 proxy_hide_header X-CDN;
 proxy_hide_header X-Server;
 proxy_intercept_errors off;
 proxy_ignore_client_abort on;
 proxy_connect_timeout 60;
 proxy_send_timeout 60;
 proxy_read_timeout 60;
 proxy_buffer_size 128k;
 proxy_buffers 16384 128k;
 proxy_busy_buffers_size 256k;
 proxy_temp_file_write_size 128k;
 proxy_cache_min_uses 0;
 include /etc/nginx/conf.d/*.conf;
 include /etc/nginx/sites-enabled/*;
}

Une petite modification côté système est à faire dans /etc/security/limits.conf :

* - nofile 65535

A mon sens, c'est celui qui offre le plus de possibilités dans le paramétrage fin et aussi dans l'évolutivité. Il ne lui manque que peu (SSI en tête de liste) pour être parfait.

Caching façon squid

On commence par installer l'applicatif :

# aptitude install squid

Vous remarquerez que l'installe une version de la branche 2.x et non 3.x. La raison est simple : squid3 est encore largement en retrait au niveau fonctionnalité (et stabilité) par rapport à la précédente branche, que l'ancienne évolue toujours et qu'on cherche à comparer des produits à mettre en prod.

On a besoin de changer les droits sur le dossier /opt :

# chmod 777 /opt

On paramètre alors le caching en éditant /etc/squid/squid.conf :

acl all src all
acl localnet src 192.168.0.0/16
acl Safe_ports port 80
acl CONNECT method CONNECT
http_access deny !Safe_ports
http_access allow localnet
http_access deny all
icp_access allow localnet
icp_access deny all
http_port 80 transparent
refresh_pattern . 0 20% 4320
acl apache rep_header Server ^Apache
broken_vary_encoding allow apache
extension_methods REPORT MERGE MKACTIVITY CHECKOUT
hosts_file /etc/hosts
coredump_dir /var/spool/squid
cache_peer source-squid parent 80 0 no-query originserver
cache_dir aufs /opt 2000 16 256
tcp_recv_bufsize 131072 bytes
maximum_object_size 1500000000 bytes

On n'oublie pas de modifier le fichier /etc/default/squid :

SQUID_MAXFD=10240

On pense encore à la petite modification côté système à faire dans etc/security/limits.conf en rajoutant :

* - nofile 65535

Squid est je pense celui qui a la configuration la moins claire. L'optimisation est très restreinte. Son avantage est l'implémentation complète de l'HTCP qui permet à l'architecture de caching de faire communiquer les noeuds entre eux, là où les autres demandes des petites astuces. Mono process, il ne profite pas des possibilités de la machine.

Caching façon varnish

On commence par l'installation :

# aptitude install varnish

On suit par la configuration en commencant par modifier les lignes suivantes dans /etc/default/varnish :

START=yes
DAEMON_OPTS="-a :80 \
 -f /etc/varnish/default.vcl \
 -S /etc/varnish/secret \
 -p thread_pools=4 \
 -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,2G"

On enchaîne avec la configuration de /etc/varnish/default.vcl :

backend default {
 .host = "source-varnish";
 .port = "80";
 .connect_timeout = 1s;
 .first_byte_timeout = 5s;
 .between_bytes_timeout = 2s;
}
sub vcl_recv {
 return(lookup);
}
sub vcl_fetch {
 return(deliver);
}

On rajoute la petite modification côté système dans etc/security/limits.conf :

* - nofile 65535

Varnish n'a pas une syntaxe super claire. De plus, le paramétrage fin s'arrête à définir les durées des objets, les latences et les actions à mener sur les HIT/MISS & co. Le seul intérêt que j'ai trouvé à varnish est l'implémentation complète des SSI (qui n'est que partielle sur nginx par ex).

Comparatif

siege

Siege fournit un test sur la durée et la qualité de réponse sur cette durée. Pour chaque fichier de test (remplacer XX par le nom du fichier), on lance les commandes suivantes :

siege -b -c 1 -t 1M http://crash/XX
siege -b -c 10 -t 1M http://crash/XX
siege -b -c 100 -t 1M http://crash/XX
siege -b -c 1000 -t 1M http://crash/XX 
siege -b -c 10000 -t 1M http://crash/XX

Siege étant très gourmand en mémoire, on est obligé de restreinte les tests sur la VM utilisée. Le tableau de résultat est disponible directement au format PDF.

Notez les points suivants :

  • les durées sont annoncées en secondes
  • les débits sont en Mo/s
  • les erreurs sont dûes soit à la partie cliente (siege) soit à la partie serveur et donc sont à mettre entre parenthèses

Siege présente des résultats assez incohérent selon la relance sur les valeurs mais aussi sur sa consommation locale de ressources. Je ne suis pas sûr de pouvoir l'utiliser pour ma conclusion (variation de plus de 10% à chaque reprise).

ab

ab fournit un test instantanné pour définir la qualité de réponse du serveur sur une charge pré définie. Pour chaque fichier de test (remplacer XX par le nom du fichier), on lance les commandes suivantes :

ab -n 1 -c 1 http://crash/XX
ab -n 10 -c 10 http://crash/XX
ab -n 100 -c 100 http://crash/XX
ab -n 1000 -c 1000 http://crash/XX

Le tableau de résultat est disponible directement au format PDF.

Notez les points suivants :

  • les durées sont annoncées en secondes
  • global est la durée globale du test
  • moyenne est le temps moyen rencontré
  • max est le temps maximum de chargement de la page
  • erreur est le nombre de retour non valide

Conclusion

varnish est une très bonne solution en soit mais il s'avère que le potentiel et les performance de nginx n'en ont pas fait l'un des serveurs HTTP de warez N°1 pour rien en son temps. Aujourd'hui, l'utiliser pour du caching a toutes ses raisons et bien plus encore.

    MAJ le 8 septembre 2012 à 16h20 : Modification de la mise en page des PDF et correction d'un lien