Ruby on Rails 4 is out featuring Russian Doll caching (AKA Cache Digests). In this article, I apply Russian Doll caching to one of my poorer performing Rails 3 pages using the cache_digests gem.
The Codex of Business Writing Software for Real-World Solutions 2.pptx
Improve Rails App Performance 67% with Russian Doll Caching
1. On Building
:
The War on ActionView
with Russian Doll Caching
Peter Lombardo
http://blog.gameface.in
http://www.appneta.com/blog
2. Peter Lombardo
I’ve been a Rubyist and Linux addict for over a decade, mainly
developing in Ruby and JavaScript (as well as PHP and Perl). I’m the
creator of numerous software tools and applications, most recently
Password Pusher, a Ruby on Rails application to communicate
passwords over the web and Gameface, a social networking
platform for gamers using Rails 3.
When I’m not creating my own applications, I help develop and
maintain the infrastructure at OurStage and manage the Ruby
community for AppNeta.
3. “Gameface was created to
showcase games,
the people who play them,
and the characters they play”
4.
5. Rails 4 is out featuring Russian Doll
caching (AKA Cache Digests). In this
article, I apply Russian Doll caching
to one of my poorer performing Rails
3 pages using the cache_digests
gem.
This article was originally featured
on AppNeta’s Application
Performance Blog. Check it out!
https://github.com/rails/cache_digests
6. ActionView templates are great. They are easy
to code, manage and extend but the one thing
they are not is fast…at least not out of the box.
In this article, I’ll be using AppNeta’s
TraceView to time ActionView performance. If
you haven’t used TraceView before, checkout
my previous article Instrumenting Ruby on
Rails with TraceView.
http://www.appneta.com/products/traceview/
8. ActionView puts forth a great
development pattern of layouts,
views and partials that is easy
to understand, implement and
maintain but that comes at a
cost: The rendering process is
complex and slow.
9.
10. The previous screenshot shows the timings for
the Users#show URL on Gameface. The page
in question is fairly straight forward containing
four components: a topbar, sidebar, user
details and a listing of game characters.
With no caching at all, the ActionView layer
averages roughly ~365ms for this URL. This
represents over 80% of average request
processing time and dwarfs all of the other
layers combined.
http://gameface.in/
11.
12. In terms of performance, ActionView
is a weapon of mass destruction and
is the low-hanging fruit for
improvement.
14. Russian Doll caching is a type of
nested fragment caching that auto
expires fragments when object
timestamps change.
15. You still make the same calls as previous
fragment caching schemes in Rails:
- cache @user do
(user view data)
- cache [ 'details', @user ] do
(user details view data)
- cache [ 'characters', @user ] do
- @user.characters.each do |character|
- cache character do
(character view data)
16. With Russian Doll caching (AKA cache
digests) unique cache keys are formed
using an md5 stamp based on the
timestamp of the object being cached:
views/users/320130530135425/7a1bb8bb15b02ee7aa69cec1d5f6f630
views/details/users/320130530135425/6f28ec6d31e7e3b73a575777d59e63ca
The advantage of this is that when objects
are updated and outer fragments are
automatically invalidated, nested fragments
can be re-used. (russian dolls)
17. A key requirement to this is that children
objects should update the timestamps of their
parent object by using ActiveRecord touch
option. This will allow for automatic cache
invalidation and avoid serving stale content.
class Character < ActiveRecord::Base
belongs_to :user, touch: true
end
19. When caching, it’s best to avoid caching
logged-in content since the same caches
will get served to all users regardless of
any logged in status.
20. For effective (and less problematic)
fragment caching, designing the page
well to separate out logged in content is
critical.
Below is the layout for the Gameface
profile view before re-design. Logged in
specific content and links are sprinkled
throughout the page making it hard to
divide it up into cache-able fragments.
21.
22. Properly caching this page as-is would
be complicated and inefficient since we
would have to tip-toe around logged-in
content.
24. To fix this, I re-organized the page to
group the logged-in specific content into
one specific area. With logged-in content
out of the way, we are then free to cache
the rest of the page in well-defined
fragments.
27. With the page re-design and Russian
Doll fragment caching applied to the
large majority of the page, we now
average a much better ~120ms for
ActionView on that URL. A reduction of
265ms (67%) in average processing
time.
28.
29. On top of the performance improvement,
we also get automatic cache invalidation
as object timestamps are updated. This
greatly simplifies the whole system by not
requiring cache sweepers or other tricks
to invalidate stale caches.
31. Russian Doll caching is a small but significant
improvement over prior caching in Rails. When
used effectively, it can greatly reduce server
side ActionView processing and automatically
expire stale fragments.
We took a previously un-cached URL with a
poor page layout that was averaging ~365ms
of processing in ActionView and reduced that
number to ~120ms for a 67% performance
improvement.
33. When using fragment caching, note which
backing cache store you are using. The default
cache store in Rails is the filesystem but you
can get even greater performance by backing
your cache store with memcache or redis
instead.
See the redis-rails gem as an example.
https://github.com/redis-store/redis-store/tree/master/redis-rails
35. Separate out logged-in content from
agnostic content for best cache
coverage. Page design affects caching
efficiency.
36. When possible, always call cache with
the object being cached. Cache keys and
their eventual invalidation will be keyed
off of the timestamp on the object being
cached.
- cache @user do
(user view data)
- cache [ 'details', @user ] do
(user details view data)
37. To cache an index of objects, you have to
revert to manually passing in the
expires_in option since there is no single
object timestamp to key off of.
- cache 'user list', expires_in: 30.minutes do
(user list view data)
38. Update belongs_to model relationships with
touch: true so that parent fragment caches
can be invalidated when children are updated.
Collect timing data before and after to quantify
and validate changes.