Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Accelerate Your Rails Site with Automatic Generation-Based Action Caching
1. Accelerate Your Rails Site with
Automatic Generation-Based Action
Caching
Rod Cope, CTO and Founder
OpenLogic, Inc.
Wednesday, July 29, 2009
2. Goal
Built-in caching is hard:
Learn how to automate it to
make the pain go away
OpenLogic Company Confidential 2
Wednesday, July 29, 2009
3. Agenda
Introduction
Built-in caching
Automatic generation-based caching
Bonus material
Conclusion & recommendations
OpenLogic Company Confidential 3
Wednesday, July 29, 2009
4. Introduction
Rod Cope
CTO & Founder of OpenLogic
25 years of software development experience
IBM Global Services, Anthem, Ericsson, many more
OpenLogic, Inc.
Governance, SLA support, security updates, and
indemnification for
over 500 Open Source packages
Dozens of Fortune 500 customers
OSS Census (osscensus.org)
Global, community effort to catalog use of open source
OpenLogic Company Confidential 4
Wednesday, July 29, 2009
5. No Spoon Feeding
OpenLogic Company Confidential 5
Wednesday, July 29, 2009
6. Don’t Panic
scotduke.com
OpenLogic Company Confidential 6
Wednesday, July 29, 2009
7. Reasons for Caching
Lots of hits, speed is king
Google
OpenLogic Company Confidential 7
Wednesday, July 29, 2009
9. Reasons for Caching
Lots of hits, speed is king
Google
Complex hits, richness is king
OpenLogic’s OLEX
OpenLogic Company Confidential 9
Wednesday, July 29, 2009
11. Rails Caching Background
Page caching
Uberfast, little control
Highly dynamic site? Move on.
Action caching
Fast, lots of control
Expiration hell
Fragment caching
Expiration hell
OpenLogic Company Confidential 11
Wednesday, July 29, 2009
12. 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.
OpenLogic Company Confidential 12
Wednesday, July 29, 2009
13. Automatic Generation-Based Caching
Generation-based
Partition the cache so that every state change increments the
“generation”
Automatic
Any action that could possibly change state bumps the count
Let memcached overwrite old generations
Very conservative
No domain knowledge
Don’t cache errors, AJAX, redirects, flash notices, etc.
OpenLogic Company Confidential 13
Wednesday, July 29, 2009
14. Overview
Non-cached Cached
Rails Rails
plumbing plumbing
Your Cache
code helper
Render
Database memcached
View
OpenLogic Company Confidential 14
Wednesday, July 29, 2009
15. Cache = Hash
Key Value
/packages/1 <html><body>Rails</body></html>
/packages/2 <html><body>Ruby</body></html>
OpenLogic Company Confidential
Wednesday, July 29, 2009
16. Generational Cache
Key Value
/gen/1/packages/1 <html>Rails</html>
/gen/1/packages/2 <html>Ruby</html>
OpenLogic Company Confidential
Wednesday, July 29, 2009
17. Generational Cache
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>
OpenLogic Company Confidential
Wednesday, July 29, 2009
18. memcached is your friend
Very fast
Don’t worry about removing old entries
memcached will automatically drop the oldest keys
when it runs low on memory
Run it on your web servers if you have the RAM
OpenLogic Company Confidential 18
Wednesday, July 29, 2009
19. Cache Helper
No cache-related code sprinkled throughout every
model, view, and/or controller
Use a global “around” filter
Automatically increment (scoped) generation count
upon POST, PUT, or DELETE
Handle event recording and playback (optional)
OpenLogic Company Confidential 19
Wednesday, July 29, 2009
20. 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)
end
end
OpenLogic Company Confidential 20
Wednesday, July 29, 2009
21. Cache Key
def make_auto_cache_key(key)
ol_gen = Rails.cache.fetch(OL_GEN) { "1" }
"olex/#{ol_gen}"
end
OpenLogic Company Confidential 21
Wednesday, July 29, 2009
22. “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
OpenLogic Company Confidential 22
Wednesday, July 29, 2009
23. 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
OpenLogic Company Confidential 23
Wednesday, July 29, 2009
24. 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 caches
Use a cache hierarchy
/olex/<gen #>/corp/<corp #>/<corp gen #>/roles/<role #s>/URL
MD5 the key (memcached has a 250 char limit)
Extra credit: Use the key as an etag
OpenLogic Company Confidential 24
Wednesday, July 29, 2009
25. Partitioned Cache
Global generation
<html>
/olex/13/corp/72/2/roles/2,7,19/packages/2 Rails (unsupported)
</html>
OpenLogic Company Confidential
Wednesday, July 29, 2009
26. Partitioned Cache
Corporate account ID
<html>
/olex/13/corp/72/2/roles/2,7,19/packages/2 Rails (unsupported)
</html>
OpenLogic Company Confidential
Wednesday, July 29, 2009
27. Partitioned Cache
Corporation #72’s generation
<html>
/olex/13/corp/72/2/roles/2,7,19/packages/2 Rails (unsupported)
</html>
OpenLogic Company Confidential
Wednesday, July 29, 2009
28. Partitioned Cache
Current user’s role IDs
<html>
/olex/13/corp/72/2/roles/2,7,19/packages/2 Rails (unsupported)
</html>
OpenLogic Company Confidential
Wednesday, July 29, 2009
29. Partitioned Cache
URL <html>
/olex/13/corp/72/2/roles/2,7,19/packages/2 Rails (unsupported)
</html>
OpenLogic Company Confidential
Wednesday, July 29, 2009
31. 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)
...
OpenLogic Company Confidential 31
Wednesday, July 29, 2009
32. 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
OpenLogic Company Confidential 32
Wednesday, July 29, 2009
33. 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
end
end
OpenLogic Company Confidential 33
Wednesday, July 29, 2009
34. Benefits
Never have to explicitly expire anything
Can’t get an expired page
Generation numbers also stored in memcached
Cache hierarchy is like a directory structure
Easy to grok
Can’t run out of “disk space”!
OpenLogic Company Confidential 34
Wednesday, July 29, 2009
35. Does it really work?
In production for over a year - a few minor issues
See 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 forever
Lots of room for more aggressive caching
OpenLogic Company Confidential 35
Wednesday, July 29, 2009
36. Gotchas
Easy to forget to make AJAX calls use REST
method:'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
OpenLogic Company Confidential 36
Wednesday, July 29, 2009
37. Recommendations
The “safety first” caching implementation - use it
It’s still action caching, so don’t expect page caching
performance
Measure the result - not all pages will benefit and there
is some overhead
OpenLogic Company Confidential 37
Wednesday, July 29, 2009
38. Bonus
Asynchronous pre-caching
Event recording and playback
OpenLogic Company Confidential 38
Wednesday, July 29, 2009
39. Async pre-caching pages at logon
Use Workling/Starling for async
When user logs on, make async call
Async call runs through list of URL’s and hits them on
behalf of the currently logged on user
Need secret back-door logon in application.rb
Use big scary session key in environment.rb
OpenLogic Company Confidential 39
Wednesday, July 29, 2009
40. 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
OpenLogic Company Confidential 40
Wednesday, July 29, 2009
41. Summary
Rails built-in caching takes
a lot of on-going work
Easy to screw it up
Automatic caching can be written
once and enjoyed forever
Much harder to screw up
Still possible to use more aggressive
techniques selectively
OpenLogic Company Confidential 41
Wednesday, July 29, 2009
42. Any questions for Rod?
OpenLogic Company Confidential 42
Wednesday, July 29, 2009
43. Resources
memcached: http://www.danga.com/memcached/
Workling: http://github.com/purzelrakete/workling/tree/
master
Starling: http://rubyforge.org/projects/starling/
OLEX: http://olex.openlogic.com
OpenLogic Company Confidential 43
Wednesday, July 29, 2009
44. Credits
Unless otherwise indicated, photos were licensed from
BigStockPhoto.com
OpenLogic Company Confidential 44
Wednesday, July 29, 2009
45. OpenLogic is hiring!
Rails guru?
Live near Denver, Colorado?
Love Open Source?
Come see me!
OpenLogic Company Confidential 45
Wednesday, July 29, 2009