Class: DedupeRequests::RedisStore

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

Overview

Redis-backed claim/release store.

  • claim: atomic SET key <token> NX EX <ttl>. Returns the token on success,

    false if the key already exists (duplicate), or :error if Redis is
    unreachable (fail open).
    
  • release: token-safe check-and-del via a Lua script — only deletes the key

    if it still holds OUR token, so a slow request whose TTL expired
    cannot wipe a newer request's fresh claim.
    

Defined Under Namespace

Classes: NullPool

Constant Summary collapse

RELEASE_SCRIPT =
<<~LUA
  if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
  else
    return 0
  end
LUA

Instance Method Summary collapse

Constructor Details

#initialize(redis_or_pool, namespace: "dedupe_requests", logger: nil) ⇒ RedisStore

Returns a new instance of RedisStore.



35
36
37
38
39
# File 'lib/dedupe_requests/redis_store.rb', line 35

def initialize(redis_or_pool, namespace: "dedupe_requests", logger: nil)
  @pool = redis_or_pool.respond_to?(:with) ? redis_or_pool : NullPool.new(redis_or_pool)
  @namespace = namespace
  @logger = logger
end

Instance Method Details

#claim(fingerprint, ttl:) ⇒ Object



41
42
43
44
45
46
47
48
# File 'lib/dedupe_requests/redis_store.rb', line 41

def claim(fingerprint, ttl:)
  token = SecureRandom.hex(16)
  ok = @pool.with { |r| r.set(key(fingerprint), token, nx: true, ex: ttl) }
  ok ? token : false
rescue StandardError => e
  log(e)
  :error
end

#key(fingerprint) ⇒ Object



58
59
60
# File 'lib/dedupe_requests/redis_store.rb', line 58

def key(fingerprint)
  "#{@namespace}:dedup:#{fingerprint}"
end

#release(fingerprint, token) ⇒ Object



50
51
52
53
54
55
56
# File 'lib/dedupe_requests/redis_store.rb', line 50

def release(fingerprint, token)
  @pool.with { |r| r.eval(RELEASE_SCRIPT, keys: [key(fingerprint)], argv: [token]) }
  true
rescue StandardError => e
  log(e)
  false
end