Class: Parse::Cache::Pool
- Inherits:
-
Object
- Object
- Parse::Cache::Pool
- Defined in:
- lib/parse/cache/pool.rb
Overview
Moneta-compatible facade over a ConnectionPool of Moneta stores. The
Faraday caching middleware only calls four methods on its store
([], key?, delete, store); this class checks out a backend
for each of them via @pool.with.
Why a pool: a single Moneta-Redis store wraps one Redis connection. Under a multi-threaded Puma worker (or any concurrent caller), threads serialize on that connection's mutex. A pool of N stores lets up to N cache calls run in parallel.
Note that a cache hit costs two checkouts (key? then []). That is
accepted to keep behavior identical to a plain Moneta store; callers
should size the pool with that in mind (default 5, which matches the
Puma default thread count).
Instance Attribute Summary collapse
-
#pool ⇒ Object
readonly
The wrapped ConnectionPool instance.
Instance Method Summary collapse
- #[](key) ⇒ Object
-
#clear ⇒ Object
Clear the underlying backend.
- #close ⇒ Object
-
#create(key, value, options = {}) ⇒ Object
Atomic SETNX-style write.
- #delete(key, options = {}) ⇒ Object
-
#increment(key, amount = 1, options = {}) ⇒ Object
Atomic counter increment.
-
#initialize(size: 5, timeout: 5) { ... } ⇒ Pool
constructor
A new instance of Pool.
- #key?(key, options = {}) ⇒ Boolean
-
#load(key, options = {}) ⇒ Object
Moneta's read primitive, carrying its options argument (
expires:to refresh a TTL on read, for instance). -
#options_aware?(store, name) ⇒ Boolean
Close all pooled backends.
- #store(key, value, options = {}) ⇒ Object
Constructor Details
#initialize(size: 5, timeout: 5) { ... } ⇒ Pool
Returns a new instance of Pool.
32 33 34 35 36 |
# File 'lib/parse/cache/pool.rb', line 32 def initialize(size: 5, timeout: 5, &block) raise ArgumentError, "Parse::Cache::Pool requires a block that builds a Moneta store" unless block_given? @pool = ConnectionPool.new(size: size, timeout: timeout, &block) @closed = false end |
Instance Attribute Details
#pool ⇒ Object (readonly)
The wrapped ConnectionPool instance.
25 26 27 |
# File 'lib/parse/cache/pool.rb', line 25 def pool @pool end |
Instance Method Details
#[](key) ⇒ Object
38 39 40 |
# File 'lib/parse/cache/pool.rb', line 38 def [](key) load(key, {}) end |
#clear ⇒ Object
Clear the underlying backend. Pooled Moneta stores all point at the
same Redis DB, so a single checkout suffices — issuing clear on
one connection flushes the DB for every connection.
94 95 96 97 |
# File 'lib/parse/cache/pool.rb', line 94 def clear @pool.with { |store| store.clear if store.respond_to?(:clear) } self end |
#close ⇒ Object
124 125 126 127 128 |
# File 'lib/parse/cache/pool.rb', line 124 def close return if @closed @closed = true @pool.shutdown { |store| store.close if store.respond_to?(:close) } end |
#create(key, value, options = {}) ⇒ Object
Atomic SETNX-style write. Required by Parse::CreateLock to acquire
cross-process locks against Redis-backed stores. Forwards to the
underlying Moneta store's #create, which returns true only if
the key was absent and is now set.
80 81 82 |
# File 'lib/parse/cache/pool.rb', line 80 def create(key, value, = {}) @pool.with { |store| store.create(key, value, ) } end |
#delete(key, options = {}) ⇒ Object
65 66 67 68 69 70 |
# File 'lib/parse/cache/pool.rb', line 65 def delete(key, = {}) @pool.with do |store| next store.delete(key, || {}) if (store, :delete) store.delete(key) end end |
#increment(key, amount = 1, options = {}) ⇒ Object
Atomic counter increment. Forwarded for parity with Moneta so callers expecting the full Moneta surface (counters, rate limits) work transparently through the pool.
87 88 89 |
# File 'lib/parse/cache/pool.rb', line 87 def increment(key, amount = 1, = {}) @pool.with { |store| store.increment(key, amount, ) } end |
#key?(key, options = {}) ⇒ Boolean
58 59 60 61 62 63 |
# File 'lib/parse/cache/pool.rb', line 58 def key?(key, = {}) @pool.with do |store| next store.key?(key, || {}) if (store, :key?) store.key?(key) end end |
#load(key, options = {}) ⇒ Object
Moneta's read primitive, carrying its options argument (expires: to
refresh a TTL on read, for instance).
Feature-detected rather than called blind. Every real Moneta store
implements load, but a custom store that only implements [] would
otherwise reach Kernel#load, which is private (so the failure is a
confusing NoMethodError) and, were it public, would try to load a
FILE named after the cache key. respond_to? answers false for the
private Kernel method, so this check is exact.
51 52 53 54 55 56 |
# File 'lib/parse/cache/pool.rb', line 51 def load(key, = {}) @pool.with do |store| next store.load(key, || {}) if store.respond_to?(:load) store[key] end end |
#options_aware?(store, name) ⇒ Boolean
Close all pooled backends. Safe to call multiple times — repeat
calls are no-ops. ConnectionPool#shutdown raises
ConnectionPool::PoolShuttingDownError on a second invocation,
so we gate it with a @closed flag.
Whether store takes Moneta's options argument for name.
Decided from arity and memoized per method, not by calling with
options and rescuing ArgumentError. That retry had three problems: a
store's own argument validation also raises ArgumentError, so a
genuine rejection was retried instead of surfaced; for delete the
retry meant the deletion could run TWICE; and because each attempt
took its own checkout, the second ran against a DIFFERENT connection
than the first. Every pooled store is built by the same block, so one
probe answers for all of them.
113 114 115 116 117 118 119 120 121 122 |
# File 'lib/parse/cache/pool.rb', line 113 def (store, name) @options_aware ||= {} return @options_aware[name] if @options_aware.key?(name) arity = begin store.method(name).arity rescue NameError 0 end @options_aware[name] = arity.negative? || arity >= 2 end |
#store(key, value, options = {}) ⇒ Object
72 73 74 |
# File 'lib/parse/cache/pool.rb', line 72 def store(key, value, = {}) @pool.with { |store| store.store(key, value, ) } end |