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!
Pingback: Migrating to Rails 3 on Heroku Bamboo | #nofail
Pingback: Using the Redis addon on Heroku | #nofail