WordPress mit HHVM und PHP-FPM als Fallback

Man liest ja immer, dass HHVM im Vergleich zu PHP, Seiten um den Faktor 2-4x schneller ausliefern soll.
Nachdem HHVM nun als relativ stabil zu bezeichnen ist, inzwischen voll kompatibel zu PHP sein soll, auf einer meiner Webseiten schon PHP-FPM läuft, habe ich auf diesem Nginx-Vhost einfach mal HHVM eingerichtet um dieser These nachzugehen.
Da die Implementation von HHVM wirklich einfach ist,  wollte ich die Hürde ein klein wenig höher legen, und habe PHP-FPM als Fallback – falls HHVM mal abkachelt – einfach mit eingebaut.

1. Was ist HHVM eigentlich?

HHVM (HipHop Virtual Machine) ist in Hack und PHP geschrieben, um PHP-Applikationen wie z.B. WordPress ausführen zu können. HHVM benutzt dazu u.a. einen JIT-Compiler (Just In Time).
Auch wenn das Ganze von Facebook entwickelt wurde, ist HHVM Open Source.

HHVM portiert aufgerufenen PHP-Quelltext in einen speziellen Bytecode (Facebook nennt diesen „HHBC“). Dieser Bytecode wird mit Hilfe des JIT-Kompilers in X64-Maschinensprache übersetzt.
Aus diesem Mechanismus heraus, soll HHVM seinen Performance Vorteil beziehen.

Mehr Informationen kann der offiziellen HHVM Webseite, bzw. dem sehr informativen HHVM-Blog entnommen werden.

2. Installation und Konfiguration von HHVM

HHVM kann man als Ubuntu-Paket direkt vom HHVM-Projekt installieren, bzw. in die sources.list einbinden. Damit wird stets die aktuellste, stabile Version installiert:

sudo apt-get install software-properties-common

sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0x5a16e7281be7a449
sudo add-apt-repository "deb http://dl.hhvm.com/ubuntu $(lsb_release -sc) main"
sudo apt-get update
sudo apt-get install hhvm

Wie gewohnt landen die Konfigurationsdateien unter /etc/ im Ordner hhvm.
Per default läuft HHVM auf dem TCP Port 9000. Da ich HHVM auf meinem Server lokal anspreche, habe ich HHVM auf einen Unix-Socket „umgebogen“, zudem dieser im Vergleich zu einem TCP Port in der Regel ein wenig performanter ist.

So sieht meine /etc/hhvm/server.ini wie folgt aus:

; php options
pid = /var/run/hhvm/pid

; hhvm specific
hhvm.server.file_socket=/var/run/hhvm/hhvm.sock
hhvm.server.type = fastcgi
hhvm.server.default_document = index.php
hhvm.log.use_log_file = true
hhvm.log.file = /var/log/hhvm/error.log
hhvm.repo.central.path = /var/run/hhvm/hhvm.hhbc
hhvm.mysql.socket = /var/run/mysqld/mysqld.sock
hhvm.server.fix_path_info = false
hhvm.log.header = true
hhvm.log.natives_stack_trace = true

Das war es auch schon … und HHVM kann mittels „service hhvm start“ auf der Konsole gestartet werden.

3. Einbinden von HHVM in den Nginx-Vhost

Die Konfiguration / Einbindung von HHVM in einen Nginx-Vhost ist prinzipiell analog zu der Einbindung von PHP-FPM; also keine „rocket science“:

...
        location ~ .(hh|php)$ {
                fastcgi_intercept_errors on;

                try_files $uri =404;

                fastcgi_split_path_info ^(.+.php)(/.+)$;
                fastcgi_keep_conn on;
                include         fastcgi_params;
                fastcgi_index   index.php;
                fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param   SERVER_NAME $host;
                fastcgi_pass    unix:/var/run/hhvm/hhvm.sock;
                fastcgi_buffer_size 128k;
                fastcgi_buffers 256 16k;
                fastcgi_busy_buffers_size 256k;
                fastcgi_temp_file_write_size 256k;
                fastcgi_read_timeout 240;
        }
...

Die Nginx-Konfiguration kann also neu geladen werden, natürlich erst nach einem Test der neuen Konfiguration:

nginx -t && service nginx reload

Der Test, ob der konfigurierte VHost nun auch mit HHVM läuft ist einfach:

foo@something:/etc/nginx# curl -I https://www.365-tage-fotoprojekte.de
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
X-Powered-By: HHVM/3.10.1
Strict-Transport-Security: max-age=31536000
Front-End-Https: on
X-Frame-Options: SAMEORIGIN
Date: Wed, 02 Dec 2015 16:29:23 GMT
X-Page-Speed: 1.9.32.10-7423
Cache-Control: max-age=0, no-cache, no-store

4. PHP-FPM als HHVM Fallback

Auf vielen Seiten im Netz findet man die Aussage, dass sich HHVM ab und an ins Datenvirvana verabschiedet. Da ich keine Nginx-Fehlerseiten, sondern Content ausliefern möchte, habe ich PHP-FPM als Fallback für HHVM eingebaut. Die Nginx-Vhost Konfiguration wird dazu wie folgt ergänzt:

...
        location ~ .(hh|php)$ {
                fastcgi_intercept_errors on;
                error_page 502 = @fallback;

                try_files $uri =404;

                fastcgi_split_path_info ^(.+.php)(/.+)$;
                fastcgi_keep_conn on;
                include         fastcgi_params;
                fastcgi_index   index.php;
                fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param   SERVER_NAME $host;
                fastcgi_pass    unix:/var/run/hhvm/hhvm.sock;
                fastcgi_buffer_size 128k;
                fastcgi_buffers 256 16k;
                fastcgi_busy_buffers_size 256k;
                fastcgi_temp_file_write_size 256k;
                fastcgi_read_timeout 240;
        }

# PHP-FPM Fallback
        location @fallback {
                try_files $uri =404;

                fastcgi_split_path_info ^(.+.php)(/.+)$;

                include         fastcgi_params;
                fastcgi_index   index.php;
                fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param   SERVER_NAME $host;
                fastcgi_pass    unix:/var/run/php5-fpm.sock;
                fastcgi_buffer_size 128k;
                fastcgi_buffers 256 16k;
                fastcgi_busy_buffers_size 256k;
                fastcgi_temp_file_write_size 256k;
                fastcgi_read_timeout 240;
                fastcgi_intercept_errors on;

        }
...

Wie üblich wird die Nginx-Konfiguration neu geladen:

nginx -t && service nginx reload

Der Fallback-Test ist wieder mittels curl durchzuführen:

foo@something:/etc/nginx# service hhvm stop
foo@something:/etc/nginx# curl -I https://www.365-tage-fotoprojekte.de
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Pragma: no-cache
Strict-Transport-Security: max-age=31536000
Front-End-Https: on
X-Frame-Options: SAMEORIGIN
Date: Wed, 02 Dec 2015 16:38:06 GMT
X-Page-Speed: 1.9.32.10-7423
Cache-Control: max-age=0, no-cache, no-store, must-revalidate, post-check=0, pre-check=0

foo@something:/etc/nginx# service hhvm start
foo@something:/etc/nginx# curl -I https://www.365-tage-fotoprojekte.de
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
X-Powered-By: HHVM/3.10.1
Pragma: no-cache
Strict-Transport-Security: max-age=31536000
Front-End-Https: on
X-Frame-Options: SAMEORIGIN
Date: Wed, 02 Dec 2015 16:38:17 GMT
X-Page-Speed: 1.9.32.10-7423
Cache-Control: max-age=0, no-cache, no-store

5. Ist HHVM nun schneller als PHP-FPM?

Die ernüchternde Antwort ist: Nein.
Also zumindest nicht auf meinem Server 🙂

Mittels dem Apache-Benchmark-Tool (ab) habe ich eine „Hallo Welt“ PHP-Datei mehrfach mit und ohne HHVM / PHP-FPM penetriert.
Bei 25 gleichzeitigen Usern und 5000 Requesten (ab -k -c 25 -n 50000 https://www.365-tage-fotoprojekte.de/hallo.php) bringt es HHVM im Durchschnitt auf 241.27 Requests pro Sekunde, PHP-FPM (mit aktiviertem Opcache!) auf 221.25 Requests pro Sekunde. Der „Performance-Gewinn“ von HHVM liegt auf meinem Server also im Durchschnitt bei 20 Requests pro Sekunde. 
Meine Conclusio: HHVM ist schneller, aber nur geringfügig.
Trotzdem werde ich auf diesem Nginx-Vhost weiterhin auf HHVM und PHP-FPM als Fallback setzen, nicht weil die darunterlegende Webseite in einen Geschwindigkeitsrausch verfällt, nein … weil ich es kann 🙂

Interessant wird es, wenn die PHP Entwickler irgendwann eine stabile PHP7 Version releasen. Dann muss HHVM gegen PHP7 antreten.  Ich werde darüber berichten.