Project

cachext

0.0
No commit activity in last 3 years
No release in over 3 years
Don't calculate the cached value twice at the same time. Use a backup of the data if the service is down.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.10
>= 0
~> 10.0
>= 0
>= 0

Runtime

>= 0
>= 0
 Project Readme

Cachext

Build Status Gem Version Code Climate

Extensions to normal Rails caching:

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 the default_expires_in, in seconds
  • default: object or proc that will be used as the default if no backup is found
  • errors: override for the default_errors: array of errors to catch and not reraise
  • reraise_errors: default true, if set to false 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 for default_expires_in, in seconds
  • return_array: return an array instead of a hash. Will include missing records as Cachext::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.