Skip to content

Instantly share code, notes, and snippets.

@barmstrong
Created November 12, 2012 22:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save barmstrong/4062316 to your computer and use it in GitHub Desktop.
Save barmstrong/4062316 to your computer and use it in GitHub Desktop.
# Another attempt using the built in Rails.cache.fetch (better serialization/deserialization)
class ActiveSupport::Cache::DalliStore
def fast_fetch(name, options=nil)
options ||= {}
name = expanded_key name
dupe_name = name+'_dupe'
if block_given?
unless options[:force]
entry = instrument(:read, name, options) do |payload|
payload[:super_operation] = :fetch if payload
read_entry(name, options)
end
end
if !entry.nil?
instrument(:fetch_hit, name, options) { |payload| }
entry
else
Thread.new do
result = instrument(:generate, name, options) do |payload|
yield
end
write(name, result, options)
write(dupe_name, result)
end
puts 'using old value'
read(dupe_name)
end
else
read(name, options)
end
end
end
# This is an alternative using redis, but it doesn't serialize/deserialize like Rails.cache
class Redis
def fetch key, options=nil
options ||= {expires_in: 10.minutes}
key_expires = key + '_expires'
entry = get(key)
expires = Time.parse(get(key_expires)) rescue 1.second.ago
if expires < Time.now
if entry.nil?
p 'update in real time'
new_entry = yield
set key, new_entry
set key_expires, options[:expires_in].from_now
new_entry
else
Thread.new do
p 'update in background thread'
new_entry = yield
set key, new_entry
set key_expires, options[:expires_in].from_now
end
set key_expires, 10.seconds.from_now # similar to :race_condition_ttl
entry
end
else
entry
end
end
end
irb(main):612:0> REDIS.fetch('test', :expires_in => 6){ sleep 1; Time.now }
"update in real time"
=> "2012-11-12 14:03:26 -0800"
irb(main):612:0> REDIS.fetch('test', :expires_in => 6){ sleep 1; Time.now }
=> "2012-11-12 14:03:26 -0800"
irb(main):612:0> REDIS.fetch('test', :expires_in => 6){ sleep 1; Time.now }
=> "2012-11-12 14:03:26 -0800"
irb(main):613:0> REDIS.fetch('test', :expires_in => 6){ sleep 1; Time.now }
"update in background thread"
=> "2012-11-12 14:03:26 -0800"
irb(main):612:0> REDIS.fetch('test', :expires_in => 6){ sleep 1; Time.now }
=> "2012-11-12 14:03:26 -0800"
irb(main):614:0> REDIS.fetch('test', :expires_in => 6){ sleep 1; Time.now }
=> "2012-11-12 14:03:33 -0800"
irb(main):615:0> REDIS.fetch('test', :expires_in => 6){ sleep 1; Time.now }
=> "2012-11-12 14:03:33 -0800"
irb(main):620:0>
# the first line where it says "update in real time" is the only one that takes 1 second to return
@barmstrong
Copy link
Author

This is my first crack at it - it seems a little inefficient because it needs to set 2 keys in redis. If anyone has a way to improve it let me know!

@kyleschmolze
Copy link

Just want to say, this is GREAT I've been searching around for a couple weeks for anyone else doing this, and I'm really surprised to see nothing out there! Thanks for the snippet :)

Also, I made a simple fork adding YAML serialization and race_condition_ttl to the redis adapter here: https://gist.github.com/kyletns/224c37c0202f71423e95

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment