Class: Tina4::CacheBackends::RedisBackend
- Inherits:
-
BaseBackend
- Object
- BaseBackend
- Tina4::CacheBackends::RedisBackend
- Defined in:
- lib/tina4/cache_backends/redis_backend.rb
Overview
Redis / Valkey backend (parity with Python _RedisBackend). Uses the
redis gem if it is installed, otherwise falls back to the raw RESP
protocol over a TCP socket — so it works with zero runtime dependencies.
URL form: scheme://[user[:password]@]host[/db]. Credentials may also be supplied via TINA4_CACHE_USERNAME / TINA4_CACHE_PASSWORD (parity with TINA4_DATABASE_USERNAME / TINA4_DATABASE_PASSWORD). Wrong credentials cause available? to return false, so the factory falls back to file.
Direct Known Subclasses
Constant Summary collapse
- PREFIX =
"tina4:cache:"
Instance Method Summary collapse
- #available? ⇒ Boolean
-
#clear ⇒ Object
Remove EVERY entry this cache can serve - on BOTH transports.
- #delete(key) ⇒ Object
- #get(key) ⇒ Object
-
#initialize(url: "redis://localhost:6379", max_entries: 1000, name: "redis") ⇒ RedisBackend
constructor
A new instance of RedisBackend.
- #name ⇒ Object
- #set(key, value, ttl) ⇒ Object
-
#stats ⇒ Object
Report OUR entries, counted with a scoped SCAN.
Methods inherited from BaseBackend
Constructor Details
#initialize(url: "redis://localhost:6379", max_entries: 1000, name: "redis") ⇒ RedisBackend
Returns a new instance of RedisBackend.
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/tina4/cache_backends/redis_backend.rb', line 21 def initialize(url: "redis://localhost:6379", max_entries: 1000, name: "redis") @max_entries = max_entries @name = name @hits = 0 @misses = 0 @client = nil @use_raw = false parse_url(url) # Try the redis gem first. begin require "redis" kwargs = { host: @host, port: @port, db: @db, timeout: 5 } kwargs[:password] = @password if @password kwargs[:username] = @username if @username @client = Redis.new(**kwargs) @client.ping @available = true rescue LoadError, StandardError @client = nil @use_raw = true # No gem — usable only if the server answers (and authenticates). @available = probe end end |
Instance Method Details
#available? ⇒ Boolean
48 49 50 |
# File 'lib/tina4/cache_backends/redis_backend.rb', line 48 def available? @available end |
#clear ⇒ Object
Remove EVERY entry this cache can serve - on BOTH transports.
The raw RESP path used to be an empty branch with a comment: "no easy pattern delete, rely on TTL". That made clear() a no-op on the ZERO-DEPENDENCY default install, so a write never invalidated the persistent DB query cache and every instance kept serving pre-write rows until the TTL ran out (ADR-0024 rule 4: the default provider counts double).
SCAN, not KEYS: clear() runs on every write in persistent DB-cache mode, and KEYS is O(N) and blocks the whole server for its duration. Redis's own documentation says to prefer SCAN in production.
The scan is scoped to our prefix, so another application sharing the server is untouched. FLUSHALL/FLUSHDB would take their data with it and is never used here.
128 129 130 131 132 133 134 135 |
# File 'lib/tina4/cache_backends/redis_backend.rb', line 128 def clear @hits = 0 @misses = 0 return scan_delete_with_client if @client return unless @use_raw scan_delete_with_raw_resp end |
#delete(key) ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/tina4/cache_backends/redis_backend.rb', line 97 def delete(key) full_key = PREFIX + key if @client begin @client.del(full_key) > 0 rescue StandardError false end elsif @use_raw resp_command("DEL", full_key) == "1" else false end end |
#get(key) ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/tina4/cache_backends/redis_backend.rb', line 52 def get(key) full_key = PREFIX + key raw = if @client begin @client.get(full_key) rescue StandardError nil end elsif @use_raw resp_command("GET", full_key) end if raw.nil? @misses += 1 return nil end @hits += 1 begin JSON.parse(raw) rescue JSON::ParserError, TypeError raw end end |
#name ⇒ Object
148 149 150 |
# File 'lib/tina4/cache_backends/redis_backend.rb', line 148 def name @name end |
#set(key, value, ttl) ⇒ Object
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/tina4/cache_backends/redis_backend.rb', line 76 def set(key, value, ttl) full_key = PREFIX + key serialized = JSON.generate(value) if @client begin if ttl > 0 @client.setex(full_key, ttl, serialized) else @client.set(full_key, serialized) end rescue StandardError end elsif @use_raw if ttl > 0 resp_command("SETEX", full_key, ttl.to_s, serialized) else resp_command("SET", full_key, serialized) end end end |
#stats ⇒ Object
Report OUR entries, counted with a scoped SCAN.
size used to be a hardcoded 0 unless the redis GEM was loaded, so on
the ZERO-DEPENDENCY raw RESP transport - the default install - stats
reported 0 no matter how many entries were cached. Anything reading that
number (a dashboard, db.cache_stats, an operator checking whether a
clear worked) was reading a constant.
144 145 146 |
# File 'lib/tina4/cache_backends/redis_backend.rb', line 144 def stats { hits: @hits, misses: @misses, size: scan_count, backend: @name } end |