Simple DB caching for Heroku
Heroku is a great platform. I like the style of the page, I appreciate the documentation and you can start up for free! One thing that I miss a lot is decent caching. The readonly filesystem eats up a lot of flexibility.
I played around with HTTP caching and Herokus Varnish works really well. The problem is that my app loads a lot of stuff from different 3rd party services like Twitter, so every new visitor will have all the load time on his first visit. Not a surprise that New Relic indicates that request times were ‘Unacceptable’…
I would like to check out the ‘Memcached Basic’ plugin of Heroku, but I did not manage to get into the private beta. So there was no other option than implementing a DB cache.
There is just one requirement that I have. Load stuff from a 3rd party service only if it’s expired. For simplicity, expired means, that the data is older than a predefined interval. In my test environment I like to use a shorter period than in production, so I define the interval in the environment files:
# config/environments/development.rb CACHE_TIME = 30.seconds # config/environments/production.rb CACHE_TIME = 10.hours
A simple key-data pair is enough for my needs, because I always have a unique key for the values I want to cache. I am using Marshal.dump/Marshal.load for serialization, as they play well with anonymous inner classes that YAML can’t deal with. Encoding the data Base64 helps working around some SQLITE issues with serialized data strings:
# app/models/storage.rb class Storage < ActiveRecord::Base validates_presence_of :key, :data def data=(data) write_attribute :data, ActiveSupport::Base64.encode64(Marshal.dump(data)) end def data Marshal.load(ActiveSupport::Base64.decode64(read_attribute :data)) end end
The actual caching logic is embeded in my application controller. I provide a simple cache method, that can be called with a block. The block contains the remote call that I want to cache and is only executed if there is no data stored for the given key or the stored data is expired:
# app/controllers/application_controller.rb def cache(key, &to_cache) from_db = Storage.first(:conditions => {:key => key}) if from_db.nil? || from_db.updated_at < Time.new - CACHE_TIME data = (yield to_cache).collect{|t|t} return [] if data.nil? || data.empty? from_db = (from_db || Storage.new) from_db.key = key from_db.data = data from_db.save! end instance_variable_set :"@#{key.to_s}", from_db.data end
Finally the data is pushed into an instance varaible, so that I have access to it within my views.
Caching is now as simple as this:
# cache all twitter posts and make them accessible via @tweets cache(:tweets){Helper::twitter_posts}
This little tweak noteable improved the response time of my app:
This week: Apdex Score: 0.700.5 (Fair) Last week: Apdex Score: 0.060.5 (Unacceptable)
Sugar on rails!













2 Comments
leave a commentTrackbacks and Pingbacks