I've had the opertunity 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.
Please note: I'm 100% open to suggestions. If you see something I did that needs to be done a different way, please let me know. I haven't included my Perconca my.conf file yet. I will shortly. Also I plan on trying this same test with HHVM 3.3 and PHP 7.
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
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
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
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
wget http://www.percona.com/downloads/RPM-GPG-KEY-percona
sudo mv RPM-GPG-KEY-percona /etc/pki/rpm-gpg/
sudo yum install -y Percona-Server-client-56 Percona-Server-server-56 Percona-Server-devel-56
service mysql start
# then run
/usr/bin/mysql_secure_installation
# setup root password
# 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
yum --enablerepo=remi install -y nginx php55w-fpm php55w-common
# 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;
}
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
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;
}
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 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 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 $?
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
SCREENSHOT HERE
HHVM has an admin tool you can use to get stats - 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
}
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.
- parameters di
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
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
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.
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
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
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
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
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
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
I'm using Magento EE 1.14.0.1 for this test and here is how I have it configured:
<?xml version="1.0"?>
<config>
<global>
<install>
<date><![CDATA[Sat, 08 Nov 2014 22:17:08 +0000]]></date>
</install>
<crypt>
<key><![CDATA[my-secret-key]]></key>
</crypt>
<disable_local_modules>false</disable_local_modules>
<resources>
<db>
<table_prefix><![CDATA[]]></table_prefix>
</db>
<default_setup>
<connection>
<host><![CDATA[localhost]]></host>
<username><![CDATA[db-user-name-here]]></username>
<password><![CDATA[db-password-here]]></password>
<dbname><![CDATA[db-name-here]]></dbname>
<initStatements><![CDATA[SET NAMES utf8]]></initStatements>
<model><![CDATA[mysql4]]></model>
<type><![CDATA[pdo_mysql]]></type>
<pdoType><![CDATA[]]></pdoType>
<active>1</active>
</connection>
</default_setup>
</resources>
<session_save>db</session_save>
<redis_session>
<host>/var/run/redis_8302.sock</host>
<port>0</port>
<password></password>
<timeout>3.5</timeout>
<persistent></persistent>
<db>0</db>
<compression_threshold>2048</compression_threshold>
<compression_lib>gzip</compression_lib>
<log_level>1</log_level>
<max_concurrency>6</max_concurrency>
<break_after_frontend>5</break_after_frontend>
<break_after_adminhtml>30</break_after_adminhtml>
<first_lifetime>600</first_lifetime>
<bot_first_lifetime>60</bot_first_lifetime>
<bot_lifetime>7200</bot_lifetime>
<disable_locking>0</disable_locking>
</redis_session>
<cache>
<backend>Mage_Cache_Backend_Redis</backend>
<backend_options>
<server>/var/run/redis_8402.sock</server>
<port>0</port>
<persistent></persistent>
<database>0</database>
<password></password>
<force_standalone>0</force_standalone>
<connect_retries>1</connect_retries>
<read_timeout>30</read_timeout>
<automatic_cleaning_factor>0</automatic_cleaning_factor>
<compress_data>1</compress_data>
<compress_tags>1</compress_tags>
<compress_threshold>20480</compress_threshold>
<compression_lib>gzip</compression_lib>
</backend_options>
</cache>
<full_page_cache>
<backend>Mage_Cache_Backend_Redis</backend>
<backend_options>
<server>/var/run/redis_8502.sock</server>
<port>0</port>
<persistent></persistent>
<database>0</database>
<password></password>
<force_standalone>0</force_standalone>
<connect_retries>1</connect_retries>
<read_timeout>30</read_timeout>
<lifetimelimit>57600</lifetimelimit>
<compress_data>0</compress_data>
</backend_options>
</full_page_cache>
</global>
<admin>
<routers>
<adminhtml>
<args>
<frontName><![CDATA[admin]]></frontName>
</args>
</adminhtml>
</routers>
</admin>
</config>
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 paramters. I'm a rookie at JMeter so I'm sure I have lots to learn.
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:
I was asked by Andi Gutmans if I was going to do this with PHP 7. Here is the results using the same config. I would be happy to further tune my PHP configuration if needed but it looks like PHP 7 and HHVM 3.2 and really close! Great work by both teams.
For thoose interested here is how I built PHP 7 on RHEL 6.5:
# remove any existing PHP installations
sudo yum remove php*
Since the distro package of bison1 isn’t the correct version, you need to build it from source.
mkdir ~/tmp
cd ~/tmp
wget http://ftp.gnu.org/gnu/bison/bison-2.4.tar.gz
tar -xzf bison-2.4.tar.gz
cd bison-2.4
./configure
make && make install
cd ../
yum install -y bzip2-devel curl-devel libjpeg-devel libpng-devel libXpm-devel gmp-devel libc-client-devel freetype-devel t1lib-devel.x86_64 libmcrypt-devel.x86_64 recode-devel libxml2-devel mysql-devel aspell-devel
kdir ~/tmp
cd ~/tmp
git clone http://git.php.net/repository/php-src.git
cd php-src
git branch phpng origin/phpng
git checkout phpng
./buildconf
./configure \
--with-config-file-path=/etc \
--enable-mbstring \
--enable-zip \
--enable-bcmath \
--enable-pcntl \
--enable-ftp \
--enable-exif \
--enable-calendar \
--enable-sysvmsg \
--enable-sysvsem \
--enable-sysvshm \
--enable-fpm \
--enable-wddx \
--enable-soap \
--with-mcrypt \
--with-curl \
--with-iconv \
--with-gmp \
--with-pspell \
--with-gd \
--with-jpeg-dir=/usr \
--with-png-dir=/usr \
--with-zlib-dir=/usr \
--with-xpm-dir=/usr \
--with-freetype-dir=/usr \
--with-t1lib=/usr \
--enable-gd-native-ttf \
--enable-gd-jis-conv \
--with-openssl \
--with-libdir=lib64 --with-mysql \
--with-pdo-mysql \
--with-gettext=/usr \
--with-zlib=/usr \
--with-bz2=/usr \
--with-recode=/usr
php -v
# PHP 7.0.0-dev (cli) (built: Nov 11 2014 10:37:43)
# Copyright (c) 1997-2014 The PHP Group
# Zend Engine v2.8.0-dev, Copyright (c) 1998-2014 Zend Technologies
sudo cp ~/tmp/php-src/sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm
chmod 755 /etc/init.d/php-fpm
vi /etc/init.d/php-fpm
prefix=
exec_prefix=
php_fpm_BIN=/usr/local/sbin/php-fpm
php_fpm_CONF=/usr/local/etc/php-fpm.conf
php_fpm_PID=/var/run/php-fpm.pid
Make any edits you need to php-fpm.conf file. By default it listens on port 9000.
mv /usr/local/etc/php-fpm.conf.default /usr/local/etc/php-fpm.conf
Here is the settings I used in my /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
[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
[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"
[soap]
soap.wsdl_cache_enabled=1
soap.wsdl_cache_dir="/tmp"
soap.wsdl_cache_ttl=86400
zend_extension=opcache.so
opcache.enable_cli=1
opcache.save_comments=0
opcache.fast_shutdown=1
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.use_cwd=1
opcache.max_accelerated_files=100000
opcache.max_wasted_percentage=5
opcache.memory_consumption=128
opcache.consistency_checks=0
I will be upgrading to HHVM 3.3 and re-running the tests later in the week. I couldn't find a repo that contained HHVM 3.3 so I may build from source. Like I said if you find any inconsistancy I'm open to trying to tune further or make changes in my config.
Let's say you have an extension that is Ioncube encoded. I'm looking at your Unirgy :) You want it to work but it can't use HHVM or lets say you have an issue with an incompatiable SOAP call or PHP extension. You can get around the issues by tricking out your Nginx configuration to serve pages that are of certain URI on PHP instead of HHVM. Here is an example:
location ~ \.php$ { ## Execute PHP scripts
if (!-e $request_filename) { rewrite / /index.php last; }
set $use_hhvm 1;
if ($request_uri ~ ^/(index.php/admin/sales_order|index.php/urapidflowadmin)) {
fastcgi_pass 127.0.0.1:9000;
set $use_hhvm 0;
}
expires off; ## Do not cache dynamic content
if ($use_hhvm = 1) {
fastcgi_pass unix:/var/run/hhvm/sock;
}
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params; ## See /etc/nginx/fastcgi_params
fastcgi_keep_conn on; #hhvm param
}
As you can see I used a pipe "|" beween the URLS I want to use plain PHP.