Thursday, August 28, 2008

InMemoryCacheStore

I really like the the acts_as_cached interface for model caching, but there are a few models that we wanted to cache in the process memory, but still maintain expiration functionality. We came up with this:

class InMemoryCacheStore < Hash

  def initialize(options={})
    @expire_entries = options[:expire_entries]
  end

  def set(key, data, ttl = nil)
    if @expire_entries && ttl
      self[key] = Entry.new(data.dup, Time.now.to_i + ttl) rescue data
    else
      self[key] = Entry.new(data.dup, nil) rescue data 
    end
  end

  # If we're expiring entires, first check the expiration and remove it if it's expired
  # key key
  def get(key)
    r = self[key]
    if @expire_entries && r && r.expires_at < Time.now.to_i
      self.delete(key)
      r = nil
    end
    r ? r.obj : nil
  end

private

  class Entry
    attr_accessor :obj        # cached object
    attr_accessor :expires_at # time in seconds

    def initialize(obj, expires_at)
      @obj = obj
      @expires_at = expires_at
    end
  end

end

Example without expiration:

class SomeModel < ActiveRecord::Base
  acts_as_cached :store => InMemoryCacheStore.new
end

Example with expiration:

class SomeModel < ActiveRecord::Base
  acts_as_cached :store => InMemoryCacheStore.new(:expire_entires => true)

  def self.ttl
    24.hours
  end
end

Performance for 1000 lookups: db on localhost: 1.52 secs memcached on localhost: 0.78 secs InMemoryCacheStore: 0.345 secs

No comments: