SYMFONY PERFORMANCE Paul Thrasher @thrashr888 | github.com/thrashr888 Sr. Software Engineer at Dogster SF Symfony Meetup October #sfsymfony
May 25, 2015
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
actual image: 1271 pixels x 1230 pixelsvia: http://kenmat.tumblr.com/post/121458457/buildingssheet7dv-png
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!
CSS SPRITE RESOURCES
• http://www.alistapart.com/articles/sprites/
• http://css-tricks.com/holy-sprites/
• http://css-tricks.com/css-sprites/
• http://www.smashingmagazine.com/2009/04/27/the-mystery-of-css-sprites-techniques-tools-and-tutorials/
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