Getting rid of .DS_Store files in your git repository

One thing that really bugs me is seeing a repository full of .DS_Store files. I am an offender my...

Caching dynamic partials when using Turbolinks

Yesterday I blogged about caching and loading dynamic partials via ajax for non static content. T...

2
Wednesday, November 28, 2012
Rails 3 full page caching on Heroku

Recently I implemented full page caching on this blog. Not because of the insane amount of traffic I've been receiving (10 hits a day) but because it was something I wanted to experiment with and I also wanted to squeeze some more speed out of this CMS.

Using Turbolinks the pages were loading fast but now with the full page caching clicking on a link instantly shows the new page. You don't even have time to blink!

Page Caching

So I went about my way implementing Rails page caching which basically caches the entire page including the layout to a static html file in your public directory. This has the advantage of not hitting the rails stack and it's called directly from the web server (nginx/apache). There are a few gotchas when implementing page caching in rails. First you can't really have anything dynamic which for this site doesn't seem like to much of a problem at first, although when you update a post or someone leaves a comment the web server continues to serve up the static html file leaving you scratching your head.

Rails has some awesome utilities to get around this. There is the expire_page method which allows you to pass in a route to remove the static file. This is useful after updating a post or when someone leaves a comment. At first I created a helper method to clear the cache until I came across Rail's cache sweepers. These allow you to observe a model and run methods after save, update and destroy. So I moved all of my caching logic into the sweeper so that whenever a post is updated the cache is cleared for the post and any other page that contains post data (home page, archives, etc.).

Here is the sweeper I originally used:

class PageSweeper < ActionController::Caching::Sweeper
  observe Page

  def sweep(page)
    # Page Caching
    expire_page pages_path
    expire_page page_path(page.slug)
    expire_page '/'
    expire_page archives_path
    FileUtils.rm_rf "#{page_cache_directory}/pages"
  end

  alias_method :after_create, :sweep
  alias_method :after_update, :sweep
  alias_method :after_destroy, :sweep
end

So everything is running quick, Rails doesn't have to serve up pages anymore but wait... unfortunately Heroku doesn't support page caching.

Heroku has an ephemeral file store, so while page caching may appear to work, it won’t work as intended.
https://devcenter.heroku.com/articles/caching-strategies#page-caching

I didn't realize this until after checking out the logs and doing a bit of research. After reading their caching strategies page I decided to use action caching.

Action Caching

Action caching works similarly to page caching although it still hits the Rails stack. There are some advantages to using the action cache over the page cache such as being able to access your before filters to determine if a user's logged in or whatever you usually do in the before filter.

Action cache uses in memory stores (memcache usually) to cache the html of the page. You can opt-out of caching the layout if you want to keep certain things dynamic such as if a user is logged in and just cache the html of the action itself.

After implementing the action cache I really couldn't tell the difference between it and page caching. It was still super fast and made clicking on links seem like they were static html files. Action caching uses an almost identical API to page caching with a few small differences.

I decided to keep both page and action caching in case one day I move away from Heroku to a host with a standard file system. I ended up creating some configuration to determine whether to use page or action caching.

In the pages controller:

caches_page :index, :show, :archives if Blog.cache == :page
caches_action :index, :show, :archives if Blog.cache == :action

And I updated my sweeper method to clear the cache based on the configuration:

class PageSweeper < ActionController::Caching::Sweeper
  observe Page

  def sweep(page)

    if Blog.cache == :page

      # Page Caching
      expire_page pages_path
      expire_page page_path(page.slug)
      expire_page '/'
      expire_page archives_path
      FileUtils.rm_rf "#{page_cache_directory}/pages"

    elsif Blog.cache == :action

      # Action Caching
      expire_action controller: '/pages', action: :index
      expire_action "#{request.host}/#{page.slug}"
      expire_action controller: '/pages', action: :archives

      # Remove pages cache
      pages = (Page.published_posts.count.to_f / Blog::POSTS_PER_PAGE.to_f).ceil
      1.upto(pages) { |p| expire_action "#{request.host}/pages/#{p}" }

    end

  end

  alias_method :after_create, :sweep
  alias_method :after_update, :sweep
  alias_method :after_destroy, :sweep
end

Loading dynamic partials

Since originally I was using full page caching I decided to load certain parts of the page via ajax and partials. For the most part this just checks if the admin is logged in and will show extra links in the navigation and options to moderate comments. I am also loading in the recent posts in the side bar via ajax so that I don't have to clear every page from the cache when adding a new post.

I will write another post on some of the cool techniques I used to only load the partials once via ajax per viewing session, since Turbolinks doesn't do full page refreshes.

More reading

Tweet this post if you found it useful

Related Posts

Open sourcing this blog. Code on GitHub

Monday, November 26, 2012  ·   0

I've tidied up the code for this site and decided to open source it if anyone is ... (continued)

Caching dynamic partials when using Turbolinks

Friday, November 30, 2012  ·   0

Yesterday I blogged about caching and loading dynamic partials via ajax for non stati... (continued)

Setting up Phalcon on Heroku

Thursday, December 6, 2012  ·   0

Yesterday I blogged about Phalcon, the PHP framework written in C. At the end of the ... (continued)

Using Polytalk inside a Meteor Application

Sunday, December 9, 2012  ·   0

I've put together a simple demo that integrates Polytalk with Meteor. The app all... (continued)

2 Comments

Comment successfully added. View comment.×

Comments are now closed for this article.

Gravatar
David · November 29, 2012 07:40AM

Nice article. Only comment I would have is to perhaps move the Blog.cache setting into settings.yml or a specific blog_settings.yml configuration file, as it seems to be more of a project wide thing and not necessarily related to the model logic.

Gravatar
Andrew Weir · November 29, 2012 01:34PM

@David thanks for the comment. Blog is actually a module inside of the initializers directory where I currently store my settings. I agree that a yaml file would be more ideal though.

https://github.com/andruu/blog-engine/blob/master/config/initializers/blog.rb