3

I have an application that uses caches_page for certain controllers/actions. To expire the cache, I use a sweeper. All in all, it's a standard solution.

However, because some changes may cause a bit of a rush of requests on the server (because push notifications are sent out and may trigger client devices to fetch new data), I'd like to be able to pre-render the cache, so it's ready before the requests roll in. I could just wait for the first request to automatically write the cache, of course, but in this case, I know that the requests will come, that there might be many, and that they may be near-simultaneous. So I'd like to have the cache ready.

To add some complexity, the updates are done via a normal web page and handled in a standard, mostly scaffolded controller, while the "page" I want to cache is the JSON response for an entirely different controller that serves as an API.

So, how do I, from a sweeper (or from the controller handling the cache-expiring update) trigger a new page cache to be written immediately?

Another way to put it might be: How do I make an internal request from one controller to another?


Edit: Ended up doing something like what you see below. It's not terribly elegant, but it is effective

class ModelSweeper < ActionController::Caching::Sweeper
  observe Model

  def after_create(model)
    expire_pages_for(model)
  end

  def after_update(model)
    expire_pages_for(model)
  end

  def after_destroy(model)
    expire_pages_for(model)
  end

  protected

    def expire_pages_for(model)
      # expire index page
      expire_and_bake(models_url)

      # expire show page
      expire_and_bake(model_url(model))
    end

    def expire_and_bake(url)
      # extract the path from the URL
      path = url.sub(%r{\Ahttp://[^/]+}, "")

      # expire the cache
      expire_page(path)

      # request the url (writes a new cache)
      system "curl '#{url}' &> /dev/null &"
    end

end
Flambino
  • 18,507
  • 2
  • 39
  • 58

1 Answers1

2

Warming a server's cache may fall outside of the realm of your application logic. I have implemented a cache warming system before using a rake task that wrapped the curl command and looped through all the areas in the website.

# lib/tasks/curl.rake
desc "curl"
task :curl do
  paths.each do |path|  
  `curl #{path}`
  end
end

You can call this task by issuing "rake curl" from inside your Rails project root.

Alternately, you could invoke this rake task (which wraps curl) from inside your sweeper method after you expire the cache. Check out the Railscast Ryan Bates did on invoking rake tasks in the background from inside your Rails application code: http://railscasts.com/episodes/127-rake-in-background

More information on curl here: http://curl.haxx.se/docs/manpage.html

Ben Simpson
  • 4,009
  • 1
  • 18
  • 10
  • In this case, it's more a question of the app being a "static file generator", so I'd say it's app logic (just not traditional JIT caching). It's only a few specific pages I'd like to "bake" - as opposed to generally warming the cache. Plus, the push notification causes, 1) a record to update, 2) expires (several) caches, 3) triggers a bunch of clients to simultaneously request the url that was just expired potentially causing redundant cache misses/writes due to race conditions, and generally bogging the server down by running the full app stack. – Flambino Feb 24 '12 at 02:43
  • Heh, after all my excuses in the earlier comment for _not_ following your suggestion, I ended up with something similar anyway (updated my question with details). I still keep the logic in the app, though. You're getting the checkmark since the Railscasts video gave me a bunch of ideas - I thought I knew all his videos, but that one had slipped my mind. Thanks for the link :) – Flambino Feb 24 '12 at 23:45