Cachext
Extensions to normal Rails caching:
- Lock (inspired by https://github.com/seamusabshere/lock_and_cache)
- Backup
- Circuit breaker
Quickstart
Cachext.configure do |config|
config.cache = Rails.cache
config.redis = Redis.current
end
key = [:foo, :bar, 1]
Cachext.fetch key, expires_in: 2.hours, default: "cow" do
Faraday.get "http://example.com/foo/bar/1"
end
- Other services making the same call at the same time will wait for the first to complete, so only 1 call is made in a 2 hour window
- A backup of the value is stored too, so if the service raises a Faraday::Error::ConnectionFailed we'll return the backup
- If no backup exists but we got a ConnectionFailed, we'll return the default of "cow"
Record = Struct.new :id
Cachext.multi [:foo, :bar], [1,2,3], expires_in: 5.minutes do |ids|
data = JSON.parse Faraday.get("http://example.com/foo/bar?ids=#{ids.join(',')}")
data.each_with_object({}) do |record, acc|
acc[record["id"]] = Record.new record["id"]
end
end
# => { 1 => Record.new(1), 2 => Record.new(2), 3 => Record.new(3) }
- The passed block will be called with the ids that were not available in the
cache. The return value of the block should either be a hash with keys of
ids, or an array of objects that have
id
methods. - In the event of a server error (ie
ConnectionFailed
), backup values are used.
Configuration options
Cachext.config.cache = Rails.cache
Cachext
expects a cache store that has the ActiveSupport::Cache
interface,
so that can be Memcache, Redis, FileStore, etc.
Cachext.config.redis = Redis.current
Cachext
uses redis for locking (the
Redlock gem under the hood), so
we need at least Redis 2.8.
Cachext.config.raise_errors = false
Cachext.config.default_errors = [
Faraday::Error::ConnectionFailed,
Faraday::Error::TimeoutError,
]
By default Cachext
will not re-raise the standard default errors. Setting
this to true
is helpful in a test environment. The default_errors
are those
caught as transient issues that a backup will be used for.
Cachext.config.not_found_errors = [Faraday::Error::ResourceNotFound]
If a NotFound exception is raised, the backup is not used, and any backup that exists will be deleted. Then the exception will be re-raised.
Cachext.config.default_expires_in = 60 # in seconds
The default TTL for values fetched. Only used for the "fresh" cache, not the backup (which has no TTL).
Cachext.config.max_lock_wait = 5 # in seconds
The most we'll wait for a lock to unlock. If it takes more than this value to get a lock (due to another service holding the lock while making the call), we'll fallback to the backup value.
Cachext.config.debug = ENV['CACHEXT_DEBUG'] == "true"
If debug
is set to true
(or you run your program/test with
CACHEXT_DEBUG=true
), you'll get lots of debug messages around the locking and
whats going on. Very helpful for debugging :)
Cachext.config.heartbeat_expires = 2 # in seconds
If a process that holds a lock crashes, other processes will have to wait this many seconds for the lock to expire.
Cachext.config.error_logger = nil
If set to an object that responds to call, will call
with any errors caught.
Cachext.config.failure_threshold = 3
Number of tries before tripping circuit breaker.
Cachext.config.breaker_timeout = 60
Time in seconds to wait before switching breaker to half-open.
Usage
Cachext.fetch key, options, &block
Available options:
-
expires_in
: override for thedefault_expires_in
, in seconds -
default
: object or proc that will be used as the default if no backup is found -
errors
: override for thedefault_errors
: array of errors to catch and not reraise -
reraise_errors
: defaulttrue
, if set tofalse
NotFound errors will not be raised -
not_found_error
: (override) array of errors where we delete the backup and reraise -
heartbeat_expires
: (override) time in seconds for process heardbeat to expire -
failure_threshold
: (override) Number of tries before tripping circuit breaker -
breaker_timeout
: (override) time in seconds to wait before switching breaker to half-open -
cache
: use the first-level cache, defaults to true. If set to false, will always call the fallback, but if an error is raised, will use the last known good value.
Cachext.multi key_base, ids, options, &block
Available options:
-
expires_in
: override fordefault_expires_in
, in seconds -
return_array
: return an array instead of a hash. Will include missing records asCachext::MissingRecord
objects so you can deal with them.
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run
rake spec
to run the tests. You can also run bin/console
for an interactive
prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To
release a new version, update the version number in version.rb
, and then run
bundle exec rake release
, which will create a git tag for the version, push
git commits and tags, and push the .gem
file to
rubygems.org.
Having trouble with a test? Set the CACHEXT_DEBUG
environmental variable to
"true" to get debug logs.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/dplummer/cachext.
License
The gem is available as open source under the terms of the MIT License.