Friday, September 25, 2009

Rack Caching: No Longer Just a Proof of Concept

‹prev | My Chain | next›

Tonight I would like to implement and deploy the Rack:Cache support that I have been prototyping the past few days. Since I have been prototyping, my first step is to delete all of the non-commited work.

Next, I need to write a spec that describes the behavior that I desire. Consider a Sinatra action that renders cookbook meals similar to:
get %r{/meals/(\d+)/(\d+)/(\d+)} do |year, month, day|
data = RestClient.get "#{@@db}/#{year}-#{month}-#{day}"
@meal = JSON.parse(data)

url = "#{@@db}/_design/meals/_view/by_date_short"
data = RestClient.get url
@meals_by_date = JSON.parse(data)['rows']

@recipes = @meal['menu'].map { |m| wiki_recipe(m) }.compact

@url = request.url

haml :meal
end
If the client (or Rack::Cache) supplies an If-None-Match value that matches the current CouchDB document requested on the first line in that action, then I would like to halt, by-passing any remaining processing. In such a case, RestClient.get would be invoked only once (on the first line). Expressing this in RSpec format:
  context "cached documents" do
describe 'GET /meals/2009/09/25' do
it "should etag with the CouchDB document's revision" do
RestClient.
should_receive(:get).
once.
and_return('{"_rev":1234}')

get "/meals/2009/09/25", { }, { 'HTTP_IF_NONE_MATCH' => '"1234"' }
end
end
end
When I execute that example, I get a failure because the meal document (the JSON described by and_return) is insufficient to describe a meal as needed in the Haml template:
1)
NoMethodError in 'eee cached documents GET /meals/2009/09/25 should etag with the CouchDB document's revision'
undefined method `map' for nil:NilClass
./eee.rb:85:in `GET (?-mix:\/meals\/(\d+)\/(\d+)\/(\d+))'
I get the example to pass by adding a Sinatra etag call to the action:
get %r{/meals/(\d+)/(\d+)/(\d+)} do |year, month, day|
data = RestClient.get "#{@@db}/#{year}-#{month}-#{day}"
@meal = JSON.parse(data)
etag(@meal['_rev'])

url = "#{@@db}/_design/meals/_view/by_date_short"
data = RestClient.get url
@meals_by_date = JSON.parse(data)['rows']

@recipes = @meal['menu'].map { |m| wiki_recipe(m) }.compact

@url = request.url

haml :meal
end
Since that passes, I know that RestClient is only called once. So the upstream (e.g. Rack::Cache) cached copy will be use instead of regenerating everything again.

After following the same procedure with the cookbook's recipes action, I am done implementing (implementation was much less than the learning that I needed). So I am ready to deploy.

My Rack configuration file has Rack::Cache configured to store its cache in /var/cache/rack. After ensuring that the Rack::Cache gem is installed on my beta site and that the /var/cache/rack directory exists and is writable by the Thin server's owner, I am ready to deploy:
jaynestown% rake vlad:stop_app
jaynestown% rake vlad:update vlad:migrate vlad:start_app
To verify that caching is working I tail my CouchDB logs on the beta site as I access a mini-chocolate cupcakes recipe. The first time I access the recipe, I see the request for the recipe document itself, the recipe photo (which is stored in CouchDB), and several views:
[Sat, 26 Sep 2009 03:30:08 GMT] [info] [<0.14733.137>] 127.0.0.1 - - 'GET' /eee/2008-07-10-choccupcake 200
[Sat, 26 Sep 2009 03:30:08 GMT] [info] [<0.14800.137>] 127.0.0.1 - - 'GET' /eee/_design/recipes/_view/by_date_short 200
[Sat, 26 Sep 2009 03:30:09 GMT] [info] [<0.14801.137>] 127.0.0.1 - - 'GET' /eee/_design/recipes/_view/updated_by?key=%222008-07-10-choccupcake%22 200
[Sat, 26 Sep 2009 03:30:09 GMT] [info] [<0.14802.137>] 127.0.0.1 - - 'GET' /eee/_design/recipes/_view/update_of?key=%222008-07-10-choccupcake%22 200
[Sat, 26 Sep 2009 03:30:09 GMT] [info] [<0.14803.137>] 127.0.0.1 - - 'GET' /eee/_design/recipes/_view/alternatives?key=%222008-07-10-choccupcake%22 200
[Sat, 26 Sep 2009 03:30:09 GMT] [info] [<0.14804.137>] 127.0.0.1 - - 'GET' /eee/2008-07-10-choccupcake/cupcake_0043.jpg 200
As expected, subsequent requests only retrieve the recipe document to check the CouchDB revision against the cache:
[Sat, 26 Sep 2009 03:30:17 GMT] [info] [<0.14805.137>] 127.0.0.1 - - 'GET' /eee/2008-07-10-choccupcake 200
[Sat, 26 Sep 2009 03:30:17 GMT] [info] [<0.14815.137>] 127.0.0.1 - - 'GET' /eee/2008-07-10-choccupcake/cupcake_0043.jpg 200
[Sat, 26 Sep 2009 03:30:19 GMT] [info] [<0.14816.137>] 127.0.0.1 - - 'GET' /eee/2008-07-10-choccupcake 200
[Sat, 26 Sep 2009 03:30:19 GMT] [info] [<0.14821.137>] 127.0.0.1 - - 'GET' /eee/2008-07-10-choccupcake/cupcake_0043.jpg 200
The recipe and meal photos are also good candidates for caching via Rack:Cache, so I will likely pick up with that tomorrow.

No comments:

Post a Comment