Class: Parse::Cache::Redis
- Inherits:
-
Object
- Object
- Parse::Cache::Redis
- Defined in:
- lib/parse/cache/redis.rb
Overview
Ergonomic Redis cache builder for Parse Stack. Composes a
ConnectionPool of Moneta-Redis stores and carries an optional
namespace that Parse::Client will pick up automatically — there
is no need to also pass cache_namespace: to Parse.setup when
using this wrapper.
Usage: Parse.setup( cache: Parse::Cache::Redis.new( url: "redis://localhost:6379/0", namespace: "app_x", pool_size: 10, ), expires: 60, ... )
The instance is a Moneta-compatible store (it delegates the four
methods the Faraday caching middleware uses — [], key?,
delete, store — to a pooled backend), so it can be passed
directly to Parse.setup(cache:) / Parse::Client.new(cache:).
Constant Summary collapse
- LOCK_RELEASE_SCRIPT =
Lua compare-and-delete: delete
keyonly if its current value equalsexpected. Atomic on the Redis server (the GET, the compare, and the DEL are one script invocation), which closes the check-then-delete race in a naive GET-then-DEL release where the lease can expire and be re-acquired by another holder between the two commands. <<~LUA if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end LUA
Instance Attribute Summary collapse
-
#namespace ⇒ String?
readonly
Cache key namespace prefix (or nil if not set).
-
#pool_size ⇒ Integer
readonly
Pool size.
-
#url ⇒ String
readonly
Redis connection URL.
Instance Method Summary collapse
- #[](key) ⇒ Object
-
#clear(scope: nil) ⇒ Object
Clear cached entries belonging to this wrapper.
-
#close ⇒ Object
Close all pooled connections.
-
#create(key, value, options = {}) ⇒ Object
Atomic SETNX.
- #delete(key) ⇒ Object
-
#flush_db! ⇒ Object
Issue
FLUSHDBon the backing Redis DB, regardless of whether a namespace is configured. -
#increment(key, amount = 1, options = {}) ⇒ Object
Atomic counter increment.
-
#initialize(url:, namespace: nil, pool_size: 5, pool_timeout: 5, **moneta_options) ⇒ Redis
constructor
A new instance of Redis.
- #key?(key) ⇒ Boolean
-
#lock_acquire(key, owner, ttl) ⇒ Boolean
Atomically acquire a lock: SET key=owner only if absent, with a native expiry.
-
#lock_release(key, owner) ⇒ Boolean
Atomically release a lock via compare-and-delete.
- #store(key, value, options = {}) ⇒ Object
Constructor Details
#initialize(url:, namespace: nil, pool_size: 5, pool_timeout: 5, **moneta_options) ⇒ Redis
Returns a new instance of Redis.
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/parse/cache/redis.rb', line 73 def initialize(url:, namespace: nil, pool_size: 5, pool_timeout: 5, **) @url = url @namespace = normalize_namespace(namespace) @pool_size = pool_size @pool_timeout = pool_timeout # Default expires: true so per-call `expires:` (the TTL the # Faraday caching middleware passes on store) is honored. The # Moneta-Redis adapter ignores per-call expires unless the # store was constructed with this flag. Without it, cached # session-scoped REST responses outlive their token's # validity. Callers can still pass `expires: false` to opt out. = { expires: true }.merge() @moneta_options = @closed = false @pool = Pool.new(size: pool_size, timeout: pool_timeout) do Moneta.new(:Redis, { url: url }.merge()) end end |
Instance Attribute Details
#namespace ⇒ String? (readonly)
Returns cache key namespace prefix (or nil if not set).
32 33 34 |
# File 'lib/parse/cache/redis.rb', line 32 def namespace @namespace end |
#pool_size ⇒ Integer (readonly)
Returns pool size.
35 36 37 |
# File 'lib/parse/cache/redis.rb', line 35 def pool_size @pool_size end |
#url ⇒ String (readonly)
Returns Redis connection URL.
38 39 40 |
# File 'lib/parse/cache/redis.rb', line 38 def url @url end |
Instance Method Details
#[](key) ⇒ Object
92 93 94 |
# File 'lib/parse/cache/redis.rb', line 92 def [](key) @pool[key] end |
#clear(scope: nil) ⇒ Object
Clear cached entries belonging to this wrapper. Required for
Parse::Client#clear_cache! compatibility.
Namespace-scoped when a namespace is set: the wrapper walks
<namespace>:* via Redis SCAN and DELs the matching keys,
leaving other tenants on the same DB untouched. When no
namespace is configured the wrapper falls back to FLUSHDB on
the backing DB — same blast radius as previous versions, but
only for unnamespaced deployments. To opt into the wide
FLUSHDB explicitly (e.g. ops tooling), call #flush_db!.
195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/parse/cache/redis.rb', line 195 def clear(scope: nil) if scope prefix = validate_scope!(scope) delete_keys_matching!("#{prefix}:*") elsif @namespace delete_keys_matching!("#{@namespace}:*") else @pool.clear end self end |
#close ⇒ Object
Close all pooled connections. Safe to call multiple times.
217 218 219 220 221 |
# File 'lib/parse/cache/redis.rb', line 217 def close return if @closed @closed = true @pool.close end |
#create(key, value, options = {}) ⇒ Object
Atomic SETNX. Required so Parse::CreateLock can acquire
cross-process locks when this wrapper is the configured cache /
synchronize_create_store. Returns true only when the key did
not already exist.
112 113 114 |
# File 'lib/parse/cache/redis.rb', line 112 def create(key, value, = {}) @pool.create(key, value, ) end |
#delete(key) ⇒ Object
100 101 102 |
# File 'lib/parse/cache/redis.rb', line 100 def delete(key) @pool.delete(key) end |
#flush_db! ⇒ Object
Issue FLUSHDB on the backing Redis DB, regardless of whether a
namespace is configured. Evicts every key on the selected DB,
including unrelated tenants — use only for ops tooling that
owns the whole DB.
211 212 213 214 |
# File 'lib/parse/cache/redis.rb', line 211 def flush_db! @pool.clear self end |
#increment(key, amount = 1, options = {}) ⇒ Object
Atomic counter increment. Forwarded for Moneta surface parity.
117 118 119 |
# File 'lib/parse/cache/redis.rb', line 117 def increment(key, amount = 1, = {}) @pool.increment(key, amount, ) end |
#key?(key) ⇒ Boolean
96 97 98 |
# File 'lib/parse/cache/redis.rb', line 96 def key?(key) @pool.key?(key) end |
#lock_acquire(key, owner, ttl) ⇒ Boolean
Atomically acquire a lock: SET key=owner only if absent, with a
native expiry. Used by LockBackend for Lock and
Parse::CreateLock. Deliberately bypasses Moneta's create —
Moneta.new(:Redis) marshals BOTH keys and values, so a raw-Redis
compare-and-delete on the marshaled blob would be fragile and
coupled to Moneta's serializer config. Routing acquire AND release
through plain-string raw Redis here keeps one consistent encoding
across both ends of the lock and makes the keys human-inspectable
in Redis (parse-stack:lock:v1:<digest>). Lock keys are
short-lived (TTL ≤ 30s) so there is no migration concern when a
deploy flips between the Moneta-encoded and raw-encoded paths.
151 152 153 154 155 156 157 |
# File 'lib/parse/cache/redis.rb', line 151 def lock_acquire(key, owner, ttl) @pool.pool.with do |store| redis = backend_client(store) # redis-rb returns "OK" on success, nil when NX fails. !!redis.set(key, owner, nx: true, ex: ttl) end end |
#lock_release(key, owner) ⇒ Boolean
Atomically release a lock via compare-and-delete. Only the holder
whose owner token still matches the stored value deletes the
key — a holder whose lease already expired and was re-acquired by
someone else is a no-op, never a cross-holder delete.
167 168 169 170 171 172 |
# File 'lib/parse/cache/redis.rb', line 167 def lock_release(key, owner) @pool.pool.with do |store| redis = backend_client(store) redis.eval(LOCK_RELEASE_SCRIPT, keys: [key], argv: [owner]).to_i == 1 end end |
#store(key, value, options = {}) ⇒ Object
104 105 106 |
# File 'lib/parse/cache/redis.rb', line 104 def store(key, value, = {}) @pool.store(key, value, ) end |