I’ve had the opportunity to try a variety of different server configurations but never really got around to trying HHVM with Magento until recently. I thought I would share a detailed walkthrough of configuring a single instance Magento server running Nginx + Fast CGI + HHVM / PHP-FPM + Redis + Percona. For the purpose of this blog post I’m assuming you are using Fedora, CentOS, or in my case RHEL 6.5.

NOTE: For an updated version visit: https://gist.github.com/tegansnyder/96d1be1dd65852d3e576

Install the EPEL, Webtatic, and REMI repos


rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
rpm -Uvh http://mirror.webtatic.com/yum/el6/latest.rpm

Install PHP 5.5.18


yum -y install php55w php55w-opcache php55w-devel php55w-mcrypt php55w-gd php55w-mbstring php55w-mysql php55w-pdo php55w-soap php55w-xmlrpc php55w-xml php55w-pdo php55w-mysqli libwebp

Install Percona
Note you may have existing mysql packages installed in your distro. If you do you will need to remove them prior to installing Percona. You can check by issuing:


rpm -qa | grep -i mysql

For instance on my server I needed to remove the following:


yum remove mysql
yum remove mysql-libs
yum remove compat-mysql51

Setup the Percona Repo
Open a VI editor to the following file.


vi /etc/yum.repos.d/Percona.repo

Add the following:


[percona]
name = CentOS $releasever - Percona
baseurl=http://repo.percona.com/centos/$releasever/os/$basearch/
enabled = 1
gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-percona
gpgcheck = 1

Grab the Percona GPG key


wget http://www.percona.com/downloads/RPM-GPG-KEY-percona
sudo mv RPM-GPG-KEY-percona /etc/pki/rpm-gpg/

Install Percona via Yum


sudo yum install -y Percona-Server-client-56 Percona-Server-server-56 Percona-Server-devel-56

Start Percona and Setup Root Pass


service mysql start
# then run
/usr/bin/mysql_secure_installation
# setup root password

Install HHVM


# needed to work around libstdc version issue
sudo yum upgrade --setopt=protected_multilib=false --skip-broken

# setup the hop5 repo
cd /etc/yum.repos.d
sudo wget http://www.hop5.in/yum/el6/hop5.repo

# show available versions of hvvm
yum list --showduplicates hhvm

# install latest verison show from list above
yum --nogpgcheck install -y hhvm-3.2.0-1.el6

Install Nginx and PHP-FPM


yum --enablerepo=remi install -y nginx php55w-fpm php55w-common

Configuring Nginx


# rename the default config as its not needed
sudo mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.old

# create a new config
vi /etc/nginx/conf.d/server.conf

server {
    server_name mydomainname.com www.mydomainname.com;
    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log info;

    # 504 is a PHP timeout and must be static
    # 502 is momentary during a PHP restart and should be treated like maintenance
    # other 50x errors are handled by Magento
    error_page 502 504 /var/www/mysite/504.html;

    listen 80;
    #listen 443 ssl;
 
    # if you are using a load balancer uncomment these lines
    # header from the hardware load balancers
    #real_ip_header X-Forwarded-For;
    # trust this header from anything inside the subnet
    #set_real_ip_from X.X.X.1/24;
    # the header is a comma-separated list; the left-most IP is the end user
    #real_ip_recursive on;

    # ensure zero calls are written to disk
    client_max_body_size          16m;
    client_body_buffer_size       2m;
    client_header_buffer_size     16k;
    large_client_header_buffers   8 8k;

    root /var/www/mysite;
    index index.php;

    fastcgi_read_timeout    90s;
    fastcgi_send_timeout    60s;
    
    # ensure zero calls are written to disk
    fastcgi_buffers 512 16k;
    fastcgi_buffer_size 512k;
    fastcgi_busy_buffers_size 512k;

    # remove the cache-busting timestamp
    location ~* (.+)\.(\d+)\.(js|css|png|jpg|jpeg|gif)$ {
        try_files $uri $1.$3;
        access_log off;
        log_not_found off;
        expires 21d;
        add_header Cache-Control "public";
    }

    # do not log static files; regexp should capture alternate cache-busting timestamps
    location ~* \.(jpg|jpeg|gif|css|png|js|ico|txt|swf|xml|svg|svgz|mp4|ogg|ogv)(\?[0-9]+)?$ {
        access_log off;
        log_not_found off;
        expires 21d;
        add_header Cache-Control "public";
    }

    # Server
    include main.conf;
    include security.conf;

}

Create a home for your website
If you don’t already have a place for your website files to live you will need to create one:


sudo mkdir -p /var/www/mysite/

# while you at it create a nice static error page
echo "error page" >> /var/www/mysite/504.html

Setup Nginx for HHVM and Magento
Nginx needs to be told how to work with PHP traffic and forward it via FastCGI to HHVM. Here is a good configuration. You will notice their is some standard rewrites for Magento assets in place.


vi /etc/nginx/main.conf

rewrite_log on;
 
location / {
  index index.php;
  try_files $uri $uri/ @handler;
}
 
location @handler  {
  rewrite / /index.php;
}
 
## force www in the URL
if ($host !~* ^www\.) {
  #rewrite / $scheme://www.$host$request_uri permanent;
}
 
## Forward paths like /js/index.php/x.js to relevant handler
location ~ \.php/ {
  rewrite ^(.*\.php)/ $1 last;
}
 
location /media/catalog/ {
  expires 1y;
  log_not_found off;
  access_log off;
}
 
location /skin/ {
  expires 1y;
}
 
location /js/ {
  access_log off;
}

location ~ \.php$ { ## Execute PHP scripts

  if (!-e $request_filename) { rewrite / /index.php last; } ## Catch 404s that try_files miss
  
  expires off; ## Do not cache dynamic content

  # for this tutorial we are going to use a unix socket
  # but if HHVM was running on another host we could forego unix socket
  # in favor of an IP address and port number as follows:
  #fastcgi_pass 127.0.0.1:8080;

  fastcgi_pass unix:/var/run/hhvm/sock;

  fastcgi_index index.php;
  #fastcgi_param HTTPS $fastcgi_https;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

  # if you need to explictly specify a store code for Magento do it here
  # this is useful if you are running multiple stores with different hostnames
  #fastcgi_param MAGE_RUN_CODE default;
  #fastcgi_param MAGE_RUN_TYPE store;

  include fastcgi_params; ## See /etc/nginx/fastcgi_params
 
  fastcgi_keep_conn on; #hhvm param
}

Next we need to setup our security configuration:


vi /etc/nginx/security.conf

## General Magento Security
location /app/ { deny all; }
location /includes/ { deny all; }
location /lib/ { deny all; }
location /media/downloadable/ { deny all; }
location /pkginfo/ { deny all; }
location /report/config.xml { deny all; }
location /var/ { deny all; }
 
## Disable .htaccess and other hidden files
location /\. {
  return 404;
}
 
## Disable all methods besides HEAD, GET and POST.
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
  return 444;
}

HHVM Configuration


vi /etc/hhvm/server.hdf

PidFile = /var/run/hhvm/pid

Server {
  Port = 8080
  SourceRoot = /var/www/mysite
  DefaultDocument = index.php
}

Log {
  Level = Warning
  AlwaysLogUnhandledExceptions = true
  RuntimeErrorReportingLevel = 8191
  UseLogFile = true
  UseSyslog = false
  File = /var/log/hhvm/error.log
  Access {
    * {
      File = /var/log/hhvm/access.log
      Format = %h %l %u % t \"%r\" %>s %b
    }
  }
}

Repo {
  Central {
    Path = /var/log/hhvm/.hhvm.hhbc
  }
}

#include "/usr/share/hhvm/hdf/static.mime-types.hdf"
StaticFile {
  FilesMatch {
    * {
      pattern = .*\.(dll|exe)
      headers {
        * = Content-Disposition: attachment
      }
    }
  }
  Extensions : StaticMimeTypes
}
MySQL {
  TypedResults = false
}

HHVM Fast-CGI support
HHVM will need to start with Fast-CGI support so Nginx can forward PHP request to it. We also need to edit the start up script to make HHVM use a unix socket. To do this edit the following file:


vi /etc/init.d/hhvm

I’ve only made a few changes to the start function start function to enable zend sorting per [Daniel Sloof](https://github.com/danslo) recommendation. I’ve also change the shutdown to kill the proper pid file (/var/run/hhvm/hhvm.pid). Here is the full init file:


#!/bin/bash
#
#	/etc/rc.d/init.d/hhvm
#
# Starts the hhvm daemon
#
# chkconfig: 345 26 74
# description: HHVM (aka the HipHop Virtual Machine) is an open-source virtual machine designed for executing programs written in Hack and PHP
# processname: hhvm

### BEGIN INIT INFO
# Provides: hhvm
# Required-Start: $local_fs
# Required-Stop: $local_fs
# Default-Start:  2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: start and stop hhvm
# Description: HHVM (aka the HipHop Virtual Machine) is an open-source virtual machine designed for executing programs written in Hack and PHP
### END INIT INFO

# Source function library.
. /etc/init.d/functions

start() {
	echo -n "Starting hhvm: "
        /usr/bin/hhvm --config /etc/hhvm/server.hdf --user apache --mode daemon -vServer.Type=fastcgi -vServer.FileSocket=/var/run/hhvm/sock -vEval.EnableZendSorting=1
	touch /var/lock/subsys/hhvm
}	

stop() {
	echo -n "Shutting down hhvm: "
	killproc -p /var/run/hhvm/pid
	rm -f /var/lock/subsys/hhvm
}

case "$1" in
    start)
	start
	;;
    stop)
	stop
	;;
    status)
    if [ ! -f /var/run/hhvm/pid ]; then
            echo "hhvm not is running"
    else
            echo "hhvm is running"
    fi
    ;;
    restart)
    	stop
	start
	;;
    reload|condrestart|probe)
	echo "$1 - Not supported."
	;;
    *)
	echo "Usage: hhvm {start|stop|status|reload|restart[|probe]"
	exit 1
	;;
esac
exit $?

Starting HHVM
As you can see if the init file for HHVM we started it with the user “apache”. So before starting HHVM make sure the directory your files are stored is owned by that group. Yes I know I’m being lazy and probably should create a new user and group running hhvm.


sudo chown apache:apache /var/www -R

We also need to give HHVM the permissions to:


mkdir -p /var/run/hhvm
chown apache:apache /var/run/hhvm
chmod 775 /var/run/hhvm

Finally we can start Nginx PHP-FPM and HHVM.


service nginx start
service php-fpm start
service hhvm start

The famous phpinfo() function will not work on HHVM but there is a very nice HHVM equivalent. Lets download it for fun:


cd /var/www/mysite/
wget https://gist.githubusercontent.com/ck-on/67ca91f0310a695ceb65/raw/hhvminfo.php

HHVM admin
HHVM has an admin tool you can use to get stats – [AdminServer](http://hhvm.com/blog/521/the-adminserver). If you want to see what is available you can create the following file:


vi /etc/nginx/conf.d/admin.conf

server {
    # hhvm admin
    listen 8889;

    location ~ {
        fastcgi_pass   127.0.0.1:8888;
        include        fastcgi_params;
    }
}

Then add this block to your hhvm configuration:


vi /etc/hhvm/config.hdf

AdminServer {
  Port = 8888
  Password = mySecretPassword
}

Some additional tuning

It is also recommended to use “pm = static” mode (instead of “pm = dynamic”) if you decide to
dedicate a server for PHP-FPM exclusively, as there is no need for dynamic allocation of resources to PHP-FPM. The “pm” part of the configuration is more or less the same as if you were to configure Apache.


vi /etc/php-fpm.d/www.conf

# make these changes
pm = static
pm.max_children = 48
pm.start_servers = 8
pm.min_spare_servers = 8
pm.max_spare_servers = 8
pm.max_requests = 40000
request_terminate_timeout = 120
catch_workers_output = yes
security.limit_extensions = .php .html .phtml

vi /etc/php.ini

[PHP]
engine = On
short_open_tag = On
asp_tags = Off
precision = 14
y2k_compliance = On
output_buffering = 4096
zlib.output_compression = Off
implicit_flush = Off
unserialize_callback_func =
serialize_precision = 100
allow_call_time_pass_reference = Off
safe_mode = Off
safe_mode_gid = Off
safe_mode_include_dir =
safe_mode_exec_dir =
safe_mode_allowed_env_vars = PHP_
safe_mode_protected_env_vars = LD_LIBRARY_PATH
disable_functions =
disable_classes =
expose_php = On
max_execution_time = 90
max_input_time = 120
memory_limit = 512M
max_input_vars = 25000
error_reporting = E_ALL & ~E_DEPRECATED
display_errors = Off
display_startup_errors = Off
log_errors = On
log_errors_max_len = 1024
ignore_repeated_errors = Off
ignore_repeated_source = Off
report_memleaks = On
track_errors = Off
html_errors = Off
variables_order = "GPCS"
request_order = "GP"
register_globals = Off
register_long_arrays = Off
register_argc_argv = Off
auto_globals_jit = On
post_max_size = 64M
magic_quotes_gpc = Off
magic_quotes_runtime = Off
magic_quotes_sybase = Off
auto_prepend_file =
auto_append_file =
default_mimetype = "text/html"
doc_root =
user_dir =
enable_dl = Off
file_uploads = On
upload_max_filesize = 64M
allow_url_fopen = On
allow_url_include = Off
default_socket_timeout = 90

realpath_cache_size = 128k
realpath_cache_ttl = 86400


[Pdo_mysql]
pdo_mysql.cache_size = 2000

[Syslog]
define_syslog_variables  = Off

[mail function]
SMTP = localhost
smtp_port = 25
sendmail_path = /usr/sbin/sendmail -t -i
mail.add_x_header = On

[SQL]
sql.safe_mode = Off

[ODBC]
odbc.allow_persistent = On
odbc.check_persistent = On
odbc.max_persistent = -1
odbc.max_links = -1
odbc.defaultlrl = 4096
odbc.defaultbinmode = 1

[MySQL]
mysql.allow_persistent = Off
mysql.max_persistent = -1
mysql.max_links = -1
mysql.default_port =
mysql.default_socket =
mysql.default_host =
mysql.default_user =
mysql.default_password =
mysql.connect_timeout = 60
mysql.trace_mode = Off

[MySQLi]
mysqli.max_links = -1
mysqli.default_port = 3306
mysqli.default_socket =
mysqli.default_host =
mysqli.default_user =
mysqli.default_pw =
mysqli.reconnect = Off

[PostgresSQL]
pgsql.allow_persistent = On
pgsql.auto_reset_persistent = Off
pgsql.max_persistent = -1
pgsql.max_links = -1
pgsql.ignore_notice = 0
pgsql.log_notice = 0

[Sybase-CT]
sybct.allow_persistent = On
sybct.max_persistent = -1
sybct.max_links = -1
sybct.min_server_severity = 10
sybct.min_client_severity = 10

[bcmath]
bcmath.scale = 0

[Session]
session.save_handler = files
session.save_path = "/var/lib/php/session"
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.cookie_httponly =
session.serialize_handler = php
session.gc_probability = 1
session.gc_divisor = 1000
session.gc_maxlifetime = 1440
session.bug_compat_42 = Off
session.bug_compat_warn = Off
session.referer_check =
session.entropy_length = 0
session.entropy_file =
session.cache_limiter = nocache
session.cache_expire = 180
session.use_trans_sid = 0
session.hash_function = 0
session.hash_bits_per_character = 5
url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry"

[MSSQL]
mssql.allow_persistent = On
mssql.max_persistent = -1
mssql.max_links = -1
mssql.min_error_severity = 10
mssql.min_message_severity = 10
mssql.compatability_mode = Off
mssql.secure_connection = Off

[Tidy]
tidy.clean_output = Off

[soap]
soap.wsdl_cache_enabled=1
soap.wsdl_cache_dir="/tmp"
soap.wsdl_cache_ttl=86400

Installing Redis
Since we are going to be using Redis for our store lets make sure to install it.


yum install -y gcc
wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
make install

# give Redis a home
mkdir -p /var/redis

Redis startup scripts
We are going to be running 3 Redis instances for Magento sessions, cache, and FPC. Each redis session is on a different port. To do this we need startup scripts. Here is my startup scripts. As you can see I’m using unix sockets and allocating 500mb for sessions, 1gb for cache, and 2gb for FPC.

Sessions on port 8302


vi /etc/redis/8302.conf

daemonize yes
pidfile /var/run/redis_8302.pid
port 8302
unixsocket /var/run/redis_8302.sock
unixsocketperm 777
timeout 0
tcp-keepalive 0
loglevel notice
logfile /var/log/redis_8302.log
databases 2
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression no
rdbchecksum yes
dbfilename dump.rdb
dir /var/redis/8302
slave-serve-stale-data yes
slave-read-only yes
repl-disable-tcp-nodelay no
slave-priority 100
maxmemory-policy volatile-lru
maxmemory 500mb
appendonly no
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes

Cache on port 8402


vi /etc/redis/8402.conf

daemonize yes
pidfile /var/run/redis_8402.pid
port 8402
unixsocket /var/run/redis_8402.sock
unixsocketperm 777
timeout 0
tcp-keepalive 0
loglevel notice
logfile /var/log/redis_8402.log
databases 2
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression no
rdbchecksum yes
dbfilename dump.rdb
dir /var/redis/8402
slave-serve-stale-data yes
slave-read-only yes
repl-disable-tcp-nodelay no
slave-priority 100
maxmemory-policy volatile-lru
maxmemory 1gb
appendonly no
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes

FPC Cache on port 8502


vi /etc/redis/8502.conf

daemonize yes
pidfile /var/run/redis_8502.pid
unixsocket /var/run/redis_8502.sock
unixsocketperm 777
port 8502
timeout 0
tcp-keepalive 0
loglevel notice
logfile /var/log/redis_8502.log
databases 2
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression no
rdbchecksum yes
dbfilename dump.rdb
dir /var/redis/8502
slave-serve-stale-data yes
slave-read-only yes
repl-disable-tcp-nodelay no
slave-priority 100
maxmemory-policy volatile-lru
maxmemory 2gb
appendonly no
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes

Redis Startup scripts
We need a way to start our servers. We can do this by creating startup scripts for it. Here are my 3 Redis startup scripts.


vi /etc/init.d/redis_8302

#!/bin/sh
#
# redis        Startup script for Redis Server
#
# chkconfig: - 90 10
# description: Redis is an open source, advanced key-value store.
#
# processname: redis-server

REDISPORT=8302
EXEC=/usr/local/bin/redis-server
CLIEXEC=/usr/local/bin/redis-cli

PIDFILE=/var/run/redis_8302.pid
CONF="/etc/redis/8302.conf"

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                PID=$(cat $PIDFILE)
                echo "Stopping ..."
                $CLIEXEC -p $REDISPORT shutdown
                while [ -x /proc/${PID} ]
                do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
    *)
        echo "Please use start or stop as first argument"
        ;;
esac

exit 0

vi /etc/init.d/redis_8402

#!/bin/sh
#
# redis        Startup script for Redis Server
#
# chkconfig: - 90 10
# description: Redis is an open source, advanced key-value store.
#
# processname: redis-server

REDISPORT=8402
EXEC=/usr/local/bin/redis-server
CLIEXEC=/usr/local/bin/redis-cli

PIDFILE=/var/run/redis_8402.pid
CONF="/etc/redis/8402.conf"

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                PID=$(cat $PIDFILE)
                echo "Stopping ..."
                $CLIEXEC -p $REDISPORT shutdown
                while [ -x /proc/${PID} ]
                do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
    *)
        echo "Please use start or stop as first argument"
        ;;
esac

exit 0

vi /etc/init.d/redis_8502

#!/bin/sh
#
# redis        Startup script for Redis Server
#
# chkconfig: - 90 10
# description: Redis is an open source, advanced key-value store.
#
# processname: redis-server

REDISPORT=8502
EXEC=/usr/local/bin/redis-server
CLIEXEC=/usr/local/bin/redis-cli

PIDFILE=/var/run/redis_8502.pid
CONF="/etc/redis/8502.conf"

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                PID=$(cat $PIDFILE)
                echo "Stopping ..."
                $CLIEXEC -p $REDISPORT shutdown
                while [ -x /proc/${PID} ]
                do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
    *)
        echo "Please use start or stop as first argument"
        ;;
esac

exit 0

Set the file permissions on the startup scripts:


cd /etc/init.d/
chmod 755 redis_*

mkdir -p /var/redis/8302
mkdir -p /var/redis/8402
mkdir -p /var/redis/8502
chmod 775 /var/redis/8302
chmod 775 /var/redis/8402
chmod 775 /var/redis/8502

Starting Redis servers


sh /etc/init.d/redis_8302 start
sh /etc/init.d/redis_8402 start
sh /etc/init.d/redis_8502 start

You can verify it is running by using the redis-cli tool:


redis-cli -p 8302
redis-cli -p 8402
redis-cli -p 8502

Apache JMeter Benchmarking
Magento has release a beta version of performance testing scripts that are available here. I followed the instructions in the accompanying PDF document, but had some troubles when I was trying to run the JMeter script on my local OSX machine. Magento doesn’t mention it in the documentation but you also need to add the JMeter plugins.

When you are ready to run the benchmark simply issue:


jmeter -n -t benchmark.jmx -Jhost=beepaux03.mmm.com -Jbase_path=/ -Jusers=100 -Jramp_period=300 -Jreport_save_path=./

Or you can use the GUI version of JMeter and get the fancy charts and graphs. You just need to enable the charts and set the parameters. I’m a rookie at JMeter so I’m sure I have lots to learn.

Here are the OSX instructions for those using homebrew:


brew install jmeter
wget http://jmeter-plugins.org/downloads/file/JMeterPlugins-Standard-1.2.0.zip
wget http://jmeter-plugins.org/downloads/file/JMeterPlugins-Extras-1.2.0.zip
unzip JMeterPlugins-Extras-1.2.0 
yes | cp -R JMeterPlugins-Extras-1.2.0/lib /usr/local/Cellar/jmeter/2.11/libexec/lib
yes | cp -R JMeterPlugins-Standard-1.2.0/lib /usr/local/Cellar/jmeter/2.11/libexec/lib

By default the distribution is as follows:

  • Browsing, adding items to a cart and abandoning the cart: 62%
  • Just browsing: 30%
  • Browsing, adding items to a cart and checking out as a guest: 4%
  • Browsing, adding items to a cart and checking out as a registered customer: 4%.

If your interested the inter-workings on the Magento JMeter script there is a detailed break down here.

Also to note:

The JMeter java configuration comes with 512 Mo and very little GC tuning. First ensure you set -Xmx option value to a reasonable value regarding your test requirements. Then change MaxNewSize option in jmeter file to respect the original ratio between MaxNewSize and -Xmx.


vi /usr/local/Cellar/jmeter/2.11/libexec/bin/jmeter
# change head param to increase memory
HEAP="-Xms1G -Xmx3G"

And now for the results you have been waiting for:

HHVM / Nginx / Redis / Percona



PHP 5.5.18 / Nginx / Redis / Percona (not using HHVM)



Charted together

In conclusion
I’m still testing HHVM before putting it in production. If you are using HHVM with Magento in production I would love to hear from you. Hit me up on twitter @tegansnyder.