Introduction
This is a guide/howto on setting up
Drizzle database,
Nginx webserver,
PHP (CGI) and PHP-apc (bytecode cache) on
Debian Linux 6 (codename Squeeze).
NOTE: If the guide is too messy to read here, head over to this
Google Document for better formatting.
Why?
Apache and MySQL are quite heavy and MySQL has been becoming worse thanks to the idiots in Oracle. NGINX has been getting a lot of recommendations and it’s proving itself to be quite stable and faster than any existing webserver in the arena.
Drizzle DB is the child of the co-founder of MySQL after he left MySQL when Sun bought it. Its main focus is web applications, and easy replication of databases.
From preliminary tests on a non-optimized virtual machine, I was able to reach 800 requests/sec for read/write operations and 2400 requests for read-only operations, hitting my URL shortening web application on NGINX and Drizzle. My VM had 1 core and 512MB RAM, but only 69MB RAM was used during the tests!
Stress Test
Hardware: Lenovo laptop with an Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz, 6GB RAM, running Debian6 32-bit.
Software: VMware Workstation v7. The VM has 512MB RAM and 1 core allocated, running Debian6 32-bit (business card installation). VMware tools were not installed during the stress tests.
I had apc statistics enabled and access logging enabled in NGINX at first, but turning them off reduced load times from 126ms to 16ms for read/write operations and from 40ms to 4ms for read-only requests.
Read/write test: ab -c13 -n 10000 http://192.168.59.135/shorten.php?longurl=http://bit.ly
Requests per second: 789.07 [#/sec] (mean)
Time per request: 16.475 [ms] (mean)
Time per request: 1.267 [ms] (mean, across all concurrent requests)
CPU utilization: 25% Drizzle, 19% NGINX, 2.3% * 15 PHP CGI processes.
Read-only test: ab -c13 -n 10000 http://192.168.59.135/a
Requests per second: 2633.09 [#/sec] (mean)
Time per request: 4.937 [ms] (mean)
Time per request: 0.380 [ms] (mean, across all concurrent requests)
CPU utilization: 49% NGINX, 3.3% * 15 PHP CGI. Drizzle wasn’t showing in “top.”
The read-only test involved engaging a REWRITE rule from NGINX, which puts a tad bit more processing on its shoulders.
The numbers above are very specific to my application, but the numbers can be much better if I install VMware tools to provide VM optimizations, so don’t let the 49% CPU usage put you off.
0) Installation
0.0) Drizzle
Note: Drizzle relies on upstart, which means sysvinit will be removed.
First, add the PPA to /etc/apt/sources.lst using your favorite editor
deb http://ppa.launchpad.net/drizzle-developers/ppa/ubuntu maverick main
deb-src http://ppa.launchpad.net/drizzle-developers/ppa/ubuntu maverick main
Run:
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 06899068
sudo apt-get update
sudo apt-get install drizzle
(to continue, you’ll have to type ‘Yes, do as I say!’ excluding the single quotes)
0.1) NGINX
sudo apt-get install nginx
0.2) PHP
sudo apt-get install php5-cgi php5-mysql php-apc
The PHP extension for drizzle is available as a PECL but it’s not kept up to date & since we’re gonna compile either way, we’ll have to install some dev packages & a compiler. Those can (should) be removed after the compilation of the extension, if you’re doing this on a production box.
apt-get install php5-dev libdrizzle-dev make
This is to fix a bug in the configure script looking in the wrong place
ln -s /usr/include/libdrizzle-1.0/libdrizzle /usr/include/libdrizzle
Grab the latest stable Drizzle PHP extension from here:
https://launchpad.net/drizzle-php-ext
wget http://launchpad.net/drizzle-php-ext/trunk/0.5/+download/drizzle-php-ext-0.5.tar.gz
tar -zxf drizzle-php-ext-0.5.tar.gz
cd drizzle-php-ext-0.5
./configure
make -j2
make install
Let’s clean up after the mess
make clean
apt-get remove php5-dev libdrizzle-dev make
apt-get autoremove
rm -I /var/cache/apt/archives/*
In my case, the modules were copied to: /usr/lib/php5/20090626+lfs/ and the module drizzle.so is ready to be included in php.ini later.
Resources:
http://www.phptutorial.info/?apc.configuration
http://devzone.zend.com/article/4793
http://php.net/manual/en/install.pecl.phpize.php
http://chrisschuld.com/2007/07/missing-phpize/
1) Configuration
1.0) NGINX
The configuration of nginx is not a standard process. Each website may have its own specific setup & needs that one would have to tweak the settings around to fit one’s needs. Make sure you refer to the referenced links to see all available options and things to avoid doing.
I’ll include my own configuration changes here and not the full configuration file.
Modify the file /etc/nginx/nginx.conf
worker_processes 3;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
access_log off;
# if you want to have an access log, comment the line above and uncomment the following one
# access_log /var/log/nginx/access.log;
sendfile on;
tcp_nopush on;
}
Modify the file /etc/nginx/sites-enabled/default
index index.php index.html index.htm;
server {
# this block redirects all connections to www.domain.com to domain.com
# this is handy for cache configurations like Varnish & for statistics
# if you prefer to view your site as www.domain.com, swap server names
listen 80;
server_name www.domain.com;
rewrite ^ $scheme://domain.com$request_uri redirect;
}
server {
listen 80;
#listen [::]:80 default ipv6only=on;
server_name domain.com;
root /var/www/;
#access_log /var/log/nginx/localhost.access.log;
location / {
try_files $uri $uri/ /index.php;
}
location ~ \.(jpg|jpeg|gif|png|css|js|ico|xml)$ {
#I don’t want it to search for directories so I removed $uri/
try_files $uri /index.php;
#enable this if you enabled access logging previously
#access_log off;
expires 30d; #useful for caches
}
location ~ \.php$ {
try_files $uri /index.php;
include fastcgi_params;
# unix sockets are faster & better than binding to a port
fastcgi_pass unix:/tmp/php.socket;
}
# remember to include other settings from the original config file
}
Modify the file /etc/nginx/fastcgi_params
fastcgi_param SERVER_SOFTWARE nginx;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
Create a new file /etc/init.d/php-cgi
#!/bin/bash
BIND=/tmp/php.socket
USER=www-data
PHP_FCGI_CHILDREN=15
PHP_FCGI_MAX_REQUESTS=1000
PHP_CGI=/usr/bin/php-cgi
PHP_CGI_NAME=`basename $PHP_CGI`
PHP_CGI_ARGS="- USER=$USER PATH=/usr/bin PHP_FCGI_CHILDREN=$PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS=$PHP_FCGI_MAX_REQUESTS $PHP_CGI -b $BIND"
RETVAL=0
start() {
echo -n "Starting PHP FastCGI: "
start-stop-daemon --quiet --start --background --chuid "$USER" --exec /usr/bin/env -- $PHP_CGI_ARGS
RETVAL=$?
echo "$PHP_CGI_NAME."
}
stop() {
echo -n "Stopping PHP FastCGI: "
killall -q -w -u $USER $PHP_CGI
RETVAL=$?
echo "$PHP_CGI_NAME."
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
echo "Usage: php-fastcgi {start|stop|restart}"
exit 1
;;
esac
exit $RETVAL
Run the commands:
chmod +x /etc/init.d/php-cgi
update-rc.d php-cgi defaults
You are now able to start the php cgi service: service php-cgi start
References:
1.1) PHP
These are minor modifications and you should take an overall look at the config file to see if you’d like to make any other changes.
Modify the file /etc/php5/cgi/php.ini
; this prevents PHP from executing scripts uploaded by users into image directories
cgi.fix_pathinfo=0
; required for security reasons on CGI deployments
cgi.force_redirect=1
expose_php = Off
; original value is 128M which in my opinion is too much
memory_limit = 64M
; before the File Uploads section, add this
; apc settings
apc.shm_size = 32M
apc.stat = 0
; before the Module Settings section, add this
extension=drizzle.so
References:
http://devzone.zend.com/article/12618
http://www.phpjabbers.com/measuring-php-page-load-time-php17.html
1.2) Drizzle
Run:
mkdir -p /home/drizzle/db/.temporary
cp -Rv /var/lib/drizzle/* /home/drizzle/db/
chmod -R 750 /home/drizzle
chown -R drizzle:drizzle /home/drizzle
mv /etc/init/drizzle.conf /etc/init/drizzle.conf.orig
Make a new file: /etc/init/drizzle.conf
# Drizzle Service
description "Drizzle Server"
author "MBH "
start on runlevel [2345]
stop on runlevel [016]
expect fork
script
DRIZ_VARS="--user drizzle --datadir /home/drizzle/db --drizzle-protocol.port 3306"
start-stop-daemon --quiet --start --pidfile /home/drizzle/db/drizzle.pid --chuid drizzle --group drizzle --startas /usr/sbin/drizzled -- $DRIZ_VARS
end script
Notes:
- Drizzle doesn’t support unix sockets to make sure applications connect to 127.0.0.1 rather than localhost, if the DB is local.
- The drizzle username created in Debian doesn’t have a shell nor a home directory, and it should remain this way.