30% conceptual
30% warnings
30% plugging a personal project
8% looking to the future
4% poor maths and misleading benchmarks
We might want to increase capacity
We might want to improve user experience
Performance requires efficiency
Minimise work performed to create a response
Client | Customer | |
---|---|---|
Server | Restaurant | |
Request | Order | |
Response | Food | |
Application | Chef | |
HTTP Daemon | Waiter/Waitress | |
Customer enters restaurant | ||
Waitress takes order | ||
Waitress creates chef and gives order | ||
Chef makes food | ||
Waitress gives food to customer | ||
Waitress brutally murders chef | ||
Don't kill the chef
Make application handle multiple requests
What if our chef gets sick?
What if our application encounters an issue that prevents it from responding to requests?
Kill the chef...
...occasionally?
Configure application to keep instances fresh
'N' requests or seconds before termination
Use a better chef
What new considerations are we introducing to our applications?
Who has programmed in a language where they had to manually manage memory?
In C...
char * join_strings(const char * string1, const char * string2)
{
char * new_string;
new_string = malloc(strlen(string1) + strlen(string2) + 1);
if (new_string == NULL) {
return NULL;
}
strcpy(new_string, string1);
strcat(new_string, string2);
return new_string;
}
char * str;
str = join_strings("Hello, ", name);
// do stuff with str
free(str);
In PHP...
$str = 'Hello, ' . $name;
// do stuff with $str
// now what?
How do we keep our application memory clean in PHP?
function some_routine($name) {
$str = 'Hello, ' . $name;
// do stuff with $str
}
// no more $str
$str = 'Hello, ' . $name;
// do stuff with $str
unset($str);
Remove references to unrequired objects
What might hide references?
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('Bundle:Page');
$page = $repository->find($id);
$response = $this->render(
'Bundle:Default:page.html.twig',
['page' => $page]
);
$em->detach($page);
return $response;
$log = new Logger('app');
$handler = new ErrorLogHandler();
$fingersCrossedHandler = new FingersCrossedHandler($handler);
$logger->pushHandler($fingersCrossedHandler);
$logger->notice('Hello');
$logger->notice('It\'s me');
$logger->notice('I was wondering if...');
// $logger->alert('Something went wrong');
ERROR 2006: MySQL server has gone away
Can be hard to detect and manage whilst testing
$em = $this->getDoctrine()->getManager();
$connection = $em->getConnection();
if ($connection->ping() === false) {
$connection->close();
$connection->connect();
}
$repository = $em->getRepository('Bundle:Page');
$page = $repository->find($id);
Distinguish between request errors and application errors
4XX - Request Error
5XX - Application Error
Let the application die if recovery is not possible
Application no longer being re-read from disk between requests
Must reload worker threads to update our applications
Separate request and application scope
Services should be application scope
If you need the request stack you're doing it wrong
Do not use static or global variables
Only use static methods when stateless
(Or just don't use static methods)
Moving on...
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 FastCGI Process manager
Like CGI... but faster
Wrap our communication in a protocol
Implement this protocol over a socket connection
Keep our application alive between requests!
Keeps the PHP interpreter alive between requests using FastCGI
We are still killing our chef
Use FastCGI directly?
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'];
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);
-> | FCGI_BEGIN_REQUEST | |
-> | FCGI_PARAMS | |
-> | FCGI_PARAMS | |
-> | ... | |
-> | FCGI_STDIN | |
-> | FCGI_STDIN | |
-> | ... | |
FCGI_STDOUT | <- | |
FCGI_STDOUT | <- | |
... | <- | |
FCGI_END_REQUEST | <- |
pack() unpack()
Or...
A tool for creating command line FastCGI applications
use PHPFastCGI\FastCGIDaemon\ApplicationFactory;
use PHPFastCGI\FastCGIDaemon\Http\RequestInterface;
use Zend\Diactoros\Response\HtmlResponse;
$kernel = function (RequestInterface $request) {
// $request->getServerRequest() PSR-7 object
// $request->getHttpFoundationRequest() HTTP foundation object
return new HtmlResponse('Hello, World!
');
};
$application = (new ApplicationFactory)->createApplication($kernel);
$application->run();
php bin/fastcgi.php run
php bin/fastcgi.php run --port=5000
php bin/fastcgi.php run --port=5000 --host=localhost
php bin/fastcgi.php run [--request-limit=200]
php bin/fastcgi.php run [--memory-limit=50000000]
php bin/fastcgi.php run [--time-limit=3600]
upstream workers {
server localhost:5000;
server localhost:5001;
server localhost:5002;
server localhost:5003;
}
server {
# ...
location / {
include /etc/nginx/fastcgi_params;
fastcgi_pass workers;
# ...
}
}
composer require "phpfastcgi/speedfony-bundle:^0.8"
// 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 [--request-limit=200]
php app/console speedfony:run --end=prod [--memory-limit=50000000]
php app/console speedfony:run --env=prod [--time-limit=3600]
How much faster is this?
500 page Symfony application
Single route which selects a random page from database
Renders using Twig
Clears entity repository after each request
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
OPcache enabled
PHP-FPM
6 worker processes
FastCGI protocol implemented in PHP userland
6 worker processes
FastCGI protocol implemented by PHP extension
PHPFastCGI has limited use in production
No support yet for uploaded files
Is your application fast enough already?
Why bother with the risk?
PHPFastCGI is a tool for high performance PHP applications
Well designed applications should not leak memory
Well designed applications should handle errors properly
PHPFastCGI is very easy to install
React PHP + Icicle
Applications run on an event loop
Controller actions return a response promise
github.com/async-interop
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 few make the effort to do this
Used carefully, this can break performance boundaries
Any questions?
https://joind.in/talk/c5434
@AndrewCarterUK
http://phpfastcgi.github.io
http://github.com/PHPFastCGI/FastCGIDaemon
http://github.com/PHPFastCGI/SpeedfonyBundle