Module: AtomicRedisCache
- Defined in:
- lib/atomic_redis_cache.rb,
lib/atomic_redis_cache/version.rb
Constant Summary collapse
- DEFAULT_EXPIRATION =
86400 seconds in a day
60*60*24
- DEFAULT_RACE_TTL =
seconds to acquire new value
30- MAX_RETRIES =
recalc attempts before expiring cache
3- VERSION =
"0.0.1"
Class Attribute Summary collapse
Class Method Summary collapse
-
.fetch(key, opts = {}, &blk) ⇒ Object
Fetch from cache with fallback, just like ActiveSupport::Cache.
Class Attribute Details
.redis ⇒ Object
55 56 57 58 |
# File 'lib/atomic_redis_cache.rb', line 55 def redis raise 'AtomicRedisCache.redis must be set before use.' unless @redis @redis.respond_to?(:call) ? @redis.call : @redis end |
Class Method Details
.fetch(key, opts = {}, &blk) ⇒ Object
Fetch from cache with fallback, just like ActiveSupport::Cache. The main differences are in the edge cases around expiration.
-
when cache expires, we avoid dogpile/thundering herd from multiple processes recalculating at once
-
when calculation takes too long (i.e., due to network traffic) we return the previously cached value for several attempts
Options: :expires_in - expiry in seconds; defaults to a day :race_condition_ttl - time to lock value for recalculation :max_retries - # of times to retry cache refresh before expiring
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/atomic_redis_cache.rb', line 21 def fetch(key, opts={}, &blk) expires_in = opts[:expires_in] || DEFAULT_EXPIRATION race_ttl = opts[:race_condition_ttl] || DEFAULT_RACE_TTL retries = opts[:max_retries] || MAX_RETRIES now = Time.now.to_i ttl = expires_in + retries * race_ttl t_key = "timer:#{key}" if val = redis.get(key) # cache hit if redis.get(t_key).to_i < now # expired entry or dne redis.set t_key, now + race_ttl # block other callers for recalc duration begin Timeout.timeout(race_ttl) do # if recalc exceeds race_ttl, abort val = Marshal.dump(blk.call) # determine new value redis.multi do # atomically cache + mark as valid redis.setex key, ttl, val redis.set t_key, now + expires_in end end rescue Timeout::Error => e # eval timed out, use cached val end end else # cache miss val = Marshal.dump(blk.call) # determine new value redis.multi do # atomically cache + mark as valid redis.setex key, ttl, val redis.set t_key, now + expires_in end end Marshal.load(val) end |