SirCachealot
SirCachealot is an easy-to-use, multi-backend, auto-expiring key-value store for Ruby, available under the 2-clause BSD license. It is built for:
- Simple, swappable, modular backends. Cache server down? Swap another one in and keep chuggin'. Currently supports Redis and an in-memory store.
- Shared memory between processes. Multi-process environments (such as in Passenger) make shared state difficult.
- Unified API supporting a selected, shared subset of each backend's features.
- Easily and seamlessly deal with cache misses.
- Lets you cache stuff the Ruby way!
Here's a quick example, which caches a user object to avoid fetching it from the database:
def login(id, password_hash)
user = Sir.get("userkey-#{id}") do |key| #Doesn't execute the block if key is found
Sir.put(key, User.find_by_id(id), 1.day) #Cache miss! So let's fetch the User and store it for a day
end # (note: 1.day comes from ActiveSupport, not included)
# your code here
user.authenticate?(password_hash) #returns true if match, false is not
end
Roadmap for 1.0:
- More storage backends: RailsCache, Postgres HSTORE, Mysql memtable, memcache, internal
- Add redis incr/decr and extra datatypes (emulate features on ramcache if necessary)
- Explore clustering applications
- Add convenience methods to ActiveRecord
Installation
Add this line to your application's Gemfile:
gem 'sir_cachealot'
And then execute:
$ bundle
Or install it yourself as:
$ gem install sir_cachealot
Usage
SirCachealot exposes a new module named Sir
. This module is designed to be available globally in both Rails apps and vanilla Ruby scripts (don't forget require 'sir'
!)
This section is a quick tour through some of SirCachealot's best features. For a comprehensive API reference, please refer to the comments in Sir::Backends::Base
.
You can use SirCachealot immediately, using either:
Sir.put(keyname, value)
Sir.put(keyname, value, expiry) # expiry can be relative or absolute time expressed in seconds (Fixnum) or a Time object.
put()
will return the object you gave it. This is useful if you wish to use get()
's yield
functionality.
If config(:delete_on_nil) == true
and value == nil
, put()
will return true
(because it deleted the key).
You can retreive the value later, if it hasn't expired, with:
my_var = Sir.get(keyname)
or
# A convenient way to rectify a cache miss!
# put() returns the object you give it
my_var = Sir.get(keyname) do |key|
Sir.put(key, User.find_by_id(id))
end
If the key does not exist, or if it has expired:
-
get()
returnsnil
if not given a block to execute. -
get()
yields to code block, if one is supplied.
To delete a cache entry, you can:
Sir.kill(key)
If you want to clear the cache, you can:
Sir.nuke
If you want to sweep and purge all expired entries, you can:
Sir.sweep
There are a few configuration options available. You can configure SirCachealot with:
cache_opts = Sir::Backends::RamCache::DEFAULTS #Change RamCache to RedisCache for redis
Sir.configure do |config|
config[:default_expiry] = 3600 # default expiration timeout in seconds
config[:mode] = :ram_cache # cache storage mode. Currently: :ram_cache, :redis_cache
config[:debug] = true|false # show some debug messages
config[:annoy] = true|false # show even more debug messages
config[:options] = cache_opts # optional, depending on backend
end
Note: Backends may have additional configuration parameters that need to be satisfied. The default configuration can be retreived from the DEFAULTS
constant in the backend class, as shown above. These values may then be modified and passed back to Sir.configure()
API Reference
See Sir::Backends::Base
Available Backends
RAM Cache (for testing)
The RAM Cache stores cache entries in a Ruby hash, and is the default cache store that SirCachealot will use if left unconfigured.
RAM Cache does not support automatic expiration, and so it must be periodically #swept(). RAM Cache's thread safety depends on your interpreter: 'green thread' implementations are safe, while true multi-threaded environments (such as JRuby) remain untested at this time.
This backend module is intentionally left simple, and is of limited usefulness. It is designed to be an example for implementing additional backends, and to satisfy basic turn-key functionality.
Configuration
Note that #configure is not necessary if you wish to use RAM cache with its default settings.
Sir.configure do |config|
config[:default_expiry] = 3600
config[:mode] = :ram_cache
end
Redis Cache
Redis cache supports a subset of full Redis functionality.
Configuration
redis_obj = Redis.new(:path => "/tmp/redis.sock")
cache_opts = Sir::Backends::RedisCache::DEFAULTS
cache_opts[:redis_obj] = redis_obj #supply a preconfigured Redis instance
cache_opts[:namespace] = "MyCacheStore" #override the default "SirCachealot" namespace
cache_opts[:serializer] = :marshal #all requests to put() are serialized. :marshal or :json are available
Sir.configure do |config|
config[:default_expiry] = 3600
config[:mode] = :redis_cache
config[:options] = cache_opts
end
Options
-
:redis_obj
: Store your pre-configuredRedis
instance in here -
:namespace
: Internally, all keys are prefixed with "SirCachealot" while stored in Redis. You can change that string with this option. -
:serializer
::marhsal
to serialize all values withMarshal.load()
, or :json to serialize all values with.to_json
- Note: Do not switch to a different serializer without first calling
Sir.nuke()
!
- Note: Do not switch to a different serializer without first calling
Testing
A test suite is available through rspec
.
sir_cachealot 0.6.0 was tested against ruby 1.9.3, 2.0, and 2.1 and passed 27/27 tests.
Testing SirCachealot
$> cd $GEM_PATH # cd into your gem path. The shown command, as written, will fail if you use rvm.
$> cd gems/sir_cachealot-0.6.0
$> bundle install
$> rspec
Change Log
0.6.0
- Modularized backends.
- Implemented Redis Cache features: FLUSHDB (
nuke()
, KEYS (keys()
), DBSIZE (length()
), BGSAVE (flush()
) - Renamed clean, clear functions to sweep, nuke (to be easier to visually differentiate)
Known issues
-
Marshal.load()
, which is used by RedisCache's:marshal
serializer, has been known to throw exceptions when it is called repeatedly (like in a tight loop)
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
License
Use of SirCachealot is permitted under the terms of the 2-clause BSD License. For more information see LICENSE.txt.