cache snippets

How to cache PHP objects to disk with CakePHP

Tagged tag, cloud, cache, ttl  Languages php

CakePHP has a view cache (similar to Rails) that can be used to cache objects. The following snippet shows a CakePHP action that uses the serialize and unserialize functions to cache a tag cloud-- an array containing tags in this case--to disk, and then read it back.

Note that we assign a TTL of 1 hours to the tag cloud, so if it's more than one hour old it will be refreshed from the database.

function index()
    {
        $maximum = 100;
        $cache_key = "tag_cloud_$maximum";
        $tag_cloud = cache($cache_key, null, '+1 hours');
        
        if(empty($tag_cloud))
        {
            $tag_cloud = Tag::generate_cloud($maximum);
            cache($cache_key, serialize($tag_cloud));
        }
        else
        {
            $tag_cloud = unserialize($tag_cloud);
        }
        
        return $tag_cloud;
    }

The cache function is defined in $APP_ROOT/cake/basics.php, which is where you should look if you want to know more about how the caching works...

How to setup and use Rack::Cache with Rails

Tagged rack, rack::cache, cache, rails  Languages ruby

Add Rack::Cache to your Gemfile:

gem 'rack-cache', :require => 'rack/cache'

Configure Rails to use Rack::Cache by adding this to config/environment.rb:

config.middleware.use Rack::Cache,
  :verbose => true,
  :metastore   => 'file:/var/cache/rack/meta',
  :entitystore => 'file:/var/cache/rack/body'

This will cache content in /var/cache/rack/meta. Check the documentation for instructions on how to use memcached.

Verify configuration

Verify the configuration:

rake middleware

You should see something like this:

…
use Rack::Cache, {:metastore=>"file:/var/cache/rack/meta", :entitystore=>"file:/var/cache/rack/body", :verbose=>true}
…

Tell Rack::Cache to cache your content

Tell Rack to cache data by calling expires_in from your controller's action or from a Rails filter:

expires_in 5.minutes, :public => true

Rails sets cache-control to private by default, Rack::Cache needs public content.

Don't cache private data

Note that you must be careful not to cache private data when a user is signed in. In this case you should set the Cache-Control header to private or completely avoid using expires_in:

fail "Don't cache private data" if signed_in?
expires_in 5.minutes, :public => true

Verify caching

With verbose set to true, you'll see this in the logs:

[cache] trace: cache miss
[cache] trace: fetching response from backend
[cache] trace: store backend response in cache (ttl: 300s)
[cache] trace: storing response in cache
[cache] trace: delivering response ...
[cache] trace: cache hit (ttl: 276s)
[cache] trace: delivering response ...

Caching more than one type of content

Make sure you cache e.g. HTML and JSON separately. You could use the Vary HTTP header to achieve this:

response.headers['Vary'] = 'Accept'

The problem is that almost every browser version has a unique Accept header.

Instead you could try using the cache_key configuration option to generate a cache key per content type:

:cache_key  =>  lambda { |request|
  Rack::Cache::Key.new(request).generate + ":jebus"
}

You could also add the format request parameter to the URL, e.g. /json-and-html?format=html

Advanced configuration

In production you probably want to disallow reloading of content. This can be done with the following configuration options:

config.middleware.use Rack::Cache,
  :metastore   => 'file:/var/cache/rack/meta',
  :entitystore => 'file:/var/cache/rack/body',
  :allow_reload     => false,
  :allow_revalidate => false,

Read the source (options.rb) for more advanced configuration options (ignore_headers, private_headers, etc).

References

HTTP RFC - Cache-Control Rack::Cache options documentation Rails expires_in documentation HTTP Vary Header

Simple Redis cache utility for Ruby

Tagged redis, cache, sinatra  Languages ruby

Use it at your own risk with e.g. Sinatra:

require 'redis'

module Cache
  mattr_accessor :redis
  class << self
    def get(key)
      Cache.redis.get(key)
    end

    def set(key, value, ttl=nil)
      if ttl
        Cache.redis.setex(key, ttl, value)
      else
        Cache.redis.set(key, value)
      end
    end

    def fetch(key)
      value = get(key)
      value = yield if block_given? && value.nil?
      value
    end

    def establish_connection
      redis_config = YAML.load_file(File.join(Dir.pwd, 'config', 'redis.yml'))
      Cache.redis.client.disconnect if Cache.redis
      Cache.redis = Redis.new(:host => redis_config["host"], :port => redis_config["port"])
    end
  end
end