Rails Illustrated

Rails, Web Design and the User Experience

Blazing Fast Speeds with Sinatra and Memcached

Sinatra is a wonderful small Ruby web framework built on top of the Rack interface. Sinatra is very fast out of the box making it perfect for light weight tasks. Sinatra does not come with support for memcached out of the box but adding support for memcached is fairly straightforward.

Requirements

  1. Download the file cache.rb.

  2. You will need to have installed memcached and also the memcache-client gem:

sudo gem install memcache-client
 

Installation

At the top of your Sinatra application should be

require 'sinatra'
require 'cache'    

CONFIG['memcached'] = 'localhost:11211'

# add caching to Sinatra
class Sinatra::Event
  include CacheableEvent
end

Usage

A standard Sinatra controller method looks like this:

get '/:name' do
    ...
    erb 'name'
end

To add caching change to:

get '/:name', :cache_key => Name.cache_key do
    ...
    erb 'name'
end

You will want Name.cache_key to return a string and to change whenever the content returned by name.html.erb view will change. You do not have to invalidate any caches directly. Furthermore the computed cache_key will automatically include details of the parameters of the request.

If you want to cache anything else inside your application use:

result = Sinatra::Cache.cache(cache_key) do
    this_is_an_expensive_method
end

How it Works

There are essentially two modification that must be made. Override the way Sinatra stores blocks that make up the controller methods and also to override the way Sinatra calls those stored blocks.

Overriding the block storage

When you declare a controller method in your application file like

get '/:name' do
    ...
    erb 'name'
end

Sinatra stores the block provided and saves it to be executed when responding to a request. The caching mechanism replaces this block with another block. This replacement block uses the caching mechanism. The cache_key and original block are saved by means of Proc.new which creates a closure and allows the block to access these values at the time of execution.

def _invoke_with_caching(*args)
  if options[:cache_key]
    # replace the block with another block that can be cached
    def wrap_block(key,block)
      Proc.new do
        Sinatra::Cache.cache(key + "/" + params.to_a.join("/")) { instance_eval(&block) }
      end
    end
    @block = wrap_block(options[:cache_key], block)
  end
  _invoke_without_caching(*args)
end

How fast is it?

This depends on how long it takes do generate your cache_key. There is also additional overhead in accessing memcached. In my case it usually takes about 15 milliseconds to respond from the cache.

It doesn't matter

However it doesn't matter that much. You web application speed usually doesn't depend on how fast your application responds, especially when the response time is less than 150 milliseconds. To understand why see this explanation of where your application is really slow.

Comments  

1

am I missing a trick or is there no such class as Sinatra::Event ?

Phil Wilson wrote on April 22 2009
2

Phil The cache.rb file contains modifications for the Sinatra::Event. When CacheableEvent is included it modifies the way Sinatra stores the blocks that respond to the actions.

erik wrote on April 24 2009
3

About the Sinatra::Event - I'm running into this error and not really sure how to get past it: ./cache.rb:30:in alias_method': undefined methodinvoke' for class Sinatra::Event' (NameError)

Any ideas?

Alex wrote on May 6 2009
4

I'm having the same issue as erik.

Kristoffer wrote on July 25 2009
5

I'm having the same issue as erik, TOO

xdanger wrote on November 25 2009
6

Guess what? I have some issue :)

Andoriyu wrote on January 19 2010
7

I updated this concept to work with Sinatra 0.9.6: http://gist.github.com/345277

Ben Tucker wrote on March 26 2010
8

I'm new to Ruby and Sinatra (down with you, Django!) and I was wondering if you or anyone else could explain the advantages of using this over Rack::Cache configured to make use of memcached as its store.

Does this just allow for a finer granularity of control over what gets cached instead of a being just a generic HTTP cache?

Ben Yip wrote on April 9 2010
9

After reading a few articles referenced Tomayko, I realize how off I was in understanding caching.

Please correct me if I'm wrong, but Rack::Cache is pure HTTP Caching, as in, it relies solely on HTTP headers.

This is application level caching where, regardless of the HTTP header, if there's a cache hit, bam, you serve whatever's in your cache.

That sound about right?

Ben Yip wrote on April 9 2010
10

Same issue as Alex. And Ben's gist is for Sinatra < 1.0, so it doesn't help me either.

pjm wrote on September 29 2011

Add Comment

(required)
(required, won't be displayed)

(Use Markdown syntax)