accelerate your rails site with automatic generation-based action caching
TRANSCRIPT
Accelerate Your Rails Site with Automatic Generation-Based Action CachingRod Cope, CTO and FounderOpenLogic, Inc.
Wednesday, July 29, 2009
OpenLogic Company Confidential
Goal
Built-in caching is hard:
Learn how to automate it tomake the pain go away
2
Wednesday, July 29, 2009
OpenLogic Company Confidential
Agenda
IntroductionBuilt-in cachingAutomatic generation-based cachingBonus materialConclusion & recommendations
3
Wednesday, July 29, 2009
OpenLogic Company Confidential
Introduction
Rod CopeCTO & Founder of OpenLogic25 years of software development experienceIBM Global Services, Anthem, Ericsson, many more
OpenLogic, Inc.Governance, SLA support, security updates, and
indemnification for over 500 Open Source packagesDozens of Fortune 500 customersOSS Census (osscensus.org)
Global, community effort to catalog use of open source
4
Wednesday, July 29, 2009
OpenLogic Company Confidential 5
No Spoon Feeding
Wednesday, July 29, 2009
OpenLogic Company Confidential
Don’t Panic
scotduke.com
6
Wednesday, July 29, 2009
OpenLogic Company Confidential
Reasons for Caching
Lots of hits, speed is kingGoogle
7
Wednesday, July 29, 2009
OpenLogic Company Confidential 8
Wednesday, July 29, 2009
OpenLogic Company Confidential
Reasons for Caching
Lots of hits, speed is kingGoogle
Complex hits, richness is kingOpenLogic’s OLEX
9
Wednesday, July 29, 2009
OpenLogic Company Confidential 10
Wednesday, July 29, 2009
OpenLogic Company Confidential
Rails Caching Background
Page cachingUberfast, little controlHighly dynamic site? Move on.
Action cachingFast, lots of controlExpiration hell
Fragment cachingExpiration hell
11
Wednesday, July 29, 2009
OpenLogic Company Confidential
Problems with Rails Caching
Caching is easy to implement“Site’s borked again - turn off caching.”Hard to clear (the right) caches when you’re mixing and matching different types, styles, developers, plugins, etc.
12
Wednesday, July 29, 2009
OpenLogic Company Confidential
Automatic Generation-Based CachingGeneration-based
Partition the cache so that every state change increments the “generation”
AutomaticAny action that could possibly change state bumps the countLet memcached overwrite old generations
Very conservativeNo domain knowledgeDon’t cache errors, AJAX, redirects, flash notices, etc.
13
Wednesday, July 29, 2009
OpenLogic Company Confidential
Overview
Yourcode
Database memcachedRender View
Cache helper
Rails plumbing
Rails plumbing
Non-cached Cached
14
Wednesday, July 29, 2009
OpenLogic Company Confidential
Key Value
/packages/1 <html><body>Rails</body></html>
/packages/2 <html><body>Ruby</body></html>
Cache = Hash
Wednesday, July 29, 2009
OpenLogic Company Confidential
Key Value
/gen/1/packages/1 <html>Rails</html>
/gen/1/packages/2 <html>Ruby</html>
Generational Cache
Wednesday, July 29, 2009
OpenLogic Company Confidential
Key Value
/gen/1/packages/1 <html>Rails</html>
/gen/1/packages/2 <html>Ruby</html>
/gen/2/packages/1 <html>Ruby on Rails</html>
Generational Cache
Wednesday, July 29, 2009
OpenLogic Company Confidential
memcached is your friend
Very fastDon’t worry about removing old entriesmemcached will automatically drop the oldest keys when it runs low on memoryRun it on your web servers if you have the RAM
18
Wednesday, July 29, 2009
OpenLogic Company Confidential
Cache Helper
No cache-related code sprinkled throughout every model, view, and/or controllerUse a global “around” filterAutomatically increment (scoped) generation count upon POST, PUT, or DELETEHandle event recording and playback (optional)
19
Wednesday, July 29, 2009
OpenLogic Company Confidential
Cache Helper Code (in “around” filter)key = make_auto_cache_key(request.request_uri)output = Rails.cache.read(key)
if output render :text => output return
else yield unless response.redirected_to || flash[:notice]
Rails.cache.write(key, response.body) endend
20
Wednesday, July 29, 2009
OpenLogic Company Confidential
Cache Key
def make_auto_cache_key(key) ol_gen = Rails.cache.fetch(OL_GEN) { "1" } "olex/#{ol_gen}"end
21
Wednesday, July 29, 2009
OpenLogic Company Confidential
“Clear” the Cache
POST, PUT, or DELETE “clears” the cache
def maybe_clear_auto_cache return if request.get? gen = Rails.cache.fetch(OL_GEN) { "1" } Rails.cache.write(OL_GEN, (gen.to_i + 1).to_s)end
22
Wednesday, July 29, 2009
OpenLogic Company Confidential
Controller Customization
Some controllers may need special cache control (e.g., user-specific cache, disable cache for certain methods)
olex_auto_cache_is_specific_to_user :only => :show
Note: the cache may still need to be cleared when skipping cache usage!
skip_filter :olex_auto_cache after_filter :maybe_clear_olex_auto_cache
23
Wednesday, July 29, 2009
OpenLogic Company Confidential
Cache Partitioning
Make it impossible for users of different corporations and permission levels to see each other’s stuff, access the same cache, clear somebody else’s cache, etc.Make sure any “global” changes clear all cachesUse a cache hierarchy
/olex/<gen #>/corp/<corp #>/<corp gen #>/roles/<role #s>/URLMD5 the key (memcached has a 250 char limit)
Extra credit: Use the key as an etag
24
Wednesday, July 29, 2009
OpenLogic Company Confidential
/olex/13/corp/72/2/roles/2,7,19/packages/2<html>
Rails (unsupported)</html>
Partitioned Cache
Global generation
Wednesday, July 29, 2009
OpenLogic Company Confidential
/olex/13/corp/72/2/roles/2,7,19/packages/2<html>
Rails (unsupported)</html>
Partitioned Cache
Corporate account ID
Wednesday, July 29, 2009
OpenLogic Company Confidential
/olex/13/corp/72/2/roles/2,7,19/packages/2<html>
Rails (unsupported)</html>
Partitioned CacheCorporation #72’s generation
Wednesday, July 29, 2009
OpenLogic Company Confidential
/olex/13/corp/72/2/roles/2,7,19/packages/2<html>
Rails (unsupported)</html>
Partitioned Cache
Current user’s role IDs
Wednesday, July 29, 2009
OpenLogic Company Confidential
/olex/13/corp/72/2/roles/2,7,19/packages/2<html>
Rails (unsupported)</html>
Partitioned Cache
URL
Wednesday, July 29, 2009
OpenLogic Company Confidential
/olex/13/corp/72/2/roles/2,7,19/packages/2<html>
Rails (unsupported)</html>
/olex/13/corp/72/2/roles/2,7,19,22/packages/2<html>
Rails (manager secret!)</html>
/olex/14/corp/72/2/roles/2,7,19/packages/2<html>
Rails (supported)</html>
/olex/14/guest/packages/2 <html>Rails</html>
Partitioned Cache
Wednesday, July 29, 2009
OpenLogic Company Confidential
Cache Key
def make_auto_cache_key(key, user = nil) ol_gen = Rails.cache.fetch(OL_GEN) { "1" } ol_prefix = "olex/#{ol_gen}" if user.nil? && self.respond_to?("current_user") user = current_user end ca = get_corporate_account(user) ...
31
Wednesday, July 29, 2009
OpenLogic Company Confidential
Cache Key
...if user.is_corp_user? corp_gen = Rails.cache.fetch(corp_gen_key(ca)){"1"} final_key = make_corp_key(user, ca, key, corp_gen)else final_key = "guest#{key}"end
32
Wednesday, July 29, 2009
OpenLogic Company Confidential
Corporate Cache Key
def make_corp_key(user, ca, key, corp_gen) roles = role_string(user) "/corp/#{ca.id}/#{corp_gen}/roles/#{roles}#{key}"end
def role_string(user) Rails.cache.fetch("roles/#{user.id}") do user.role_string endend
33
Wednesday, July 29, 2009
OpenLogic Company Confidential
Benefits
Never have to explicitly expire anythingCan’t get an expired page
Generation numbers also stored in memcachedCache hierarchy is like a directory structure
Easy to grokCan’t run out of “disk space”!
34
Wednesday, July 29, 2009
OpenLogic Company Confidential
Does it really work?
In production for over a year - a few minor issuesSee Gotchas
Huge performance improvement for us, almost no developer pain
e.g., needed cookie-based “Welcome Joe!”Great for “passive” caching
Write caching code once, enjoy it foreverLots of room for more aggressive caching
35
Wednesday, July 29, 2009
OpenLogic Company Confidential
Gotchas
Easy to forget to make AJAX calls use RESTmethod:'get','post', 'put', or 'delete'
Test with and without caching, and test the caching itself!Caching doesn’t help the first hit
Mitigate by pre-caching when feasible
36
Wednesday, July 29, 2009
OpenLogic Company Confidential
Recommendations
The “safety first” caching implementation - use itIt’s still action caching, so don’t expect page caching performanceMeasure the result - not all pages will benefit and there is some overhead
37
Wednesday, July 29, 2009
OpenLogic Company Confidential
Bonus
Asynchronous pre-caching
Event recording and playback
38
Wednesday, July 29, 2009
OpenLogic Company Confidential
Async pre-caching pages at logon
Use Workling/Starling for asyncWhen user logs on, make async callAsync call runs through list of URL’s and hits them on behalf of the currently logged on userNeed secret back-door logon in application.rb
Use big scary session key in environment.rb
39
Wednesday, July 29, 2009
OpenLogic Company Confidential
Event Recording
Q: What if you want to record the fact that something happened even though the page was automatically cached and served?A: Watch it while being cached, record what happens in memcached, play it back later when serving cached version
40
Wednesday, July 29, 2009
OpenLogic Company Confidential
Summary
Rails built-in caching takesa lot of on-going work
Easy to screw it up
Automatic caching can be writtenonce and enjoyed forever
Much harder to screw up
Still possible to use more aggressive techniques selectively
41
Wednesday, July 29, 2009
OpenLogic Company Confidential
Any questions for Rod?
42
Wednesday, July 29, 2009
OpenLogic Company Confidential
Resources
memcached: http://www.danga.com/memcached/Workling: http://github.com/purzelrakete/workling/tree/masterStarling: http://rubyforge.org/projects/starling/OLEX: http://olex.openlogic.com
43
Wednesday, July 29, 2009
OpenLogic Company Confidential
Credits
Unless otherwise indicated, photos were licensed from BigStockPhoto.com
44
Wednesday, July 29, 2009
OpenLogic Company Confidential
OpenLogic is hiring!
Rails guru?Live near Denver, Colorado?Love Open Source?
Come see me!
45
Wednesday, July 29, 2009