Class: Parse::Cache::Redis

Inherits:
Object
  • Object
show all
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:).

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(url:, namespace: nil, pool_size: 5, pool_timeout: 5, **moneta_options) ⇒ Redis

Returns a new instance of Redis.

Parameters:

  • url (String)

    Redis URL (e.g. "redis://localhost:6379/0").

  • namespace (String, nil) (defaults to: nil)

    optional key prefix so multiple Parse apps can share one Redis without colliding. When non-nil, the namespace is automatically forwarded to the caching middleware as cache_namespace:.

  • pool_size (Integer) (defaults to: 5)

    number of pooled Moneta-Redis stores. Defaults to 5 (the Puma default thread count).

    Sizing math (per Faraday request):

    • cache hit: key? + [] = 2 checkouts
    • GET miss + successful store: key? + 3 variant deletes (anonymous + master-key sibling + final key) + 1 store in on_complete = up to 5 checkouts
    • non-GET write (POST/PUT/DELETE): 3 variant deletes = 3 checkouts

    The worst case (5) is on the write-through-after-miss path, not the hit path. Rule of thumb: start at pool_size = RAILS_MAX_THREADS, then bump it up if you observe ConnectionPool::TimeoutError in parse.cache.error notifications (the middleware swallows that error into a passthrough request rather than raising to the caller).

  • pool_timeout (Numeric) (defaults to: 5)

    seconds to wait for a backend checkout before raising ConnectionPool::TimeoutError. Defaults to 5s. The caching middleware catches that error and falls back to a passthrough request rather than raising to the caller.

  • moneta_options (Hash)

    extra options passed through to Moneta.new(:Redis, ...) (e.g. :db, :connect_timeout). expires: true is set automatically so per-key TTLs supplied by the caching middleware (the :expires Faraday option) are honored by Redis. Pass expires: false here to opt out — but note that doing so causes cached responses to live forever, which is rarely what you want for a session-token-scoped response cache.



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, **moneta_options)
  @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.
  merged_options = { expires: true }.merge(moneta_options)
  @moneta_options = merged_options
  @closed = false
  @pool = Pool.new(size: pool_size, timeout: pool_timeout) do
    Moneta.new(:Redis, { url: url }.merge(merged_options))
  end
end

Instance Attribute Details

#namespaceString? (readonly)

Returns cache key namespace prefix (or nil if not set).

Returns:

  • (String, nil)

    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_sizeInteger (readonly)

Returns pool size.

Returns:

  • (Integer)

    pool size.



35
36
37
# File 'lib/parse/cache/redis.rb', line 35

def pool_size
  @pool_size
end

#urlString (readonly)

Returns Redis connection URL.

Returns:

  • (String)

    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!.

Parameters:

  • scope (String, nil) (defaults to: nil)

    explicit namespace prefix to scan-delete. When provided, overrides the wrapper's configured @namespace and SCAN-deletes <scope>:* regardless of how the wrapper was built. This is the safe escape hatch for tenants that share a non- namespaced wrapper but still want to evict only their own keys without FLUSHDB-ing siblings (and without wiping parse-stack:foc:v1:* create-lock keys that live on the same DB). The scope must be a non-empty String; the trailing : is added automatically and any trailing : in the input is stripped so "tenant_x" and "tenant_x:" are equivalent.



142
143
144
145
146
147
148
149
150
151
152
# File 'lib/parse/cache/redis.rb', line 142

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

#closeObject

Close all pooled connections. Safe to call multiple times.



164
165
166
167
168
# File 'lib/parse/cache/redis.rb', line 164

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, options = {})
  @pool.create(key, value, options)
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.



158
159
160
161
# File 'lib/parse/cache/redis.rb', line 158

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, options = {})
  @pool.increment(key, amount, options)
end

#key?(key) ⇒ Boolean

Returns:

  • (Boolean)


96
97
98
# File 'lib/parse/cache/redis.rb', line 96

def key?(key)
  @pool.key?(key)
end

#store(key, value, options = {}) ⇒ Object



104
105
106
# File 'lib/parse/cache/redis.rb', line 104

def store(key, value, options = {})
  @pool.store(key, value, options)
end