Class: GCRA::RedisStore

Inherits:
Object
  • Object
show all
Defined in:
lib/gcra/redis_store.rb

Overview

Redis store, expects all timestamps and durations to be integers with nanoseconds since epoch.

Constant Summary collapse

CAS_SCRIPT =
<<-EOF.freeze
local v = redis.call('get', KEYS[1])
if v == false then
  return redis.error_reply("key does not exist")
end
if v ~= ARGV[1] then
  return 0
end
redis.call('psetex', KEYS[1], ARGV[3], ARGV[2])
return 1
EOF
CAS_SHA =

Digest::SHA1.hexdigest(CAS_SCRIPT)

"89118e702230c0d65969c5fc557a6e942a2f4d31".freeze
CAS_SCRIPT_MISSING_KEY_RESPONSE =
'key does not exist'.freeze
SCRIPT_NOT_IN_CACHE_RESPONSE =
'NOSCRIPT No matching script. Please use EVAL.'

Instance Method Summary collapse

Constructor Details

#initialize(redis, key_prefix) ⇒ RedisStore

Returns a new instance of RedisStore.



21
22
23
24
# File 'lib/gcra/redis_store.rb', line 21

def initialize(redis, key_prefix)
  @redis = redis
  @key_prefix = key_prefix
end

Instance Method Details

#compare_and_set_with_ttl(key, old_value, new_value, ttl_nano) ⇒ Object

Atomically compare the value at key to the old value. If it matches, set it to the new value and return true. Otherwise, return false. If the key does not exist in the store, return false with no error. If the swap succeeds, update the ttl for the key atomically.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/gcra/redis_store.rb', line 53

def compare_and_set_with_ttl(key, old_value, new_value, ttl_nano)
  full_key = @key_prefix + key
  retried = false
  begin
    ttl_milli = calculate_ttl_milli(ttl_nano)
    swapped = @redis.evalsha(CAS_SHA, keys: [full_key], argv: [old_value, new_value, ttl_milli])
  rescue Redis::CommandError => e
    if e.message == CAS_SCRIPT_MISSING_KEY_RESPONSE
      return false
    elsif e.message == SCRIPT_NOT_IN_CACHE_RESPONSE && !retried
      @redis.script('load', CAS_SCRIPT)
      retried = true
      retry
    end
    raise
  end

  return swapped == 1
end

#get_with_time(key) ⇒ Object

Returns the value of the key or nil, if it isn’t in the store. Also returns the time from the Redis server, with microsecond precision.



28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/gcra/redis_store.rb', line 28

def get_with_time(key)
  time_response, value = @redis.pipelined do
    @redis.time # returns tuple (seconds since epoch, microseconds)
    @redis.get(@key_prefix + key)
  end
  # Convert tuple to nanoseconds
  time = (time_response[0] * 1_000_000 + time_response[1]) * 1_000
  if value != nil
    value = value.to_i
  end

  return value, time
end

#set_if_not_exists_with_ttl(key, value, ttl_nano) ⇒ Object

Set the value of key only if it is not already set. Return whether the value was set. Also set the key’s expiration (ttl, in seconds).



44
45
46
47
48
# File 'lib/gcra/redis_store.rb', line 44

def set_if_not_exists_with_ttl(key, value, ttl_nano)
  full_key = @key_prefix + key
  ttl_milli = calculate_ttl_milli(ttl_nano)
  @redis.set(full_key, value, nx: true, px: ttl_milli)
end