On Building

:

The War on ActionView
with Russian Doll Caching
Peter Lombardo
http://blog.gameface.in

http://www.appneta.com/blog
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.
“Gameface was created to
showcase games,
the people who play them,
and the characters they play”
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
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/
ActionView is Slow;
Pitfalls Ahead
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.
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/
In terms of performance, ActionView
is a weapon of mass destruction and
is the low-hanging fruit for
improvement.
Russian Doll
Caching
Russian Doll caching is a type of
nested fragment caching that auto
expires fragments when object
timestamps change.
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)
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)
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
Cache Friendly
Page Layouts
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.
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.
Properly caching this page as-is would
be complicated and inefficient since we
would have to tip-toe around logged-in
content.
The Redesign
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.
The Results
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.
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.
Summary
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.
Additional
Considerations
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
Cheatsheet
Separate out logged-in content from
agnostic content for best cache
coverage. Page design affects caching
efficiency.
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)
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)
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.
End

The War on ActionView with Russian Doll Caching

  • 1.
    On Building : The Waron ActionView with Russian Doll Caching Peter Lombardo http://blog.gameface.in http://www.appneta.com/blog
  • 2.
    Peter Lombardo I’ve beena 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 createdto showcase games, the people who play them, and the characters they play”
  • 5.
    Rails 4 isout 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 aregreat. 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/
  • 7.
  • 8.
    ActionView puts fortha 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.
  • 10.
    The previous screenshotshows 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/
  • 12.
    In terms ofperformance, ActionView is a weapon of mass destruction and is the low-hanging fruit for improvement.
  • 13.
  • 14.
    Russian Doll cachingis a type of nested fragment caching that auto expires fragments when object timestamps change.
  • 15.
    You still makethe 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 Dollcaching (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 requirementto 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
  • 18.
  • 19.
    When caching, it’sbest 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 (andless 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.
  • 22.
    Properly caching thispage as-is would be complicated and inefficient since we would have to tip-toe around logged-in content.
  • 23.
  • 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.
  • 26.
  • 27.
    With the pagere-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.
  • 29.
    On top ofthe 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.
  • 30.
  • 31.
    Russian Doll cachingis 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.
  • 32.
  • 33.
    When using fragmentcaching, 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
  • 34.
  • 35.
    Separate out logged-incontent from agnostic content for best cache coverage. Page design affects caching efficiency.
  • 36.
    When possible, alwayscall 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 anindex 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 modelrelationships 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.
  • 39.