Adding Page Caching to Sitemaps

The shortest distance between two points is not a straight line. The shortest distance is zero. Admittedly, we're not about to fold space or do time travel, but you get the idea.

Likewise, for any web framework, the fastest way to deliver content to clients is to not use the framework at all, but let the webserver serve static files to clients.

In Rails this means using page caching and leave the heavy lifting to the webserver. Keeping your cache fresh and still maintaining stellar performance might seem like a daunting task, but Rails gives you quite a bit of help, so it is actually not that hard to do.

In this post I will take you through the steps to add page caching to our Sitemap, that we created in Creating Sitemaps with Comatose CMS

Enable page caching for our Sitemap

As you can see below, enabling page caching is as simple as just adding one extra line in a controller

## app/controllers/info_controller.rb
class InfoController < ApplicationController
    caches_page :sitemap
    def sitemap
        respond_to do |format|
      format.xml do
                @website_url = 'http://example.com'
                @pages = ComatosePage.find(:all, :conditions => "full_path not like ''")
                render :action => 'sitemap', :layout => false
            end
        end
    end
end

Verify caching

To verify that we are have succesfully enabled page cache, let's update the development environment to mimick the production environment.

## config/environments/development.rb
config.action_controller.perform_caching = true

Then we need to restart the server, do a request to http://localhost:3000/sitemap.xml and you should see something like the following in your log.

Processing InfoController#sitemap (for 127.0.0.1 at 2009-03-17 17:04:26) [GET]
  Parameters: {"action"=>"sitemap", "controller"=>"info"}
  SQL (0.2ms)   SET NAMES 'utf8'
  SQL (0.1ms)   SET SQL_AUTO_IS_NULL=0
  ComatosePage Load (1.5ms)   SELECT * FROM `comatose_pages` WHERE full_path not like ''
Rendering info/sitemap
  ComatosePage Columns (2.0ms)   SHOW FIELDS FROM `comatose_pages`
Cached page: /sitemap.xml (0.7ms)
Completed in 16ms (View: 9, DB: 4) | 200 OK [http://localhost/sitemap.xml]

The line we're looking for is Cached page: /sitemap.xml (0.7ms)

In your public folder you should also see a sitemap.xml file.

If everything is configured correctly, subsequent requests to /sitemap.xml should not show up in the applications log, as the webserver is now serving the static file and not using Rails at all :-)

Add a cache sweeper

Now that we can succesfully cache the sitemap, we need to make sure it stays fresh, when content in Comatose is updated.

Rails provides us with Cache Sweepers, which is kind of like an observer, but specialised. Let's create a cache sweeper that observes ComatosePage, and deletes the cached sitemap.xml when pages are saved or destroyed.

## app/sweepers/comatose_sweeper.rb
class ComatoseSweeper < ActionController::Caching::Sweeper
    observe ComatosePage
  
    def after_save(page)
        expire_cache(page)
    end
  
    def after_destroy(page)
        expire_cache(page)
    end
  
    def expire_cache(page)
        # Use the ApplicationController, as were outside the scope 
        # of the executing controller      
        ApplicationController.expire_page( "/sitemap.xml" )
  end
end

Finally, we need to tell Comatose to use the sweeper.

## config/initializers/comatose.rb
# set a cache sweeper to react when comatose expires pages from it's page caching
ComatoseAdminController.send :cache_sweeper, 
    :comatose_sweeper, :only => 'expire_cms_page'

Verify cache sweeper

To verify that your cache sweeper is working, all you need to is to is to save a page in Comatose and then verify the absense of sitemap.xml from the public folder.

Finally

That's it! You know have a Comatose based Sitemap that will only be generated when there are changes.

P.S. Don't forget to set your development environment to NOT use caching anymore.

Resources