Symfony Performance

Preview:

DESCRIPTION

Covers Performance improvements with the Symfony web framework for PHP. - Google cares about user happiness, Google owns your 
search traffic

...so Google put page speed in PageRank (and crawl speed) - Your site is more trustworthy and less frustrating - Increase page views and ad impressions - Increase conversions and revenue! It pays for itself! - Bonus: run less app servers

Citation preview

SYMFONY PERFORMANCE

Paul Thrasher@thrashr888 | github.com/thrashr888

Sr. Software Engineer at Dogster

SF Symfony Meetup October#sfsymfony

WHY WORRY ABOUT PERFORMANCE?

• Google cares about user happiness, Google owns your search traffic

...so Google put page speed in PageRank (and crawl speed)

• Your site is more trustworthy and less frustrating

• Increase page views and ad impressions

• Increase conversions and revenue! It pays for itself!

• Bonus: run less app servers

you andV

• Started in 2004

• Catster.com is the same codebase

• ~2MM monthly uniques

• ~12MM monthly page views

• Over 1 million dogs and cats on our sites

• Production servers: 5 app, 3 master DBs, 8 slave DBs

• Spent the summer redesigning and working on performance

DOGSTER, INC.

Backend Performance

Benchmark, PHP, Database, Caching

Symfony 1.4.X Performance

You and your team, Caching

Frontend Performance

Benchmark, HTML, JS, CSS, Images, CDN

Questions at End

Backend

BACKEND - OPPORTUNITIES

Benchmark before you do anything!

• The servers

• PHP

• Apache

• Database

• Your crappy code

• Caching

Use siege or ab (Apache Benchmark)http://www.joedog.org/index/siege-home

thrashr888@Pauls-MacBook-Pro-2 workspace$ siege http://www.dogster.com** SIEGE 2.69** Preparing 50 concurrent users for battle.The server is now under siege... 12: HTTP/1.1 200 0.65 secs: 11063 bytes ==> / 5: HTTP/1.1 200 0.68 secs: 11062 bytes ==> / 39: HTTP/1.1 200 0.70 secs: 11062 bytes ==> / 28: HTTP/1.1 200 0.73 secs: 11031 bytes ==> / 2: HTTP/1.1 200 0.74 secs: 11061 bytes ==> / 31: HTTP/1.1 200 0.48 secs: 11032 bytes ==> / 22: HTTP/1.1 200 0.48 secs: 11063 bytes ==> / 2: HTTP/1.1 200 0.46 secs: 11062 bytes ==> / 6: HTTP/1.1 200 0.46 secs: 11031 bytes ==> / 5: HTTP/1.1 200 0.47 secs: 11063 bytes ==> /^CLifting the server siege... done.Transactions: 224 hitsAvailability: 100.00 %Elapsed time: 5.11 secsData transferred: 2.36 MBResponse time: 0.55 secsTransaction rate: 43.84 trans/secThroughput: 0.46 MB/secConcurrency: 24.24Successful transactions: 224Failed transactions: 0Longest transaction: 1.58Shortest transaction: 0.32

BACKEND - PHP 5.3Migrate to PHP 5.3 http://github.com/smalyshev/migrate/

thrashr888@MacBook workspace$ php migrate.php SymStersWARNING: Function 'spliti' is deprecated, please use 'preg_split' instead in file SymSters/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/vendor/doctrine/Doctrine/Connection/Mssql.php line 173WARNING: Function 'spliti' is deprecated, please use 'preg_split' instead in file SymSters/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/vendor/doctrine/Doctrine/Connection/Mssql.php line 192WARNING: Function 'ereg' is deprecated, please use 'preg_match' instead in file SymSters/lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/phing/lib/Zip.php line 1852WARNING: Function 'ereg' is deprecated, please use 'preg_match' instead in file SymSters/lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/phing/lib/Zip.php line 2735WARNING: Function 'split' is deprecated, please use 'explode' or 'preg_split' instead in file SymSters/lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/phing/tasks/ext/coverage/CoverageReportTask.php line 168WARNING: Function 'set_magic_quotes_runtime' is deprecated, its use is no longer recommended in file SymSters/lib/vendor/symfony/lib/vendor/swiftmailer/classes/Swift/ByteStream/FileByteStream.php line 82WARNING: Function 'set_magic_quotes_runtime' is deprecated, its use is no longer recommended in file SymSters/lib/vendor/symfony/lib/vendor/swiftmailer/classes/Swift/ByteStream/FileByteStream.php line 87WARNING: Function 'set_magic_quotes_runtime' is deprecated, its use is no longer recommended in file SymSters/lib/vendor/symfony/lib/vendor/swiftmailer/classes/Swift/KeyCache/DiskKeyCache.php line 174WARNING: Function 'set_magic_quotes_runtime' is deprecated, its use is no longer recommended in file SymSters/lib/vendor/symfony/lib/vendor/swiftmailer/classes/Swift/KeyCache/DiskKeyCache.php line 183WARNING: Function 'set_magic_quotes_runtime' is deprecated, its use is no longer recommended in file SymSters/lib/vendor/symfony/lib/vendor/swiftmailer/classes/Swift/KeyCache/DiskKeyCache.php line 202WARNING: Function 'set_magic_quotes_runtime' is deprecated, its use is no longer recommended in file SymSters/lib/vendor/symfony/lib/vendor/swiftmailer/classes/Swift/KeyCache/DiskKeyCache.php line 210WARNING: Function 'ereg' is deprecated, please use 'preg_match' instead in file SymSters/plugins/sfPropel15Plugin/lib/vendor/phing/lib/Zip.php line 1852WARNING: Function 'ereg' is deprecated, please use 'preg_match' instead in file SymSters/plugins/sfPropel15Plugin/lib/vendor/phing/lib/Zip.php line 2735

BACKEND - MYSQL SLOW QUERY LOGCount : 55 (5.46%)Time : 204.477485 s total, 3.717772 s avg, 1.007915 s to 20.940426 s max (9.81%) 95% of Time : 160.326234 s total, 3.083197 s avg, 1.007915 s to 9.951604 s maxLock Time (s) : 2.507 ms total, 46 盜 avg, 34 盜 to 65 盜 max (1.86%) 95% of Lock : 2.318 ms total, 45 盜 avg, 34 盜 to 61 盜 maxRows sent : 3 avg, 3 to 3 max (0.00%)Rows examined : 14.69k avg, 311 to 112.50k max (0.12%)Database : starsterEXPLAIN : 14369 produced, 2278976448465661 read id: 1 select_type: SIMPLE table: p type: ref possible_keys: pub_index,user_id_idx,NI_PET_CODE key: user_id_idx key_len: 3 ref: const rows: 1732 Extra: Using where; Using filesort

Query abstract:SET timestamp=N; SELECT p.post_id, p.body, p.pet_id, p.thread_id, p.main_topic_id FROM forums_post_new p WHERE p.pub_status != 'S' AND p.user_id = 'S' AND p.pet_code = 'S' ORDER BY p.post_id DESC LIMIT N;

Query sample:SET timestamp=1286803854;SELECT p.post_id, p.body, p.pet_id, p.thread_id, p.main_topic_id FROM forums_post_new p WHERE p.pub_status != 'n' AND p.user_id = '243549' AND p.pet_code = 'd' ORDER BY p.post_id DESC LIMIT 3;

BACKEND - OTHER METHODS

• Setup timers, refactor and cache slow code

• Memcache, APC cache, etc.

• Add app and DB servers

• Use Twig for layouts, it uses file-based caching

Don’t get too caught up here unless there are major issues.

Symfony

The big secret to better performance with Symfony is…

…you’re already doing it.(Symfony improves team performance!)

Symfony already does a few other things for you:

• caches settings and config

• keeps junk off production servers

• provides caching framework to make your caching easier

• Core Compilation (turns 30 files into 1 file)

• BUT Symfony is for apps, not speedy scripts

• Symfony performance mostly means caching…

SYMFONY - PROJECT:OPTIMIZE

thrashr888@MacBook workspace$ php symfony project:optimize api prod>> sfPatternRouting Connect sfRoute "sf_guard_signin" (/login)>> sfPatternRouting Connect sfRoute "sf_guard_signout" (/logout)>> sfPatternRouting Connect sfRoute "sf_guard_password" (/request_password)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user" (/sf_guard_user.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_new" (/sf_guard_user/new.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_create" (/sf_guard_user.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_edit" (/sf_guard_user/:id/edit.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_update" (/sf_guard_user/:id.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_delete" (/sf_guard_user/:id.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_show" (/sf_guard_user/:id.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_object" (/sf_guard_user/:id/:action.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_collection" (/sf_guard_user/:action/action.:sf_format)>> sfPatternRouting Connect sfRoute "sf_guard_signin" (/login)>> sfPatternRouting Connect sfRoute "sf_guard_signout" (/logout)>> sfPatternRouting Connect sfRoute "sf_guard_password" (/request_password)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user" (/sf_guard_user.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_new" (/sf_guard_user/new.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_create" (/sf_guard_user.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_edit" (/sf_guard_user/:id/edit.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_update" (/sf_guard_user/:id.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_delete" (/sf_guard_user/:id.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_show" (/sf_guard_user/:id.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_object" (/sf_guard_user/:id/:action.:sf_format)>> sfPatternRouting Connect sfPropelRoute "sf_guard_user_collection" (/sf_guard_user/:action/action.:sf_format)>> dir+ /Users/thrashr888/workspace/SymSters/cache/api/prod/config>> file+ /Users/thrashr888/workspace/SymSters/cache/api/prod/config/configuration.php

SYMFONY - VIEW CACHE

# apps/frontend/config/settings.ymlprod: .settings: cache: true

# apps/frontend/config/cache.ymldefault: enabled: false with_layout: false lifetime: 86400

http://www.symfony-project.org/jobeet/1_4/Doctrine/en/21

# apps/frontend/modules/homepage/config/cache.ymlindex: enabled: trueall: with_layout: true

SYMFONY - ACTION CACHE

http://www.symfony-project.org/jobeet/1_4/Doctrine/en/21

# apps/frontend/modules/homepage/config/cache.ymlall: with_layout: false

Just cache the action, not the views:

SYMFONY - PARTIAL CACHE

http://www.symfony-project.org/jobeet/1_4/Doctrine/en/21

# apps/frontend/modules/homepage/config/cache.ymlindex: enabled: true

_sidebar: enabled: true

all: with_layout: true

Just give it the name of the partial file:

SYMFONY - FUNCTION CACHE

<?php

$dog = new Dog();

$cache = new sfMemcacheCache();$func = new sfFunctionCache($cache);

$func->call(array($dog, 'getFriends'), array('25'));

You can cache many function calls:

SYMFONY - TEMPLATE CACHE

<?php if (!cache('name')): ?>

<div id="dogs"> <?php foreach($dogs as $dog): ?> <div id="<?php echo $dog->id ?>" class="dog"><?php echo $dog->name ?></div> <?php endforeach; ?> </div>

<?php cache_save() ?><?php endif; ?>

Cache expensive bits of your template:

Stop worrying so much about speeding up your backend because…

Frontend

DOGSTER.COMUncached page load

URL

status

domain

size

timeline

DOGSTER.COM

GOOD{

DOGSTER.COM

Not so good {

FRONTEND - HOW DO WE FIX THIS?

Less HTML, JS, CSS

Less images

Less photoshop (more HTML)

Faster JS

Faster CSS

Faster images

FRONTEND - LESS IS MORE

Less HTML, JS, CSS

• Remove code you’re not using

• Concat files together for lower HTTP requests

Less images

• Make design decisions to remove images

• Use sprites for less HTTP requests

Less Photoshop, more HTML

• Don’t go crazy with Photoshop when you’re designing

FRONTEND - FASTER DOWNLOADS

Faster JavaScript

• Be careful about where and how you place your JS

Faster CSS

• Minify and gzip your files

Faster images

• Compress your images before uploading

GUEST SPEAKERYUKO TAKAHASHI

DESIGNER AT DOGSTER

FRONT ENDCSS SPRITES :: IMAGE OPTIMIZATION :: CSS3

WHAT IS A CSS SPRITE?

• A single image that contains a series of smaller images

CSS SPRITE - BUTTON EXAMPLES

digicliff

webair

THE CSS IS EASY AS PIE

• CSS calls and positions the sprite withbackground-image and background-position properties

• See? Easy as pie!

.image { background-image:

url(http://www.website.com/images/sprite-image.png);background-position: 0px 100px;width: 100px;height: 50px; }

WHY USE CSS SPRITES?

• Reduce the number of HTTP Requests to server

• Improves load time of your website

• Faster load time on your pages means better search engine page ranking

EXAMPLE

• Dogster global elements used 32 images that were“sprite-able”. We tiled all 32 images onto one image

• Combined 32 images = 170 KBSingle sprite image = 70 KB

HOW TO CREATE CSS SPRITES

• Option 1: Create them manually

• Option 2: Use a Sprite generator

• SpriteMe – http://spriteme.org/

• Project Fondue CSS Sprite generator - http://spritegen.website-performance.org/

• SmartSprites - http://csssprites.org/

IMAGE OPTIMIZATION

Using image compression tools to make sure your files are as small as they can be

• OptiPNG - http://optipng.sourceforge.net/

• Image Optimizer - http://www.imageoptimizer.net/Pages/Home.aspx

• PNG Crusher - http://www.amake.us/software/pngcrusher/

IMAGE OPTIMIZATION (CONT’D.)

Avoid using text images, i.e., images that just contain text. Try to use a font service instead:

• Typekit - http://typekit.com/

• Font-Squirrel (free!) - http://www.fontsquirrel.com/

• FontFont - http://www.fontfont.com/

• Google Fonts - http://code.google.com/webfonts

POWER OF CSS3

Use cool CSS3 style effects - enables you to style WITHOUT images

• Rounded corners, drop shadows, gradients

#findAVetMain {-moz-border-radius:10px 10px 10px 10px;-moz-box-shadow:0 0 8px #666666;background-color:#FFFFFF;border:1px solid #999999;height:180px;margin:10px 0 20px;

}

LESS PHOTOSHOP, MORE HTML

When designing mockups for your site, think of how it will get built with html and css

• E.g. Does your nav bar really need an image for each item? style it with css using a UL

• Think of how you can build each element without an image

• Build it with clean HTML - don’t stick everything in a div. Use your H1s, your Ps, ULs, etc...and it will perform better

THANKS EVERYONE!

FRONTEND - IMPLEMENTATIONS

LABjs

• Download your JS files in parallel

CDN: Amazon S3 and CloudFront

• Give your assets a proper home

Quarantine Your Ads

• They can be a pain, put them in a box

FRONTEND - LABJS

Use LABjs to download your JS in parallel

<script type="text/javascript" src="http://remote.tld/jquery.js"></script><script type="text/javascript" src="local/plugin1.jquery.js"></script><script type="text/javascript" src="local/plugin2.jquery.js"></script><script type="text/javascript" src="local/init.js"></script><script type="text/javascript"> initMyPage();</script>

<script type="text/javascript" src="LAB.js"></script><script type="text/javascript">$LAB.script("http://remote.tld/jquery.js").wait().script("/local/plugin1.jquery.js").script("/local/plugin2.jquery.js").wait().script("/local/init.js").wait(function(){ initMyPage();});</script>

JS Loading Asynchronously

FRONTEND - JAVASCRIPT

Also move inline scripts to bottom of HTML body

<!DOCTYPE html><html><body> <p>NO</p> <script>sleep(2);</script> <p>'TIL BROOKLYN</p></body></html>

<!DOCTYPE html><html><body> <p>Don't block me, bro!</p> <script>(function(){sleep(2);})();</script> <script>$({sleep(2);});</script> <script>$(document).ready(function(){sleep(2);});</script></body></html>

Assets mostly load before JS scripts

FRONTEND - CDN ADVANTAGES

Use a CDN, or giving your assets the home they deserve. There are several advantages to using Amazon’s CDN.

• Geographical edge caching brings files closer to your users

• Purposefully set HTTP headers

• Cookie-less domain

• No need to use Apache/PHP to serve assets

• Overall quicker downloads

• Easy to round-robin domains

FRONTEND - STATIC FILES

Static

http://a1.cdnsters.com/static/images/slideshow/220x220rainydaydog.jpg

• Files that rarely change

• Typically libraries and images

• User generated content

• We mirror our images FTP server every 15 minutes

Two classes of assets you’ll be serving:

FRONTEND - RELEASE FILES

Release

http://a2.cdnsters.com/releases/20101014-1128/topic/css/main.min.css.gz

• Files that might change each release

• Typically related to the apps

Two classes of assets you’ll be serving:

FRONTEND - CDN IMPLEMENTATION

So, how do you implement S3 and CloudFront?

1. Concat files by hand or using scripts

2. Compress images with ImageOptim

3. Create Minified versions of your JS and CSS

4. Create gzipped versions of all files

5. “rsync” white-listed folders to S3, with proper headers

6. CloudFront automatically gets files from S3Bonus: https://gist.github.com/b82e5671e1f46760f0d9

Also try: http://tinycdn.com/

FRONTEND - HTTP HEADERS

Expires

FRONTEND - HTTP HEADERS

Gzip

FRONTEND - HTTP HEADERS

Content-Type

FRONTEND - ADS

Ads typically load external scripts that block page rendering. You’ll probably want to quarantine your ads in some manner.

• Ads really really suck

• Place them in iframes

• Insert placeholders in HTML that populate at end of page via Javascript

• Async loading probably doesn’t work because of document.write() in ad scripts

* Google likely does count ads in overall page speed time

Backend Performance

Benchmark, PHP, Database, Caching

Symfony 1.4.X Performance

You and your team, Caching

Frontend Performance

Benchmark, HTML, JS, CSS, Images, CDN

WRAP UP

THANK YOU.QUESTIONS?

http://www.dogster.com/http://www.joedog.org/index/siege-homehttp://github.com/smalyshev/migrate/http://dev.mysql.com/doc/refman/5.5/en/slow-query-log.htmlhttp://www.symfony-project.org/jobeet/1_4/Doctrine/en/21http://www.yotta.com/http://www.webpagetest.org/http://labjs.com/

More JS loading tips: http://jquerysbestfriends.com/

Rules for Faster-Loading Web Sites: http://stevesouders.com/hpws/rules.php

http://aws.amazon.com/s3/http://aws.amazon.com/cloudfront/http://tinycdn.com/http://code.google.com/p/minify/http://www.io.com/~maus/HttpKeepAlive.html

Bonus S3 Symfony Task: https://gist.github.com/b82e5671e1f46760f0d9

Yuko Takahashi@yukodesignsUI/Interactive Designer at Dogster

Paul Thrasher@thrashr888 | github.com/thrashr888

Sr. Software Engineer at Dogster

SF Symfony Meetup#sfsymfony