Skip to content

Instantly share code, notes, and snippets.

@jhjguxin
Last active January 3, 2016 14:29
Show Gist options
  • Save jhjguxin/8476409 to your computer and use it in GitHub Desktop.
Save jhjguxin/8476409 to your computer and use it in GitHub Desktop.
Rails cache (dalli) auto renew before cached entry was expired

Rails cache (dalli) snippet

base on guanxi.me
author: Francis Jiang

usual case

def smart_fetch(name, options, &blk)
  options = HashWithIndifferentAccess.new(options)

  in_cache = Rails.cache.fetch(name)
  return in_cache if in_cache
  val = yield
  Rails.cache.write(name, val, options)
  return val
end

loop do
  proc = Proc.new{ 1+3;sleep 5 }
  puts smart_fetch("test", {:expires_in => 3.seconds}, &proc)
  sleep 1
end

Implement auto renew before cached entry was expired

def generate_cache_key(cache_key = nil, expires_in = nil, options = {})
  cache_key_suffix ||= "_#{Rails.env}"

  if expires_in.present?
    "#{cache_key}_expires_in_#{expires_in}_#{cache_key_suffix}"
  else
    "#{cache_key}_#{cache_key_suffix}"
  end
end

# cache_value
# if with the cache_key have no entry cache the value from proc.call
# if arg[:refresh] present cache the value from proc.call
# elsif time is overtake arg[:fresh_countdown] cache the value from proc.call and run it with an new thread
# else read from cache server
# Utility.cache_value({cache_key: "#{User.name.underscore}", expires_in: 10.minutes, fresh_countdown: 60}) { User.count }
def cache_value(arg = {cache_key: nil, expires_in: 10.minutes, fresh_countdown: 60})
  arg = HashWithIndifferentAccess.new(arg)
  expires_in = arg[:expires_in] || 10.minutes
  fresh_countdown = arg[:fresh_countdown] || 1.minutes

  user_cache_key = arg[:cache_key]
  # FIXME cache key will not invoke expires time
  cache_key = generate_cache_key(user_cache_key)

  refresh = arg[:refresh]

  if cache_key.present? and expires_in.present?
    cached_value = Rails.cache.read(cache_key)

    if refresh or cached_value.nil?
      value = yield
      Rails.cache.write(cache_key, value, :expires_in => expires_in)
      cached_value = value
    elsif  should_fresh?(cache_key, {:expires_in => expires_in}, fresh_countdown)
      Thread.new() {
        Rails.cache.write(cache_key, value, :expires_in => expires_in)
      }
    end

    cached_value
  else
    raise "Argument Missing cache_key: #{cache_key}, expires_in: #{expires_in}"
  end
end

# through common cache options auto renewal within a fresh_countdown times
# return a time instance told us when should refresh an cached entry
def should_refresh_at(cache_key, options = {}, fresh_countdown = 60)
  options = HashWithIndifferentAccess.new(options)
  if options[:expires_at].respond_to?(:to_time)
    _expires_at = options[:expires_at].to_time
    _expires_in = _expires_at - Time.now
  end
  if options[:expires_in].present?
    _expires_in = options[:expires_in].to_i
    _expires_at = Time.at(Time.now + _expires_in)
  end

  _should_expires_in = _expires_in - fresh_countdown + 2

  return if _expires_at.nil? or _expires_in.nil? or (_should_expires_in < 0)
  cache_key = "#{cache_key}_should_expires_in_#{_should_expires_in}"

  in_cache = Rails.cache.read(cache_key)
  if in_cache.nil?
    Rails.cache.write(cache_key, _expires_at, {expires_in: _should_expires_in})
    _expires_at
  else
    in_cache
  end
end


# if time is overtake fresh_countdown return true else false
# otherwise return true
# loop do
#   puts Utility.should_fresh?("aa1",{expires_in: 10.seconds},3)
#   sleep 1
# end
def should_fresh?(cache_key, options = {}, fresh_countdown = 60)
  _expires_at = should_refresh_at(cache_key, options, fresh_countdown)

  if _expires_at.present?
    (_expires_at - Time.now) < fresh_countdown
  else
    true
  end
end

# just pick the value `cache_value` cached
def get_cache_value(cache_key = nil)
  if cache_key.present?
    user_cache_key = cache_key
    cache_key = generate_cache_key(user_cache_key)

    Rails.cache.read(cache_key)
  end
end

# lazy cache
# when value present update cached entry
# else read from cache server
def lazy_cacher(arg = {cache_key: nil, value: nil, expires_in: 1.days})
  expires_in = arg[:expires_in]
  expires_in ||= 1.days
  user_cache_key = arg[:cache_key]
  cache_key = generate_cache_key(user_cache_key, expires_in)

  value = arg[:value]
  destroy = arg[:destroy]

  if cache_key.present? and expires_in.present?

    cache_key = "#{arg[:cache_key]}_expires_in_#{expires_in}_#{Rails.env}"

    if value.present?
      Rails.cache.write(cache_key, value, :expires_in => expires_in)
    else
      value = Rails.cache.read(cache_key)
      Rails.cache.delete(cache_key) if destroy
    end

    value
  else
    raise "Argument Missing cache_key: #{cache_key}, value: #{value}, expires_in: #{expires_in}"
  end
end

Utility.should_refresh_at("rand", {expires_in: 10.seconds}, 10)
# Utility.cache_value({cache_key: "rand", expires_in: 10.seconds, fresh_countdown: 3}) { rand(1..8) }

loop do
  puts Utility.should_fresh?("aa1",{expires_in: 10.seconds},3)
  sleep 1
end

loop do
  puts Utility.cache_value({cache_key: "rand", expires_in: 10.seconds, fresh_countdown: 3}) { sleep 3;rand(1..8) }
  sleep 1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment