62
SYMFONY PERFORMANCE Paul Thrasher @thrashr888 | github.com/thrashr888 Sr. Software Engineer at Dogster SF Symfony Meetup October #sfsymfony

Symfony Performance

Embed Size (px)

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

Page 1: Symfony Performance

SYMFONY PERFORMANCE

Paul Thrasher@thrashr888 | github.com/thrashr888

Sr. Software Engineer at Dogster

SF Symfony Meetup October#sfsymfony

Page 2: Symfony Performance

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

Page 3: Symfony Performance

• 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.

Page 4: Symfony Performance

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

Page 5: Symfony Performance

Backend

Page 6: Symfony Performance

BACKEND - OPPORTUNITIES

Benchmark before you do anything!

• The servers

• PHP

• Apache

• Database

• Your crappy code

• Caching

Page 7: Symfony Performance

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

Page 8: Symfony Performance

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

Page 9: Symfony Performance

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;

Page 10: Symfony Performance

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

Page 11: Symfony Performance

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

Page 12: Symfony Performance

Symfony

Page 13: Symfony Performance

The big secret to better performance with Symfony is…

Page 14: Symfony Performance

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

Page 15: Symfony 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…

Page 16: Symfony Performance

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

Page 17: Symfony Performance

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

Page 18: Symfony Performance

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:

Page 19: Symfony Performance

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:

Page 20: Symfony Performance

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:

Page 21: Symfony Performance

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:

Page 22: Symfony Performance

Stop worrying so much about speeding up your backend because…

Page 23: Symfony Performance

Frontend

Page 24: Symfony Performance
Page 25: Symfony Performance

DOGSTER.COMUncached page load

Page 26: Symfony Performance

URL

status

domain

size

timeline

Page 27: Symfony Performance

DOGSTER.COM

GOOD{

Page 28: Symfony Performance

DOGSTER.COM

Not so good {

Page 29: Symfony Performance

FRONTEND - HOW DO WE FIX THIS?

Less HTML, JS, CSS

Less images

Less photoshop (more HTML)

Faster JS

Faster CSS

Faster images

Page 30: Symfony Performance

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

Page 31: Symfony Performance

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

Page 32: Symfony Performance

GUEST SPEAKERYUKO TAKAHASHI

DESIGNER AT DOGSTER

Page 33: Symfony Performance

FRONT ENDCSS SPRITES :: IMAGE OPTIMIZATION :: CSS3

Page 35: Symfony Performance

WHAT IS A CSS SPRITE?

• A single image that contains a series of smaller images

Page 36: Symfony Performance

CSS SPRITE - BUTTON EXAMPLES

digicliff

webair

Page 37: Symfony Performance

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; }

Page 38: Symfony Performance

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

Page 39: Symfony Performance

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

Page 40: Symfony Performance

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/

Page 41: Symfony Performance

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/

Page 42: Symfony Performance

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

Page 43: Symfony Performance

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;

}

Page 44: Symfony Performance

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

Page 45: Symfony Performance

THANKS EVERYONE!

Page 47: Symfony Performance

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

Page 48: Symfony Performance

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>

Page 49: Symfony Performance

JS Loading Asynchronously

Page 50: Symfony Performance

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>

Page 51: Symfony Performance

Assets mostly load before JS scripts

Page 52: Symfony Performance

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

Page 53: Symfony Performance

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:

Page 54: Symfony Performance

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:

Page 55: Symfony Performance

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/

Page 56: Symfony Performance

FRONTEND - HTTP HEADERS

Expires

Page 57: Symfony Performance

FRONTEND - HTTP HEADERS

Gzip

Page 58: Symfony Performance

FRONTEND - HTTP HEADERS

Content-Type

Page 59: Symfony Performance

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

Page 60: Symfony Performance

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

Page 61: Symfony Performance

THANK YOU.QUESTIONS?

Page 62: Symfony Performance

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