performance tuning with zend framework

26
Performance Tuning with Zend Framework Alan Seiden August 23, 2011 New York City ZF Meetup

Upload: alan-seiden

Post on 12-May-2015

5.094 views

Category:

Technology


1 download

DESCRIPTION

How to measure and optimize performance of applications that use Zend Framework 1.x. A talk presented at the New York City Zend Framework Meetup (http://www.meetup.com/ZendFramework-NYCmetro/) on August 23, 2011.

TRANSCRIPT

Performance Tuning with Zend Framework

Alan SeidenAugust 23, 2011

New York CityZF Meetup

Why a ZF Performance topic?

• I’ve recently helped several clients with performance of their ZF apps

• Performance is important to everyone today

What we’ll cover tonight

• Question: Does ZF performance differ from regular PHP performance?

• Using ZF performance tools– Zend_Db_Profiler– Zend_Cache

• Other ZF performance optimizations• Client side measurement and

optimizations

ZF vs. regular PHP• ZF is PHP

– Framework is PHP– Your app is PHP

• But it’s more PHP code than your app would use if built from scratch– Meant to cover common use cases

• With ZF’s MVC, each request goes through routing, dispatch

• Each class contains redundant require_once() calls– Redundant if you use class autoloader (best performance)– Only in ZF 1.x. To be corrected in ZF 2.0

Zend_Db query profiler

• A good reason to use Zend_Db• Better than manual profiling because you

won’t miss any queries• See the actual SQL created by Zend_Db

• One way: Firebug/FirePHP– In application.ini:

resources.db.params.profiler.enabled = true

resources.db.params.profiler.class =

"Zend_Db_Profiler_Firebug"

Query profiling viewed in FirePHP

Profiling to a log file// a good place to put this profiling code is in the postDispatch() event of a front

controller plugin

$db = Zend_Registry::get('db'); // defined in bootstrap

$profiler = $db->getProfiler();

$totalTime = $profiler->getTotalElapsedSecs();

$queryCount = $profiler->getTotalNumQueries();

foreach ($profiler->getQueryProfiles() as $i=>$query) {

$secs = $query->getElapsedSecs();

$msg = $i . ' - "' . $query->getQuery() . '"';

$msg .= ', Params: ' . implode(',', $query->getQueryParams());

$msg .= ', Time: ' . number_format($secs, 6). ' seconds';

$messages[] = $msg;

}

$log = $queryCount . ' queries in ' . number_format($totalTime, 6)

. ' seconds' . "\n";

$log .= "Queries:\n";

$log .= implode("\n", $messages);

$logger = Zend_Registry::get(‘logger’); // defined in bootstrap

$logger->debug($log);

Log file results2011-08-18T11:34:06-04:00 DEBUG (7): 2 queries in 0.937705 seconds

Queries:

0 - "SELECT COUNT(1) AS "zend_paginator_row_count" FROM "SQHMSTP"

LEFT JOIN "XUPMSTP" AS "UP1" ON QHAFSR = UP1.UPUID

LEFT JOIN "XUPMSTP" AS "UP2" ON QHAUSR = UP2.UPUID

INNER JOIN "XTVMSTP" AS "TV1" ON TV1.TVFLD = 'QHSTAT' and TV1.TVCODE = QHSTAT

INNER JOIN "XTVMSTP" AS "TV2" ON TV2.TVFLD = 'RPTTYP' and TV2.TVCODE = QHTYPE WHERE (QHCOCD = '01')",

Params: , Time: 0.820897 seconds

1 - "SELECT "SQHMSTP"."QHCASE", "SQHMSTP"."QHCHAS", (QHADMM * 10000 + QHADDD * 100 + QHADYY) AS "QHADDT", "SQHMSTP"."QHTYPE", "SQHMSTP"."QHDLR", "SQHMSTP"."QHSTAT", "SQHMSTP"."QHRPRF", "SQHMSTP"."QHCREF", "SQHMSTP"."QHSTAT", CASE WHEN (QHSTAT = '20' OR (QHSTAT = '40' AND QHRPRF = '')) THEN 1 ELSE 0 END AS "EDITABLE", CASE WHEN (QHSTAT = '20' OR QHSTAT = '40') THEN 1 ELSE 0 END AS "DELETABLE", "UP1"."UPNAME" AS "QHASSNAME", "UP2"."UPNAME" AS "QHAUSRNAME", "TV1"."TVDESC" AS "QHSTATDESC", "TV2"."TVDESC" AS "QHTYPEDESC" FROM "SQHMSTP"

LEFT JOIN "XUPMSTP" AS "UP1" ON QHAFSR = UP1.UPUID

LEFT JOIN "XUPMSTP" AS "UP2" ON QHAUSR = UP2.UPUID

INNER JOIN "XTVMSTP" AS "TV1" ON TV1.TVFLD = 'QHSTAT' and TV1.TVCODE = QHSTAT

INNER JOIN "XTVMSTP" AS "TV2" ON TV2.TVFLD = 'RPTTYP' and TV2.TVCODE = QHTYPE WHERE (QHCOCD = '01') ORDER BY "QHCASE" DESC FETCH FIRST 40 ROWS ONLY",

Params: , Time: 0.116808 seconds

Zend_Cache• Flexible caching component• Caches any kind of data: output from PHP

scripts, complete web pages, ACL objects, query results

• Zend_Cache API stores cached data in your choice of “backends” (next slide)

Zend_Cache• Back-ends where cached data can be stored

– Zend Server memory or disk cache– Disk (your choice of location)– Memcached– APC– SQLite– Xcache– Static (for generating static files for Apache to serve)– Two-tier fast/slow

Zend_Cache configuration• Easiest way is in application.ini

– If you set up your app using Zend_Tool

; front-end

resources.cachemanager.database.frontend.name = Core

; lifetime of 3600 means one hour

resources.cachemanager.database.frontend.options.lifetime = 3600

; automatic_serialization enables non-strings (objects) to be cached

resources.cachemanager.database.frontend.options.automatic_serialization = true

; back-end

; ZendServer_ShMem is Zend Server’s shared memory cache

resources.cachemanager.database.backend.name = "ZendServer_ShMem"

resources.cachemanager.database.backend.customBackendNaming = true

Caching tip for Zend_Db_Table• Do cache metadata (table/field definitions) if you

use Zend_Db_Table• Otherwise you will have a performance hit• The degree of performance penalty of always

reading metadata depends on the database server

• Play it safe and cache this metadata– Assuming tables/fields are relatively constant

// in application.ini

// (“database” cache was defined on previous slide)

resources.db.defaultMetadataCache = "database"

Use an opcode/bytecode cache• Frameworks add classes and code to an app• PHP ordinarily must read/interpret/compile all

that code on each request• A bytecode cache stores the “compiled”

bytecode in memory after first execution, speeding subsequent runs

• Examples of bytecode caches:– Zend Server’s Optimizer+– APC– XCache– Windows Cache Extension for PHP

ZF Performance Guidehttp://framework.zend.com/manual/en/performance.html

• Covers several topics related to ZF performance• Written by the ZF development team

• Among its recommendations:– Avoid “action view helper”: invokes dispatch cycle

• Replace with view helpers that query a model directly– “Use partial() only when really necessary”

• Partial() clones the whole View object. Use render() if do not need a new, clean View object

– And…

Class loading• The issues around class loading are given

special attention in the Performance Guide

• In particular, the “autoloader/require_once()”issue is the most frequently discussed performance “flaw” of ZF 1.x

• It will be fixed in ZF 2.0

• Details of 1.x “flaw” on next slide......

Autoloader/require_once() issue• The good:

– ZF’s autoloader is deemed a well performing component• Enabled in /public/index.php like so:

require_once 'Zend/Loader/Autoloader.php'; Zend_Loader_Autoloader::getInstance();

• The bad: – Even though autoloader loads classes as needed, each class

executes require_once() statements at the top for each class it might need

• Solution: remove require_once() statements from almost every ZF class– P.S. Matthew Weier O’Phinney says, “this will only improve

speed if an opcode cache is used.”

How to remove require_once()

Official UNIX way% cd path/to/ZendFramework/library

% find . -name '*.php' -not -wholename

'*/Loader/Autoloader.php' \ -not -wholename

'*/Application.php' -print0 | \ xargs -0 sed --regexp-

extended --in-place 's/(require_once)/\/\/ \1/g'

Doesn’t remove it from Autoloader.php and Application.php because it’s needed there!

Removing require_once() #2Using PHP// from http://pastebin.com/wHKJZ68e

chdir (‘mylocation\library\Zend');

foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator('.')) as $o_File) {

if (

'.php' === substr($o_File, -4) &&

false === strrpos($o_File, '.' . DIRECTORY_SEPARATOR . 'Loader' . DIRECTORY_SEPARATOR . 'Autoloader.php') &&

false === strrpos($o_File, '.' . DIRECTORY_SEPARATOR . 'Application.php')) {

$s_Code = preg_replace('/^(\s*)(require_once)/im', '\1// \2', file_get_contents($o_File), -1, $i_Replacements);

if ($i_Replacements > 0) {

echo $o_File, ' with ', $i_Replacements, ' replacements.', PHP_EOL;

file_put_contents($o_File, $s_Code);

}

}

}

Keep an eye on the front end

• Otherwise known as the “client” side• Includes .js, .css, images, and AJAX calls• Check it out with Firebug’s “Net” panel or your

favorite tool

• Example coming up...

HTTP requests

In particular, beware if several AJAX calls must execute on page load(not shown here) in order for page to render

Apache rewrite rule.htaccess usually looks like this:RewriteEngine On

RewriteCond %{REQUEST_FILENAME} -s [OR]

RewriteCond %{REQUEST_FILENAME} -l [OR]

RewriteCond %{REQUEST_FILENAME}

RewriteRule ^.*$ – [NC,L]

RewriteRule ^.*$ index.php [NC,L]

• Any request that’s not a real file gets routed into ZF/PHP

• What’s the performance flaw?

Nonexistent filesRewriteEngine On

RewriteCond %{REQUEST_FILENAME} -s [OR]

RewriteCond %{REQUEST_FILENAME} -l [OR]

RewriteCond %{REQUEST_FILENAME}

RewriteRule ^.*$ – [NC,L]

RewriteRule ^.*$ index.php [NC,L]

• Nonexistent files (whether favicon.ico or my.hacker.getya) get routed to ZF, putting load on app server, before generating a 404 not found error

• Shouldn’t the web server handle 404?

Solution• I haven’t found a perfect solution

• To intercept normal “file not found” errors in Apache:– RewriteRule !\.(js|ico|gif|jpg|png|css|html|txt|log)$ index.php

• If I’m confident that app URLs shouldn’t have any periods/dots in ZF URLs:– RewriteRule !\.([^\.]+)$ index.php

– ZF will only receive period-free URLs– Apache can then catch “weird” URLs such as

“w00tw00t.at.ISC.SAN” (I found this in a customer’s Apache log)

• Demonstration on next slide

• Better idea? Send to [email protected]

404 Before/after new rule

Before After

Further learning• Attend the NYC Web Performance Meetup• Follow me at @alanseiden• Keep coming to our ZF meetup:

http://www.meetup.com/ZendFramework-NYCmetro/

• Attend ZendCon, Oct. 17-20, 2011

• Share your discoveries—you are welcome to present at the ZF Meetup

New York City area Zend Framework Meetup

http://www.meetup.com/ZendFramework-NYCmetro/

Affiliated with http://www.nyphp.org/

Alan Seiden http://[email protected]: @alanseiden

Thanks for attending Performance Tuning with Zend Framework

presented on Aug. 23, 2011 by

Sign up to hear about all our ZF meetups at http://www.meetup.com/ZendFramework-NYCmetro/