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