Breaking Boundaries with FastCGI - Andrew Carter
@AndrewCarterUK

Breaking Boundaries with FastCGI

HTTP pipeline - as a restaurant

Client Customer
Server Restaurant
Request Order
Response Food
Application Chef
HTTP Daemon Waiter/Waitress

If PHP were a restaurant...

Customer enters restaurant
Waitress takes order
Waitress creates chef and gives order
Chef makes food
Waitress gives food to customer
Waitress brutally murders chef

The Proposal?



Don't kill the chef

Problem

What if our chef gets ill?

Solution #1


Kill the chefs before they have a chance to get ill

Solution #2


Use a better chef

What can cause our chef to get ill?

Memory Meltdown


Chef never forgets some of the meals they have made

Chef can only remember so much

Chef's head explodes


Remove references to unrequired objects

Timeouts


Chef forgets how to use oven after a couple of hours

Chef needs oven


ERROR 2006: MySQL server has gone away

Tantrum Handling


Chef encounters minor issue

Chef throws big tantrum


Distinguish between request errors and application errors

Let the application die if recovery is not possible

Evil Customer


Chef brainwashed by evil customer

Chef tricked into poisoning other customers


Think like an attacker

Avoid static and global memory

Chef Upgrade


Restaurant owner wants Chef with new recipes

Chef is pretty set in their ways

Restaurant owner forgets to kill old Chef

Chef keeps serving up old recipes


Remember to kill old Chef(s)

Leaving behind our restaurant...

CGI: Common Gateway Interface

CGI application must be executable by the web server

One instance of our application per request

HTTP request provided via environment variables

HTTP response written to standard output by application

Back in the day...

when I was 3

PHP was just a set of CGI binaries

We can still integrate PHP using CGI

We can also use native web server modules

We can also use PHP-FPM

PHP-FPM?

PHP FastCGI Process manager

FastCGI?

Like CGI... but faster

Making CGI Faster

Wrap our communication in a protocol

Implement this protocol over a socket connection

Keep our application alive between requests!

PHP-FPM

Keeps the PHP interpreter alive between requests using FastCGI


We are still killing our chef

Use FastCGI directly?

With a legacy application?


include 'lib/common.php';
include 'lib/database.php';

$escaped_url = mysql_real_escape_string($_SERVER['REQUEST_URI']);

$result = mysql_query(
    'SELECT html ' .
    'FROM pages ' .
    'WHERE url=\'' . $escaped_url . '\''
);

if (false === $result || !($page = mysql_fetch_assoc($result))) {
    header('HTTP/1.1 404 Not Found');
    $page = get_404_page();
}

echo $page['html'];
					

With Symfony?


use Symfony\Component\ClassLoader\ApcClassLoader;
use Symfony\Component\HttpFoundation\Request;

$loader = require_once __DIR__.'/../app/bootstrap.php.cache';

require_once __DIR__.'/../app/AppKernel.php';

$kernel = new AppKernel('prod', false);
$kernel->loadClassCache();

$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
                    

The FastCGI protocol

->FCGI_BEGIN_REQUEST
->FCGI_PARAMS
->FCGI_PARAMS
->...
->FCGI_STDIN
->FCGI_STDIN
->...
FCGI_STDOUT<-
FCGI_STDOUT<-
...<-
FCGI_END_REQUEST<-

Binary Protocol


                            pack() unpack()
                        

Or...

PHPFastCGI

Speedfony Bundle

composer require "phpfastcgi/speedfony-bundle:0.6.*"

// app/AppKernel.php

// ...
class AppKernel extends Kernel
{
  public function registerBundles()
  {
    $bundles = array(
      // ...
      new PHPFastCGI\SpeedfonyBundle\PHPFastCGISpeedfonyBundle(),
    );

    // ...
  }
// ...
                    

php app/console speedfony:run --env=prod
php app/console speedfony:run --env=prod --port=5000
php app/console speedfony:run --env=prod --port=5000 --host=localhost
php app/console speedfony:run --env=prod [--max-requests=200]
                    

How much faster is this?

Benchmarking Application

500 page Symfony application

Single route which selects a random page from database

Renders using Twig

Clears entity repository after each request

Benchmarking System

VMWare Fusion - 2GB RAM - 4 cores (Intel Core i7, 3.4 GHz)

Ubuntu 64-bit Server 15.04

PHP 5.6.4

NGINX

'ab', 50000 requests, concurrency level of 20

Control Test

OPcache enabled

PHP-FPM

First Test

6 worker processes

FastCGI protocol implemented in PHP userland

Second Test

6 worker processes

FastCGI protocol implemented by PHP extension

The Results


Results graph

Warnings

PHPFastCGI is not yet production ready

No support yet for uploaded files

The Symfony kernel slowly leaks memory over multiple requests

Do you need PHPFastCGI?

Is your application fast enough already?

Why bother with the risk?

PHPFastCGI is a tool for high performance PHP applications

However

Well designed applications should not leak memory

Well designed applications should handle errors properly

PHPFastCGI is very easy to install

To Conclude

Consider long running processes when developing components and services

FastCGI is designed to allow applications to stay alive between requests

PHP is not designed to allow applications to stay alive between requests

That is possibly why no one has made a serious effort to do this

Used carefully, this can break performance boundaries

Thank you for listening


Any questions?


@AndrewCarterUK


http://phpfastcgi.github.io
http://github.com/PHPFastCGI/SpeedfonyBundle