March 03, 2020
Application performance is always a concern when building in the modern, competitive web and mobile space. At Scout, it’s why we created application performance monitoring tools in the first place. That said, there are steps you can take to build a more performant application.
If you are using Ruby on Rails, caching might be one of the best tools on your belt to build a better application. The idea behind caching in Rails is to serve thousands of concurrent users on a single server with a single database attached. Let’s take a look at some of the benefits caching can provide.
Use the links below to jump ahead:
Caching is an integral part of performance management when it comes to application development. Regardless of what language you develop with, caching can help improve performance both on the back end and within the user's experience.
Depending on the method of caching used, Rails can store cached information in different places. For anyone using Rails 2.1 or higher, ActiveSupport::Cache::Store is mainly used for strings while other areas, like MemoryStore, can cache other objects. While this is helpful, MemoryStore isn’t thread-safe, so there is some risk involved.
Let’s cover some of the different kinds of Rails cache stores and their applications: Page and Action caching (pre-Rails 4), Fragment caching, and Russian Doll caching.
Page caching is the most straightforward caching techniques. With page caching, Rails delivers the desired page or content without going through the entire Rails stack. All requests for the desired resource are delivered by the webserver, generally NGINX or Apache, depending on how your application is deployed. While the performance improvement is huge in comparison to the other caching methods, Page caching is not a universal solution. Part of the reason for this is it is an older caching method that has been removed in Rails 4. Additionally, it is only appropriate for non-authenticated full pages, meaning it’s less likely useful in an application with many partials or the need for moderate to heavy security.
Action caching is great for anything you may need cached that has a before filter placed on it, like features that require authentication. Similar to Page caching, the big difference is the Rails stack being hit to ensure any actions needing to take place before the cached copy is hit can be performed. This increases performance while taking into account the functionality required to access parts of our application.
In Rails 4, both Page caching and Action caching have been removed and gemified (actionpack-page_caching and actionpack-action_caching gems, respectively).
For those who are using Rails 4, Fragment caching can help take advantage of the dynamic features of your application. Fragment caching allows you to caches portions of a page instead of the entire page.
By wrapping portions of view logic in a cache block, these pieces can be served from the cached store instead of serving each request for the resource. For example:
<% @books.each do |book| %> <% cache product do %> <%= render book %> <% end %> <% end %>
Unlike other forms of caching, fragment caches will expire after a certain point in time. Note the render helper in the bit of code. This is part of Collection caching, which caches for templates rendered as part of a collection. This can be iterated on to improve the performance of large scale objects that need to be cached.
Nested fragments of code may be better served by Russian Doll caching. The significant benefit here is, unlike Fragment caching, which updates or handles one piece of the set, Russian Doll caching will act as the parent and any child portions that may also be able to take advantage of caching.
For example, books have chapters. If you need to cache the book, Russian Doll caching would also cache the chapters.
<% cache book do %> <%= render book.chapters %> <% end %>
Would render this view:
<% cache chapter do %> <%= render chapter %> <% end %>
This is made possible by using the touch method in the model
class Book < ApplicationRecord has_many :chapters end class Chapter < ApplicationRecord belongs_to :book, touch: true end
Other forms of caching are available, and each has a significant performance benefit for your application.
Rails 5 brings in the ability to take advantage of Memcached directly in your application’s code. If you aren’t using Rails 5 yet, most of this functionality is approachable through other parts of the environment, like Redis or Rack:Memcache. The use of Memcached, when applied correctly, can boost performance.
Assuming you are using Rails defaults when creating your application, :file_store is used to write any cache information into the file system. This can be overridden if you desire; otherwise, everything will be sent to tmp/cache.
:file_store is a useful default feature in your application because it caches each entry. This means it’s possible to take something out of the cache by simply removing its file. It’s also perfect for large caches that have the potential to grow large at a rapid pace.
With Rails 5, :memory_store automatically generates when starting a new application Cached data is held in memory in whatever Ruby web server your application is using. This is an advantage for the development environment because any cache can be cleared by restarting the server. In production, a separate method may be necessary to clear the cache. That aside, :memory_store is one of the best default features Rails has to offer when it comes to caching and improving performance in your application.
At some point, it will be best to clear any cache stores you may have created. This can be done via a regular rake task.
namespace :cache do task :clear do FileUtils.rm_rf(Dir[‘tmp/cache/[^.]*’) end end
The quick and dirty method to clear is:
Rails caching is a vital tool to improve performance. As a Rails application grows, performance will become a greater and more significant issue. Using some of the caching techniques we’ve included here can help to find the behaviors that will help improve your application and your users’ overall experience.