<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text">Web</title>
  <updated>2011-02-14T14:28:41+00:00</updated>
  <generator uri="http://framework.zend.com" version="1.10.6">Zend_Feed_Writer</generator>
  <link rel="self" type="application/atom+xml" href="http://tfountain.co.uk/blog/web/atom.xml"/>
  <id>http://tfountain.co.uk/</id>
  <entry xmlns:xhtml="http://www.w3.org/1999/xhtml">
    <title type="html"><![CDATA[Dojo: preventing form submit on Enter]]></title>
    <summary type="html"><![CDATA[If you've got some AJAX functionality inside a standard form (e.g. an address lookup feature where people type in their postcode and click a button) then there's a temptation for people to hit 'Enter']]></summary>
    <published>2011-02-14T14:28:41+00:00</published>
    <updated>2011-02-14T14:28:41+00:00</updated>
    <link rel="alternate" type="text/html" href="http://tfountain.co.uk/blog/2011/2/14/dojo-prevent-form-submit-enter"/>
    <id>http://tfountain.co.uk/blog/2011/2/14/dojo-prevent-form-submit-enter</id>
    <author>
      <name>Tim Fountain</name>
    </author>
    <content xmlns:xhtml="http://www.w3.org/1999/xhtml" type="xhtml">
      <xhtml:div xmlns:xhtml="http://www.w3.org/1999/xhtml"><xhtml:p>If you've got some AJAX functionality inside a standard form
(e.g. an address lookup feature where people type in their postcode
and click a button) then there's a temptation for people to hit
'Enter' to perform the search. By default this will submit the
whole form, which isn't what you want.</xhtml:p>
<xhtml:p>Catching the 'Enter' key press is fairly easy to do with some
inline Javascript, but it can easily be done unobtrusively with
Dojo as well:</xhtml:p>
<xhtml:pre class="brush:javascript">
dojo.addOnLoad(
        function() {
                dojo.connect(dojo.byId('postcode'), 'onkeydown', function(event){
                        if (event.keyCode == dojo.keys.ENTER) {
                                // CALL AJAX CODE HERE

                                dojo.stopEvent(event);
                        }
                });
        }
);
</xhtml:pre>
<xhtml:p>I found that 'onkeyup' as the event doesn't work here - the code
runs but doesn't prevent the submission, so stick to onkeydown.</xhtml:p>
</xhtml:div>
    </content>
  </entry>
  <entry xmlns:xhtml="http://www.w3.org/1999/xhtml">
    <title type="html"><![CDATA[Vanity URLs in Zend Framework]]></title>
    <summary type="html"><![CDATA[A question I've seen pop up in a few places is that of vanity URLs, and how to achieve them using frameworks. What I mean by a vanity URL is one where you have a URL structure such as http://example.c]]></summary>
    <published>2010-09-09T20:22:27+01:00</published>
    <updated>2010-09-09T20:22:27+01:00</updated>
    <link rel="alternate" type="text/html" href="http://tfountain.co.uk/blog/2010/9/9/vanity-urls-zend-framework"/>
    <id>http://tfountain.co.uk/blog/2010/9/9/vanity-urls-zend-framework</id>
    <author>
      <name>Tim Fountain</name>
    </author>
    <content xmlns:xhtml="http://www.w3.org/1999/xhtml" type="xhtml">
      <xhtml:div xmlns:xhtml="http://www.w3.org/1999/xhtml"><xhtml:p>A question I've seen pop up in a few places is that of vanity
URLs, and how to achieve them using frameworks. What I mean by a
vanity URL is one where you have a URL structure such as
<xhtml:tt>http://example.com/&lt;something&gt;</xhtml:tt>, where
<xhtml:em>something</xhtml:em> is some kind of user-generated string stored in
the database, perhaps to give users a more memorable profile URL.
Twitter is the obvious example of this, e.g. my twitter page is
<xhtml:a href="http://twitter.com/tfountain">http://twitter.com/tfountain</xhtml:a>.</xhtml:p>
<xhtml:p>How would you setup routing for this? Easy, you might think, you
just create a standard route containing a 'username' variable:</xhtml:p>
<xhtml:pre class="brush:php">
$route = new Zend_Controller_Router_Route(
    ':username',
    array(
        'controller' =&gt; 'users',
        'action' =&gt; 'profile'
    )
);
</xhtml:pre>
<xhtml:p>But what if you want to add <xhtml:tt>http://twitter.com/about</xhtml:tt>
and route it to a different place? Your router has no way of
knowing whether 'about' is a username or not, so you end up having
to add a load of static routes for your normal pages, leaving your
username route as the default one checked last.</xhtml:p>
<xhtml:p>What if you then want a top level vanity URL for another
feature? Say you introduced a groups feature, where groups can be
setup with URLs like <xhtml:tt>http://twitter.com/&lt;group
name&gt;</xhtml:tt>. You now have two identical URL structures which need
to route to different places.</xhtml:p>
<xhtml:p>This is something that can be quite difficult to do with a lot
of frameworks. Most follow the Rails-style approach where you
define a load of URL patterns in a routes file along with details
of where each should map to. This is easy to use and will cover 90%
of routing cases. But for the other 10% you're screwed - you're
left with a handful of messy options which either involve bypassing
the routing process or compromising on your desired URL
structure.</xhtml:p>
<xhtml:p>In my opinion the biggest strength of Zend Framework is its
flexibility, and the routing is a great example of this. You
<xhtml:em>can</xhtml:em> just load all your routes in from a file in ZF, but
you can also create route objects and assign them to the router
directly, or define your own route types and assign these, or
extend the router class with some customisations, or define your
own router; or some combination of these options. There's a
slightly bigger learning curve, but you're able to setup your
routing to match your application's needs, rather than having to
change your application to work within the limitations of the
framework.</xhtml:p>
<xhtml:p>In ZF a custom route class is the way to go to for vanity URLs.
Remember how I said the router has no way of knowing whether
'about' is a username or not? Well if you create a 'username' route
class that checks the database to see whether a particular string
is a user or not, then suddenly your router can handle these routes
like any other. You are also able to add additional route classes
for any other vanity URL structures you need.</xhtml:p>
<xhtml:p>A custom route class at its most basic just needs to implement
the Zend_Controller_Router_Route_Interface. This defines three
methods:</xhtml:p>
<xhtml:pre class="brush:php">
interface Zend_Controller_Router_Route_Interface {
    public function match($path);
    public function assemble($data = array(), $reset = false, $encode = false);
    public static function getInstance(Zend_Config $config);
}
</xhtml:pre>
<xhtml:p>the match function is where the action is - this takes the path
from the URL as a parameter, and should return an array of params
to be passed to the controller if the route matched, false if it
did not. The params array should also contain the module,
controller and action name that tell the router which part of your
application to route the request to.</xhtml:p>
<xhtml:p>I'd recommend extending the
Zend_Controller_Router_Route_Abstract class, as this is used by all
of the standard ZF route classes and will allow you to follow some
of their conventions.</xhtml:p>
<xhtml:p>Here's an basic example for a username route:</xhtml:p>
<xhtml:pre class="brush:php">
&lt;?php

class Application_Route_User extends Zend_Controller_Router_Route
{
    public static function getInstance(Zend_Config $config)
    {
        $defs = ($config-&gt;defaults instanceof Zend_Config) ? $config-&gt;defaults-&gt;toArray() : array();
        return new self($config-&gt;route, $defs);    
    }

    public function __construct($route, $defaults = array())
    {
        $this-&gt;_route = trim($route, $this-&gt;_urlDelimiter);
        $this-&gt;_defaults = (array)$defaults;
    }

    public function match($path, $partial = false)
    {
        if ($path instanceof Zend_Controller_Request_Http) {
            $path = $path-&gt;getPathInfo();
        }

        $path = trim($path, $this-&gt;_urlDelimiter);
        $pathBits = explode($this-&gt;_urlDelimiter, $path);

        if (count($pathBits) != 1) {
            return false;
        }

        // check database for this user
        $result = Zend_Registry::get('db')-&gt;fetchRow('SELECT userID, username FROM users WHERE username = ?', $pathBits[0]);
        if ($result) {
            // user found
            $values = $this-&gt;_defaults + $result;

            return $values;
        }

        return false;
    }

    public function assemble($data = array(), $reset = false, $encode = false)
    {
        return $data['username'];
    }
}
</xhtml:pre>
<xhtml:p>(This example assumes that there's a Zend_Db instance in the
registry under the key 'db'.)</xhtml:p>
<xhtml:p>All it's doing is extracting the string from the path and
looking this up in the database. If a match is found, it adds the
user ID and username to an array that already contains the module,
controller and action names and passes this back to the router.</xhtml:p>
<xhtml:p>To use this route you assign it to the router during your
bootstrap:</xhtml:p>
<xhtml:pre class="brush:php">
    protected function _initRoutes()
    {
        $router = Zend_Controller_Front::getInstance()-&gt;getRouter();

        // general page route
        $router-&gt;addRoute('about', new Zend_Controller_Router_Route(
            ':page',
            array(
                'module' =&gt; 'default',
                'controller' =&gt; 'pages',
                'action' =&gt; 'show'
            )
        ));

        // username route
        $router-&gt;addRoute('user', new Application_Route_User(
            'user',
            array(
                'module' =&gt; 'default',
                'controller' =&gt; 'users',
                'action' =&gt; 'show'
            )
        ));
    }
</xhtml:pre>
<xhtml:p>You'll see I've included a page route here as well, and note
that I can now use a more conventional :page variable route instead
of a static route, as there's no longer any ambiguity. This way you
don't have to modify your routes whenever you add a new page.</xhtml:p>
<xhtml:p>I've only scratched the surface of what you can do with ZF's
routing. I've used a similar approach (custom route class) for
hierarchical routes, where you have
/category/sub-category/sub-sub-category type structure, but that is
a topic for another blog post.</xhtml:p>
</xhtml:div>
    </content>
  </entry>
  <entry xmlns:xhtml="http://www.w3.org/1999/xhtml">
    <title type="html"><![CDATA[Speeding up phpMyAdmin]]></title>
    <summary type="html"><![CDATA[Here's a quick tip for speeding up phpMyAdmin when using it on a remote server. A big drain on rendering speed for the app seems to be the sheer number of theme related requests (images and stylesheet]]></summary>
    <published>2010-07-29T23:35:55+01:00</published>
    <updated>2010-07-29T23:35:55+01:00</updated>
    <link rel="alternate" type="text/html" href="http://tfountain.co.uk/blog/2010/7/29/speeding-up-phpmyadmin"/>
    <id>http://tfountain.co.uk/blog/2010/7/29/speeding-up-phpmyadmin</id>
    <author>
      <name>Tim Fountain</name>
    </author>
    <content xmlns:xhtml="http://www.w3.org/1999/xhtml" type="xhtml">
      <xhtml:div xmlns:xhtml="http://www.w3.org/1999/xhtml"><xhtml:p>Here's a quick tip for speeding up phpMyAdmin when using it on a
remote server. A big drain on rendering speed for the app seems to
be the sheer number of theme related requests (images and
stylesheets) the browser makes on every page load. An easy way
around this is to use the Apache module mod_expires to send an
expires header with these files, which tells the browser not to
bother requesting them again for a set period. This cuts down the
total requests by about 90%.</xhtml:p>
<xhtml:p>Firstly, make sure mod_expires is enabled:</xhtml:p>
<xhtml:pre>
sudo a2enmod expires
sudo apache2ctl restart
</xhtml:pre>
<xhtml:p>then open the phpMyAdmin Apache configuration file (by default
in located at /etc/apache2/conf.d/phpmyadmin.conf) in your text
editor of choice. You'll see an IfModule block which sets up some
php values:</xhtml:p>
<xhtml:pre>
&lt;Directory /usr/share/phpmyadmin&gt;
    Options FollowSymLinks
    DirectoryIndex index.php

    &lt;IfModule mod_php5.c&gt;
        AddType application/x-httpd-php .php

        php_flag magic_quotes_gpc Off
        php_flag track_vars On
        php_flag register_globals Off
        php_value include_path .
    &lt;/IfModule&gt;
&lt;/Directory&gt;
</xhtml:pre>
<xhtml:p>insert the following mod_expires block below the existing
&lt;/IfModule&gt;:</xhtml:p>
<xhtml:pre>
    &lt;IfModule mod_expires.c&gt;
        ExpiresActive On
        ExpiresByType image/gif "access plus 7 days"
        ExpiresByType image/jpg "access plus 7 days"
        ExpiresByType image/png "access plus 7 days"
        ExpiresByType text/css "access plus 7 days"
        ExpiresByType application/javascript "access plus 7 days"
    &lt;/IfModule&gt;
</xhtml:pre>
<xhtml:p>which should leave you with this:</xhtml:p>
<xhtml:pre>
&lt;Directory /usr/share/phpmyadmin&gt;
    Options FollowSymLinks
    DirectoryIndex index.php

    &lt;IfModule mod_php5.c&gt;
        AddType application/x-httpd-php .php

        php_flag magic_quotes_gpc Off
        php_flag track_vars On
        php_flag register_globals Off
        php_value include_path .
    &lt;/IfModule&gt;

    &lt;IfModule mod_expires.c&gt;
        ExpiresActive On
        ExpiresByType image/gif "access plus 7 days"
        ExpiresByType image/jpg "access plus 7 days"
        ExpiresByType image/png "access plus 7 days"
        ExpiresByType text/css "access plus 7 days"
        ExpiresByType application/javascript "access plus 7 days"
    &lt;/IfModule&gt;
&lt;/Directory&gt;
</xhtml:pre>
<xhtml:p>Restart Apache (sudo apache2ctl restart) so the changes take
effect.</xhtml:p>
<xhtml:p>What this does is tell Apache to send an expires header of 7
days into the future for all image, CSS and javascript files within
phpMyAdmin. The initial request after the restart will be as
before, but after that the browser knows that the files it got back
are good for 7 days, so on subsequent requests it will only request
the HTML page. You should see a noticeable speed improvement.</xhtml:p>
<xhtml:p>Normally when using mod_expires you would use a much longer
expire time than 7 days, but this way if a future phpMyAdmin update
does change these theme files you're less likely to get caught
out.</xhtml:p>
</xhtml:div>
    </content>
  </entry>
  <entry xmlns:xhtml="http://www.w3.org/1999/xhtml">
    <title type="html"><![CDATA[Deploying Zend Framework apps with Capistrano]]></title>
    <summary type="html"><![CDATA[One of the good things the Ruby community has brought us is Capistrano, a command line tool for automated deployment. Although it was written for Rails apps, it can be used with other languages, as pr]]></summary>
    <published>2009-05-11T21:02:28+01:00</published>
    <updated>2010-07-28T12:57:50+01:00</updated>
    <link rel="alternate" type="text/html" href="http://tfountain.co.uk/blog/2009/5/11/zend-framework-capistrano-deployment"/>
    <id>http://tfountain.co.uk/blog/2009/5/11/zend-framework-capistrano-deployment</id>
    <author>
      <name>Tim Fountain</name>
    </author>
    <content xmlns:xhtml="http://www.w3.org/1999/xhtml" type="xhtml">
      <xhtml:div xmlns:xhtml="http://www.w3.org/1999/xhtml"><xhtml:p>One of the good things the Ruby community has brought us is
<xhtml:a href="http://www.capify.org/">Capistrano</xhtml:a>, a command line
tool for automated deployment. Although it was written for Rails
apps, it can be used with other languages, as proven by a <xhtml:a href="http://www.simplisticcomplexity.com/2006/08/16/automated-php-deployment-with-capistrano/">
blog post</xhtml:a> from a few years ago detailing how to use it to
deploy PHP code. I thought I'd update this to show how it can be
used to deploy Zend Framework applications.</xhtml:p>
<xhtml:p>If you're not familiar with Capistrano, once it's all setup
you'll be able to run:</xhtml:p>
<xhtml:pre>
cap production deploy
</xhtml:pre>
<xhtml:p>from your ZF project folder. This will automatically login to
your remote server, checkout a copy of your code, run any custom
tasks (e.g. setting permissions), and then switch the live site to
point at the new release. If anything goes wrong with any of these
steps, the deployment will stop, the new checkout will be removed,
and your site will remain exactly as it is. This allows you to very
easily deploy code updates and new versions with basically no
downtime.</xhtml:p>
<xhtml:p>I'm going to assume you already have Capistrano installed (if
not, there are plenty of guides around for this). You'll also need
the <xhtml:tt>capistrano-ext</xhtml:tt> gem for the multistage stuff I'm going
to mention later. For reference, at the time of writing I have
versions capistrano 2.5.5 and capistrano-ext 1.2.1 of these
gems.</xhtml:p>
<xhtml:p>Capistrano has a command <xhtml:tt>capify</xhtml:tt> which creates the files
it requires, however this doesn't work with ZF applications because
the file structure is slightly different. So we're going to create
these files manually. If you're using the recommended ZF
application structure (i.e. you have an
<xhtml:tt>application/configs</xhtml:tt> folder), I've packaged up my examples
below into an <xhtml:a href="/files/blog/631.zip">archive</xhtml:a> you can
just extract into the root folder of your ZF project, but read on
anyway so you know what the files do.</xhtml:p>
<xhtml:p>The first of these is <xhtml:tt>Capfile</xhtml:tt>, which needs to live in
the root directory of your ZF project. This should look something
like this:</xhtml:p>
<xhtml:pre class="brush:ruby">
load 'deploy' if respond_to?(:namespace) # cap2 differentiator
load 'application/configs/deploy'
</xhtml:pre>
<xhtml:p>this file simply loads in your main deployment configuration
which in the above example will be
<xhtml:tt>application/configs/deploy.rb</xhtml:tt>. If you aren't using the
standard ZF app structure, adjust this path to point at wherever
you want your deployment configuration to live.</xhtml:p>
<xhtml:p>The next file is <xhtml:tt>deploy.rb</xhtml:tt>, which is where most of the
deployment settings live and is the file referenced above. Create
this file in your <xhtml:tt>application/configs</xhtml:tt> folder. Here's an
example:</xhtml:p>
<xhtml:pre class="brush:ruby">
# general settings
default_run_options[:pty] = true
set :use_sudo, false

# source control settings
set :scm, :git
set :deploy_via, :remote_cache
set :repository, "ssh://git@example.com/yourapp.git"

# stages
set :stages, %w(staging production)
set :stage_dir, "application/configs/deploy"
require 'capistrano/ext/multistage' 


namespace :deploy do

  task :migrate do
    # overrides the standard Rails database migrations task
  end

  task :start, :roles =&gt; :app do

  end

  task :stop, :roles =&gt; :app do

  end

  task :restart, :roles =&gt; :app do
    # no restart required for Apache/mod_php
  end 

end
</xhtml:pre>
<xhtml:p>Adjust the source control section to contain details for the
repository your application lives in. The example above shows
typical settings for a git repo (replace
git@example.com/yourapp.git with your clone URL). If you're using
Subversion, try something like this:</xhtml:p>
<xhtml:pre class="brush:ruby">
set :scm, :subversion
set :repository, "svn+ssh://example.com/yourapp/trunk"
set :scm_username, "your_svn_username"
set :scm_password, "your_svn_password"
set :deploy_via, :export
</xhtml:pre>
<xhtml:p>The stages section allows you to define a number of different
environments for your application. In my example I'm defining two
stages - production and staging. You then need a file for each
which contains the appropriate login details. <xhtml:tt>:stages_dir</xhtml:tt>
defines where these files live, in my example it's
<xhtml:tt>application/configs/deploy/</xhtml:tt>. These files need to be named
the same as your stages (with a .rb extension), e.g.
<xhtml:tt>production.rb</xhtml:tt>. Here's an example:</xhtml:p>
<xhtml:pre class="brush:ruby">
role :app, "example.com"
role :web, "example.com"
role :db, "example.com", :primary =&gt; true

set :deploy_to, "/path/to/your/app/"

set :user, "your_ssh_user"
set :password, "your_ssh_password"
</xhtml:pre>
<xhtml:p>Set <xhtml:tt>:app</xhtml:tt> to be the hostname of your remote server.
<xhtml:tt>:web</xhtml:tt> and <xhtml:tt>:db</xhtml:tt> will usually be the same, unless
your have separate dedicated servers for each of these and need to
run any custom tasks on them later. <xhtml:tt>:deploy_to</xhtml:tt> should be
the full path to the location on your remote server where you want
your application to live. Change <xhtml:tt>:user</xhtml:tt> and
<xhtml:tt>:password</xhtml:tt> to be the SSH login details for your remote
server.</xhtml:p>
<xhtml:p>To add a new stage, simply add it to the <xhtml:tt>:stages</xhtml:tt>
variable in <xhtml:tt>deploy.rb</xhtml:tt> (<xhtml:code>%w(staging production)</xhtml:code>
is the Ruby equivalent of <xhtml:code>array('staging',
'production')</xhtml:code>), and then create a file in
<xhtml:tt>application/configs/deploy/</xhtml:tt> with the appropriate login
details.</xhtml:p>
<xhtml:p>The <xhtml:tt>deploy.rb</xhtml:tt> example above also contains a section
that overrides the default Capistrano tasks <xhtml:tt>:migrate</xhtml:tt>,
<xhtml:tt>:start</xhtml:tt>, <xhtml:tt>:stop</xhtml:tt> and <xhtml:tt>:restart</xhtml:tt>. These are
pretty Rails-specific, so we override them because we don't need
them. However if you have any custom restart requirements you'd
define them here. E.g. if you're using APC with stat set to 0, you
might want to add some code to the :restart task to restart Apache
(this will run after each deployment).</xhtml:p>
<xhtml:p>Assuming you've got all that setup correctly, it's time to give
it a try. (Remember these commands will be performing actions on
your remote server!) First run:</xhtml:p>
<xhtml:pre>
cap production deploy:setup
</xhtml:pre>
<xhtml:p>this will create default Capistrano folders on your remote
server. After running it, login to your remote server and in your
<xhtml:tt>:deploy_to</xhtml:tt> location you should see two folders: releases,
and shared. Then (from your local machine again), run:</xhtml:p>
<xhtml:pre>
cap production deploy:cold
</xhtml:pre>
<xhtml:p>this will run the first full deployment of your application. If
you check your remote server again, you'll find a new timestamped
folder in the releases folder that contains a full copy of your
code. You'll also see a 'current' symlink at the
<xhtml:tt>:deploy_to</xhtml:tt> location that symlinks to the latest release.
So just set your vhost to point at (your path)current/public and
your site should be up and running!</xhtml:p>
<xhtml:p>Then in future, to deploy a new update you just have to run:</xhtml:p>
<xhtml:pre>
cap production deploy
</xhtml:pre>
<xhtml:p>this will checkout a new copy of the code, and assuming no
problems, switch the 'current' symlink to point at the new release.
And that's it! To perform the same thing on your staging setup (or
any other stages you add), simple replace 'production' with the
name of the stage in the commands above. E.g. cap <xhtml:tt>staging</xhtml:tt>
deploy.</xhtml:p>
<xhtml:p>Hopefully that should be enough to get you started. Run <xhtml:tt>cap
-T</xhtml:tt> from your ZF project folder to see what other commands you
can use, and the <xhtml:a href="http://wiki.capify.org/index.php/Frequently_Asked_Questions">Capistrano
FAQ</xhtml:a> is a good place to start if you need to customise
anything.</xhtml:p>
</xhtml:div>
    </content>
  </entry>
  <entry xmlns:xhtml="http://www.w3.org/1999/xhtml">
    <title type="html"><![CDATA[Optimizing server configuration for PHP applications]]></title>
    <summary type="html"><![CDATA[We recently migrated all of our websites to some new servers, and for the first time we have some dedicated servers for our PHP CMS platform. This gave me the opportunity to fine tune configuration sp]]></summary>
    <published>2009-04-23T18:16:38+01:00</published>
    <updated>2010-07-28T12:57:50+01:00</updated>
    <link rel="alternate" type="text/html" href="http://tfountain.co.uk/blog/2009/4/23/optimizing-server-configuration-php"/>
    <id>http://tfountain.co.uk/blog/2009/4/23/optimizing-server-configuration-php</id>
    <author>
      <name>Tim Fountain</name>
    </author>
    <content xmlns:xhtml="http://www.w3.org/1999/xhtml" type="xhtml">
      <xhtml:div xmlns:xhtml="http://www.w3.org/1999/xhtml"><xhtml:p>We recently migrated all of our websites to some new servers,
and for the first time we have some dedicated servers for our PHP
CMS platform. This gave me the opportunity to fine tune
configuration specifically for PHP, and I was surprised at how much
difference could be made without even touching the application
code.</xhtml:p>
<xhtml:p>The setup includes two web servers and a dedicated MySQL server,
all with 1G of RAM, running CentOS. I was using Apache bench for
the tests, with the parameters <xhtml:tt>ab -c 10 -n 500
http://www.example.com/</xhtml:tt> (10 concurrent requests, 500 in
total). The starting point was a standard yum-installed LAMP setup
except for the addition of the <xhtml:a href="http://blog.famillecollet.com/pages/Config-en">Remi</xhtml:a>
repositories for more recent PHP packages (PHP 5.2.9, MySQL 5.0.45,
Apache 2.2.3).</xhtml:p>
<xhtml:p>If you're not familiar with Apache bench, the 'requests per
second' stat is the thing to look out for in the results, higher is
better. Each test was run after 'warming up' the caches, and there
weren't any failed requests.</xhtml:p>
<xhtml:p>Firstly the baseline test (fresh after installation):</xhtml:p>
<xhtml:blockquote>
<xhtml:p>Requests per second: <xhtml:b>13.31</xhtml:b> [#/sec] (mean)<xhtml:br/>
Time per request: 751.477 [ms] (mean)<xhtml:br/>
Time per request: 75.148 [ms] (mean, across all concurrent
requests)<xhtml:br/>
Transfer rate: 43.27 [Kbytes/sec] received</xhtml:p>
</xhtml:blockquote>
<xhtml:p>Then after installing APC:</xhtml:p>
<xhtml:blockquote>
<xhtml:p>Requests per second: <xhtml:b>18.52</xhtml:b> [#/sec] (mean)<xhtml:br/>
Time per request: 539.833 [ms] (mean)<xhtml:br/>
Time per request: 53.983 [ms] (mean, across all concurrent
requests)<xhtml:br/>
Transfer rate: 60.24 [Kbytes/sec] received</xhtml:p>
</xhtml:blockquote>
<xhtml:p>Then started the tweaking. The Remi php-apc package has
<xhtml:tt>apc.shm_size</xhtml:tt> set to 32M by default (slightly higher than
the PHP default of 30M). As I understand it, this variable controls
how much RAM is reserved by APC. If it is set too low, APC won't be
able to cache all of your commonly included PHP files; but as you
increase it, once you pass the point at which your whole app can be
cached you'll start to see a reduction in performance, since you're
reducing the amount of memory that can be used by Apache. After
some experimentation I kept this at 32M.</xhtml:p>
<xhtml:p>Next I turned off <xhtml:tt>apc.stat</xhtml:tt>. With this option enabled
APC checks the last modified time of each included PHP script on
each request to see if it needs to be recompiled. With it off you
save quite a lot of file system calls, especially if you have a lot
of file includes, but it means you need to restart Apache after any
code changes. Results:</xhtml:p>
<xhtml:blockquote>
<xhtml:p>Requests per second: <xhtml:b>18.89</xhtml:b> [#/sec] (mean)<xhtml:br/>
Time per request: 529.346 [ms] (mean)<xhtml:br/>
Time per request: 52.935 [ms] (mean, across all concurrent
requests)<xhtml:br/>
Transfer rate: 61.43 [Kbytes/sec] received</xhtml:p>
</xhtml:blockquote>
<xhtml:p>Not much difference! The application code in this case is stored
on a shared NFS drive, so perhaps the local NFS client is caching
some of the file information.</xhtml:p>
<xhtml:p>Next I enabled <xhtml:tt>apc.include_once_override</xhtml:tt>, which gets
around the problem APC used to have caching files included using
include_once or require_once:</xhtml:p>
<xhtml:blockquote>
<xhtml:p>Requests per second: <xhtml:b>23.00</xhtml:b> [#/sec] (mean)<xhtml:br/>
Time per request: 434.698 [ms] (mean)<xhtml:br/>
Time per request: 43.470 [ms] (mean, across all concurrent
requests)<xhtml:br/>
Transfer rate: 74.81 [Kbytes/sec] received</xhtml:p>
</xhtml:blockquote>
<xhtml:p>If your application uses Zend Framework you'll probably see even
more benefit here, since ZF uses include_once extensively.</xhtml:p>
<xhtml:p>Next came the MySQL tweaking. Although MySQL comes with some
suggested configuration files for different setups, the defaults
are quite conservative. If you're not familiar with the different
MySQL config options, I'd suggest installing <xhtml:a href="http://wiki.mysqltuner.com/MySQLTuner">MySQLTuner</xhtml:a>, a free
script you can run which will analyse your installation and suggest
some changes.</xhtml:p>
<xhtml:p>By far the biggest improvements you can make are enabling the
query cache and increasing <xhtml:tt>key_buffer_size</xhtml:tt>. If you're
running a dedicated server for MySQL you can assign a decent chunk
of RAM to these two. The settings I used:</xhtml:p>
<xhtml:blockquote>
<xhtml:p>key_buffer_size=128M<xhtml:br/>
query_cache_size=256M<xhtml:br/>
query_cache_limit=16M<xhtml:br/>
thread_cache_size=4<xhtml:br/>
table_cache=128</xhtml:p>
</xhtml:blockquote>
<xhtml:p>Results:</xhtml:p>
<xhtml:blockquote>
<xhtml:p>Requests per second: <xhtml:b>61.60</xhtml:b> [#/sec] (mean)<xhtml:br/>
Time per request: 162.339 [ms] (mean)<xhtml:br/>
Time per request: 16.234 [ms] (mean, across all concurrent
requests)<xhtml:br/>
Transfer rate: 199.42 [Kbytes/sec] received</xhtml:p>
</xhtml:blockquote>
<xhtml:p>Big improvement!</xhtml:p>
<xhtml:p>Obviously there are a number of application-level things that
can be done to improve performance further (page/fragment caching,
caching objects in APC/memcache), but this shows how much
improvement you can gain just by tweaking some config files.</xhtml:p>
</xhtml:div>
    </content>
  </entry>
</feed>
