Symfony on HHVM 3 and Nginx 1.4 vs PHP 5.5 and Apache 2.4

symfony_hhvm_nginx

Installing Symfony

From symfony.com/download we get the latest (2.4.4) version of Symfony. I have unpacked it and put it in the directory “/home/maurits/public_html”. In “app/AppKernel.php” I moved the “AcmeDemoBundle” to the production section and in “routing.yml” I added the “_acme_demo” route that was originally in “routing_dev.yml”.

Test environment

I tested on my i5-2300 CPU with 16GB of RAM and an SSD. To run a benchmark, I installed on my Ubuntu 14.04 both Apache 2.4 with PHP 5.5 and Nginx 1.4 with HHVM 3. I used Apache Bench (ab) to test the “/demo” path on both web servers. In Apache, I disabled XDebug and enabled Zend OPcache.

Install and configure HHVM 3 with Nginx 1.4 for Symfony

For HHVM, we find pre-built (64-bit) packages listed on Github. This is how you install them on Ubuntu 14.04:

wget -O - http://dl.hhvm.com/conf/hhvm.gpg.key | sudo apt-key add -
echo deb http://dl.hhvm.com/ubuntu trusty main | sudo tee /etc/apt/sources.list.d/hhvm.list
sudo apt-get update
sudo apt-get install hhvm

First we install Nginx from the Ubuntu 14.04 repository using:

sudo apt-get install nginx

Now we configure Nginx using the “normal” FastCGI configuration:

server {
    listen             8080;
    server_name        sf2testproject.dev;

    root /home/maurits/public_html/web;

    location / {
        # try to serve file directly, fallback to rewrite
        try_files $uri @rewriteapp;
    }

    location @rewriteapp {
        # rewrite all to app.php
        rewrite ^(.*)$ /app.php/$1 last;
    }

    location ~ ^/(app|app_dev|config)\.php(/|$) {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS off;
    }
}

Single request

When doing a single request and analyzing the response times using the Firebug “Net” panel there is no noticeable difference. This is probably because the threads do not have to compete for CPU. So let’s skip this and do some load testing.

Apache Bench results (Symfony 2.4 / Apache 2.4 / PHP 5.5)

maurits@nuc:~$ ab -c 10 -n 2000 http://sf2testproject.dev/demo/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking sf2testproject.dev (be patient)
Completed 200 requests
Completed 400 requests
Completed 600 requests
Completed 800 requests
Completed 1000 requests
Completed 1200 requests
Completed 1400 requests
Completed 1600 requests
Completed 1800 requests
Completed 2000 requests
Finished 2000 requests

Server Software:        Apache/2.4.7
Server Hostname:        sf2testproject.dev
Server Port:            80

Document Path:          /demo/
Document Length:        4658 bytes

Concurrency Level:      10
Time taken for tests:   9.784 seconds
Complete requests:      2000
Failed requests:        0
Total transferred:      9982000 bytes
HTML transferred:       9316000 bytes
Requests per second:    204.42 [#/sec] (mean)
Time per request:       48.918 [ms] (mean)
Time per request:       4.892 [ms] (mean, across all concurrent requests)
Transfer rate:          996.36 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       3
Processing:    18   49  10.9     50      90
Waiting:       15   41  10.1     41      78
Total:         18   49  10.9     50      91

Percentage of the requests served within a certain time (ms)
  50%     50
  66%     54
  75%     56
  80%     58
  90%     62
  95%     65
  98%     69
  99%     73
 100%     91 (longest request)

Apache Bench results (Symfony 2.4 / Ningx 1.4 / HHVM 3)

maurits@nuc:~$ ab -c 10 -n 2000 http://sf2testproject.dev:8080/demo/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking sf2testproject.dev (be patient)
Completed 200 requests
Completed 400 requests
Completed 600 requests
Completed 800 requests
Completed 1000 requests
Completed 1200 requests
Completed 1400 requests
Completed 1600 requests
Completed 1800 requests
Completed 2000 requests
Finished 2000 requests

Server Software:        nginx/1.4.6
Server Hostname:        sf2testproject.dev
Server Port:            8080

Document Path:          /demo/
Document Length:        4658 bytes

Concurrency Level:      10
Time taken for tests:   4.678 seconds
Complete requests:      2000
Failed requests:        0
Total transferred:      9900000 bytes
HTML transferred:       9316000 bytes
Requests per second:    427.50 [#/sec] (mean)
Time per request:       23.392 [ms] (mean)
Time per request:       2.339 [ms] (mean, across all concurrent requests)
Transfer rate:          2066.52 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:     8   23  11.5     21      84
Waiting:        8   22  11.3     20      84
Total:          8   23  11.5     21      84

Percentage of the requests served within a certain time (ms)
  50%     21
  66%     26
  75%     30
  80%     32
  90%     39
  95%     46
  98%     54
  99%     58
 100%     84 (longest request)

Conclusion

On both setups, I did not do any optimization. I just installed and ran the benchmark. It seems that Symfony 2.4 on HHVM is about twice as fast as on PHP 5.5. In a real life setup this means you need half the machines. This seem like a good cost reduction at first, but it may not be as good as it looks. I believe that in reality most Symfony applications are doing database and/or API calls. These will not be faster when using HHVM, since HHVM only speeds up PHP execution. This is why I think that the difference will be smaller (than a factor 2) for a real life Symfony application. What do you think?

Symfony2 Guzzle bundle for cURL API calling

The LswGuzzleBundle adds Guzzle API call functionality to your Symfony2 application. It is easy to use from the code and is aimed to provide full debugging capabilities. source: Github

On Packagist, you can see that in the past two years we have published 10 LeaseWeb Symfony2 bundles. The latest and tenth addition to the list is the LswGuzzleBundle. It is an alternative for the LswApiCallerBundle, so it also is a bundle that helps you to call API’s using cURL. The difference is that this bundle uses Guzzle as a wrapper for cURL.

Guzzle profiler and debugger

This bundle also adds a debug panel to your Symfony2, where you can find the cURL API calls Guzzle makes in your application. It provides profiling and extensive debugging capabilities to your Symfony2 application. We feel it is an essential bundle if you create an application that does not work with a database, but instead has an API as data store backend. Guzzle allows you to specify the parameters and and available calls in a Guzzle service description. Since this allows the bundle to understand the structure of the call, it will display the parsed parameters and output. This is an unique feature of this bundle.

guzzle_bundle

Open source

The bundle is MIT licensed. You can find it at:

As always there is a readme documentation on GitHub to get you started.

Symfony2 Memcache session locking

In one of the previous posts we wrote about session reliability. Today we will talk about “locking session data”. This is another session reliability topic and we will look at the problems that may occur in Symfony2 and how to solve them.

Session locking

Session locking is when the web server thread acquires an exclusive lock on the session data to avoid concurrent access. Browsers use HTTP 1.1 keep-alive and would normally just use one open TCP connection and reuse that to get all dynamic content. When loading images (and other static content) the browser may decide to use multiple TCP connections (concurrent) to get the data as fast as possible. This also happens when using AJAX. This may (and will most likely) lead to different workers (threads) on the web server answering these concurrent requests concurrently.

Each of the requests may read the session data update and write it back. The last write wins, so some writes may get lost. This can be countered by applying session locking. The session lock will prevent race conditions from occurring and prevent any corrupted data appearing in the session. This can easily be understood by looking at the following two images.

session-access-without-lockingsession-access-with-locking

The left image shows concurrent requests without session locking and the right shows concurrent requests with session locking. This is very well described in this post by Andy Bakun. Note that the above images are also from that post. Reading the Andy Bakun post allows you to truly understand the session locking problem (and the performance problems that AJAX may cause).

Symfony2 sessions

In Symfony2 one would normally use the NativeFileSessionHandler, which will just use the default PHP session handler. This works flawless in most cases. PHP uses “flock” to acquire an exclusive lock on the local filesystem. But when you scale out and run a server farm with multiple web servers you cannot use the local filesystem. You might be using a shared (NFS) filesystem and run into problems with “flock” (see the Linux NFS FAQs). If you use the database you may run into performance problems, escpecially when applying locking.

This leaves Memcache or Redis as options for session storage. These are fast key/value stores that can be used for session storage. Unfortunately the Symfony2 session storage implementations for Memcache (in Symfony) and Redis (in phpredis) do not implement session locking. This potentially leads to problems, especially when relying on AJAX calls as explained above. Note that other frameworks (like CakePHP) also do not implement session locking when using Memcache as session storage. Edit: This post has inspired the guys from SncRedisBundle and this Symfony2 bundle now supports session locking, which is totally awesome!

Custom save handlers

One can write “Custom Save Handlers” as described by the Symfony2 documentation:

Custom handlers are those which completely replace PHP’s built in session save handlers by providing six callback functions which PHP calls internally at various points in the session workflow. Symfony2 HttpFoundation provides some by default and these can easily serve as examples if you wish to write your own. — Symfony2 documentation

But you should be careful, since the examples do not implement session locking.

LswMemcacheBundle to the rescue

At LeaseWeb we love (to use) Memcache. Therefore, we have built session locking into our LswMemcacheBundle. It actually implements acquiring a “spin lock” with the timeout set to PHP’s “max_execution_time” (defaults to 30 seconds). The spin lock tries to acquire the lock every 150 ms (configurable). It will also hold the lock for a maximum time of the PHP “max_execution_time”. By using Memcache’s built-in key expire mechanism, we can ensure the lock is not held indefinitely.

This (spin-lock) implementation is a port of the session locking code from the memcached PECL module (written in C). Our bundle enables locking by default. If you want, you can disable the locking by setting the “locking” configuration parameter to “false” as described in the documentation.

This session locking code was also ported to SncRedisBundle and submitted as PR #109. LswMemcacheBundle is open-source and can be found on our GitHub account:

https://github.com/LeaseWeb/LswMemcacheBundle

7 bundles for highly effective software development

photo

At the Dutch Symfony Meetup on 4th of September (that was generously hosted by my employer LeaseWeb) I have been giving a talk about LeaseWeb’s open-sourced Symfony2 bundles. The slides are available!

1. Software consistency in large teams

Bundle: LswDefaultRoutingBundle – v1.0.1
Github URL: https://github.com/LeaseWeb/LswDefaultRoutingBundle
Description: Symfony2 bundle that provides default routing, relative routing and default templating.

Things like “conventions” and the compliance of those could be measurements for software quality. But having conventions means that you can actually choose not to follow them, which would lead to lower software quality, so why would one allow it? One of these cases is the Symfony routing system. The routes need to be individually specified in the routing configuration using annotations (special comment blocks) in the Controller code of the application or in an YML (or XML) file that resides either on application level or on bundle level.

Well, as you see this written down you might ask yourself: Why do you allow the programmer to have all these choices. Fellow programmers will answer: these are features of the routing system. It is awesome, because it supports all ways I might want to specify my routes. People who look at software quality and cost of maintenance may disagree: having to look at all these places to find a defined route in a software package might make adding a feature or doing a bug-fix more expensive. Also it is prone to typos to have to specify every route individually.

In Symfony1, one could choose to create a default route to avoid having to specify every route individually. In Symfony2 this option is no longer available and the DefaultRoutingBundle has exactly that mission: “bring back default routing functionality to Symfony2”. The aim is to do that in such a way it respects all features and tools the Symfony2 routing system has.

Apart from routing one can also argue that software development gets cheaper when bundles are reused in different software teams. Not only because development times are lower, but also because maintenance can be combined and it provides a way sharing lessons learned between teams.

2. Debugging API based applications

Bundle: LswApiCallerBundle – v1.0.1
Github URL: https://github.com/LeaseWeb/LswApiCallerBundle
Description: Symfony2 bundle that adds cURL API call functionality with full Web Debug Toolbar integration.

In modern web development, the use of web based API’s is omni-present. Connecting to these API’s is not hard at all, but debugging your Symfony application when these API’s fail can be a nightmare. This bundle provides insights  into: call parameters, call responses, time taken per call, total call time, number of calls and errors during calls. This information can be accessed from the Web Debug Toolbar of Symfony in the same way you can access this information for debugging SQL queries made on the database.

The API caller bundle understands different data formats (like XML, JSON and HTML), but can also display responses as plain text. To help developers create healthy (fast) applications the bundle shows the total amount of milliseconds spent to do API calls. It also shows the amount of API calls that are made from a single page. It will these show numbers in green, yellow and red to warn developers to not make too many API calls.

3. Caching for high traffic web applications

Bundle: LswMemcacheBundle – v1.1.0
Github URL: https://github.com/LeaseWeb/LswMemcacheBundle
Description: Symfony2 bundle for Memcache Doctrine caching and session storage in the Web Debug Toolbar.

When building a real high traffic web page you need Memcache. Memcache is a caching server that is very popular for high traffic websites. Especially when you have a web-farm it will become a necessity to use Memcache. You can use your database server(s) as cache server(s) by running Memcache on them.

This bundle provides debugging capabilities, multi-server support and a feature that is rarely used, but very valuable in extreme caching cases: Anti-Dog-Pile support. It also supports MemCached v2, so it is very future proof.

4. High performance and industry standard translations

Bundle: LswGettextTranslationBundle – v1.0.1
Github URL: https://github.com/LeaseWeb/LswGettextTranslationBundle
Description: Symfony2 bundle that adds native (faster) gettext translation support and is easy to use.

Translating agencies are used to work with gettext (worlds most popular translation engine for open source software). Adding translation support to any application is not only complex, it is also a lot of work. A large part of the complexity is in the work-flow. How do you handle updates to the software? How to extract untranslated texts and how to send them to the translators for all supported languages. What formats do they accept? How do you separate the software over different agencies and important: can you reuse part of the translations, like with bundles?

Then there is a performance problem. How expensive is it to render a multi-lingual page? Does it not slow down the application too much? All these questions are answered with this bundle. Since most people will run Linux web-servers with native gettext support we’ve chosen that to get maximum performance. And to optimize translation reuse the choice was made to use one translation file per bundle (per language). The bundle provides commands to extract language files from source-code and the tool “poedit” can be used to enter the missing translations.

5. Better testing support for staging environments

Bundle: LswVersionInformationBundle – v1.0.1
Github URL: https://github.com/LeaseWeb/LswVersionInformationBundle
Description: Symfony2 bundle that adds output of SVN and Git “status” commands to the Web Debug Toolbar.

One of the things you want to know when testing an application is what you version of the software you are testing. In our projects we have chosen Subversion (and recently Git) as our SCM to keep track of versions. Within these systems we use branches and tags to identify versions. But only the programmers who create those tags and the system administrators that deploy these versions know what version is running. This bundle has the mission to “make visible to the testers of the application what version of the software they are testing”. One of the more advanced things the bundle does is that is shows a list of dirty files (if any).

6. Consistent updates for high quality staging

Bundle: LswAutomaticUpdateBundle – v1.0.1
Github URL: https://github.com/LeaseWeb/LswAutomaticUpdateBundle
Description: Symfony2 bundle that enables automatic updates of the application from the Web Debug Toolbar.

This will show in the web debug toolbar which of your (composer) packages are not running stable versions. It also shows when the “composer update” was last run, so that you can see whether or not software maintenance was executed. It also provides (an experimental) feature to update your environment from the web debug toolbar.

7. Symfony1 to symfony2 migration

Bundle: LswRemoteTemplateBundle – v1.0.1
Github URL: https://github.com/LeaseWeb/LswRemoteTemplateBundle
Description: Symfony2 bundle that allows you to load your (view) template from a remote site.

When you want to migrate from Symfony1 to Symfony2 you should probably try to avoid a “big bang” migration. It would be too risky and too much work. Probably managers will not let you work on the migration that could take months, will not bring much new functionality and causes high risk for regression. This bundle allows you to load the template from your symfony1 application while actually sharing your session between both Symfony1 and Symfony2. This way you can gradually migrate from Symfony1 to Symfony2 one Controller or even one view at a time.

@Secure annotation for Symfony 2.3

We created the LswSecureControllerBundle to provide @Secure annotation in Symfony 2.3 to secure actions in controllers by specifying required roles. This functionality was provided by the JMSSecurityExtraBundle, included in Symfony 2.0, 2.1 and 2.2. In  Symfony 2.3 it was removed because of a license incompatibility between Symfony Core and the JMSSecurityExtraBundle. On the official blog Fabien Potencier (author of Symfony) writes:

As the Symfony Standard Edition is under the MIT license, we did not want to have anything else in its deps. That helps people developing GPL projects on top of Symfony. That said, if you are developing your own project, using dependencies under an Apache license should not be a problem at all. So, feel free to add them back. — Fabien Potencier in a response

So, the JMS bundles are no longer included by default and honestly, apart from the license, it is a huge pile of code. Especially as the only feature we use from it, is the @Secure annotation to require roles for executing specific actions in the controllers. Please note that it is not only a beefy bundle, but it also has an impressive set of dependencies:

And don’t get me wrong; I believe the JMSSecurityExtraBundle does a lot of other things that are very valuable and fully justifies its size and its dependencies. I also highly respect Johannes Schmitt for his efforts in the Symfony community and his coding skills. However, if you only need this one feature and want to stay lean (122 lines of code) or if you need a more permissive license, then our bundle is the one for you.

Installation and usage

secure_test
Figure 1: Symfony 2.3 denying access based on a @Secure annotation

Installation of this bundle is just like any other; details can be found in the README. We will show the usage by providing an example based on the standard Symfony 2.3 AcmeDemoBundle. We use the @Secure annotation in the AcmeDemoBundle to secure the “hello world” page requiring the role “ROLE_TEST” to execute.

In “src/Acme/DemoBundle/Controller/SecuredController.php” you should add the following line on top, but under the namespace definition:

use Lsw\SecureControllerBundle\Annotation\Secure;

To require the “ROLE_TEST” for “helloAction” in the “SecuredController” you should add @Secure annotation to the DocBlock of the “helloAction” like this (line 2):

    /**
     * @Secure(roles="ROLE_TEST")
     * @Route("/hello", defaults={"name"="World"}),
     * @Route("/hello/{name}", name="_demo_secured_hello")
     * @Template()
     */
    public function helloAction($name)
    {
        return array('name' => $name);
    }

Note that you can only use the @Secure annotation on actions that are protected by a firewall. You can configure the firewall in the “app/config/security.yml” file.

Credits

Creating the bundle would not have been possible without Matthias Noback’s excellent posts:

Thank you Matthias!

Support

We are committed to support this bundle and, as with all our bundles, you can contact us via the LswSecureControllerBundle Github page if you find a bug or want a feature to be added.