legion-cache 1.4.1 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +26 -0
- data/README.md +152 -30
- data/legion-cache.gemspec +3 -2
- data/lib/legion/cache/async_writer.rb +88 -36
- data/lib/legion/cache/cacheable.rb +37 -36
- data/lib/legion/cache/helper.rb +14 -12
- data/lib/legion/cache/local.rb +118 -109
- data/lib/legion/cache/memcached.rb +97 -23
- data/lib/legion/cache/memory.rb +222 -87
- data/lib/legion/cache/pool.rb +32 -15
- data/lib/legion/cache/reconnector.rb +50 -11
- data/lib/legion/cache/redis.rb +155 -118
- data/lib/legion/cache/redis_hash.rb +16 -13
- data/lib/legion/cache/settings.rb +8 -0
- data/lib/legion/cache/version.rb +1 -1
- data/lib/legion/cache.rb +222 -345
- metadata +35 -3
data/lib/legion/cache/redis.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'openssl'
|
|
4
4
|
require 'redis'
|
|
5
|
+
require 'uri'
|
|
5
6
|
require 'legion/logging/helper'
|
|
6
7
|
require 'legion/cache/pool'
|
|
7
8
|
require 'legion/cache/settings'
|
|
@@ -13,67 +14,69 @@ module Legion
|
|
|
13
14
|
extend self
|
|
14
15
|
extend Legion::Logging::Helper
|
|
15
16
|
|
|
16
|
-
def client(
|
|
17
|
-
username: nil, password: nil, logger: nil, **opts)
|
|
17
|
+
def client(**opts)
|
|
18
18
|
return @client unless @client.nil?
|
|
19
19
|
|
|
20
|
-
settings =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
replica = opts.delete(:replica) || false
|
|
26
|
-
fixed_hostname = opts.delete(:fixed_hostname)
|
|
27
|
-
db = opts.delete(:db)
|
|
28
|
-
reconnect_attempts = opts.delete(:reconnect_attempts) || [0, 0.5, 1]
|
|
29
|
-
|
|
30
|
-
@pool_size = pool_size
|
|
31
|
-
@timeout = timeout
|
|
32
|
-
@cluster_mode = Array(cluster).compact.any?
|
|
33
|
-
@component_logger = logger || log
|
|
34
|
-
|
|
35
|
-
@connection_opts = { username: username, password: password, timeout: @timeout }.compact
|
|
36
|
-
@connection_opts.merge!(redis_tls_options(port: resolve_primary_port(server: server, servers: servers, cluster: cluster)))
|
|
37
|
-
|
|
38
|
-
checkout_timeout = opts[:pool_checkout_timeout] || settings[:pool_checkout_timeout] || @timeout
|
|
39
|
-
@client = ConnectionPool.new(size: pool_size, timeout: checkout_timeout) do
|
|
40
|
-
build_redis_client(server: server, servers: servers, cluster: cluster,
|
|
41
|
-
replica: replica, fixed_hostname: fixed_hostname,
|
|
42
|
-
username: username, password: password, db: db,
|
|
43
|
-
reconnect_attempts: reconnect_attempts)
|
|
20
|
+
settings = redis_settings.merge(opts)
|
|
21
|
+
if opts.key?(:server) || opts.key?(:servers)
|
|
22
|
+
settings[:server] = opts[:server]
|
|
23
|
+
settings[:servers] = opts.fetch(:servers, [])
|
|
24
|
+
settings[:cluster] = nil unless opts.key?(:cluster)
|
|
44
25
|
end
|
|
45
|
-
@
|
|
46
|
-
|
|
26
|
+
@redis_settings = settings
|
|
27
|
+
@pool_size = settings[:pool_size] || 10
|
|
28
|
+
@timeout = settings[:timeout] || 5
|
|
29
|
+
raise ArgumentError, 'Redis timeout must be positive' unless @timeout.positive?
|
|
30
|
+
|
|
31
|
+
@cluster_mode = Array(settings[:cluster]).compact.any?
|
|
32
|
+
@component_logger = settings[:logger] || log
|
|
33
|
+
checkout_timeout = settings[:pool_checkout_timeout] || @timeout
|
|
34
|
+
pending_pool = ConnectionPool.new(size: @pool_size, timeout: checkout_timeout) do
|
|
35
|
+
build_redis_client(**settings.slice(:server, :servers, :cluster, :replica, :fixed_hostname,
|
|
36
|
+
:username, :password, :db, :reconnect_attempts))
|
|
37
|
+
end
|
|
38
|
+
pending_pool.with do |conn|
|
|
39
|
+
raise ::Redis::ConnectionError, 'Redis health check failed' unless conn.ping == 'PONG'
|
|
40
|
+
end
|
|
41
|
+
@client = pending_pool
|
|
42
|
+
cache_mark_connected(true)
|
|
47
43
|
@client
|
|
48
44
|
rescue StandardError => e
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
cache_mark_connected(false)
|
|
46
|
+
@client = nil
|
|
47
|
+
begin
|
|
48
|
+
pending_pool&.shutdown(&:close)
|
|
49
|
+
rescue StandardError => close_error
|
|
50
|
+
handle_exception(close_error, level: :warn, handled: true, operation: :redis_failed_pool_close)
|
|
51
|
+
end
|
|
52
|
+
handle_exception(e, level: :error, handled: false, operation: :redis_client)
|
|
52
53
|
raise
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def build_redis_client(server: nil, servers: [], cluster: nil, replica: false, fixed_hostname: nil, # rubocop:disable Metrics/ParameterLists
|
|
56
57
|
username: nil, password: nil, db: nil, reconnect_attempts: [0, 0.5, 1])
|
|
57
58
|
nodes = Array(cluster).compact
|
|
59
|
+
options = { username: username, password: password, db: db, timeout: @timeout || 5,
|
|
60
|
+
reconnect_attempts: reconnect_attempts }.compact
|
|
58
61
|
if nodes.any?
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
require 'redis-clustering'
|
|
63
|
+
raise ArgumentError, 'Redis Cluster only supports database 0' unless db.nil? || db.to_i.zero?
|
|
64
|
+
|
|
65
|
+
options.merge!(redis_tls_options(port: resolve_primary_port(cluster: nodes)))
|
|
66
|
+
options[:replica] = replica
|
|
67
|
+
options[:fixed_hostname] = fixed_hostname unless fixed_hostname.nil?
|
|
68
|
+
::Redis::Cluster.new(nodes: nodes, **options)
|
|
65
69
|
else
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
::Redis.new(**redis_opts)
|
|
70
|
+
address = (Array(servers) + Array(server)).first
|
|
71
|
+
if address.to_s.start_with?('redis://', 'rediss://')
|
|
72
|
+
options[:url] = address
|
|
73
|
+
port = URI.parse(address).port || 6379
|
|
74
|
+
else
|
|
75
|
+
host, port = Legion::Cache::Settings.parse_server_address(address, default_port: 6379)
|
|
76
|
+
options.merge!(host: host, port: port)
|
|
77
|
+
end
|
|
78
|
+
options.merge!(redis_tls_options(port: port))
|
|
79
|
+
::Redis.new(**options)
|
|
77
80
|
end
|
|
78
81
|
end
|
|
79
82
|
|
|
@@ -81,23 +84,47 @@ module Legion
|
|
|
81
84
|
@cluster_mode == true
|
|
82
85
|
end
|
|
83
86
|
|
|
87
|
+
def redis_key(key)
|
|
88
|
+
namespace = redis_settings[:namespace].to_s
|
|
89
|
+
namespace.empty? ? key.to_s : "#{namespace}:#{key}"
|
|
90
|
+
end
|
|
91
|
+
|
|
84
92
|
def get(key)
|
|
85
|
-
raw = client.with { |conn| conn.get(key) }
|
|
93
|
+
raw = client.with { |conn| conn.get(redis_key(key)) }
|
|
86
94
|
result = deserialize_value(raw)
|
|
87
95
|
log.debug { "[cache] GET #{key} hit=#{!result.nil?}" }
|
|
88
96
|
result
|
|
89
97
|
rescue StandardError => e
|
|
98
|
+
cache_backend_failed(e)
|
|
90
99
|
handle_exception(e, level: :warn, handled: true, operation: :redis_get, key: key)
|
|
91
100
|
nil
|
|
92
101
|
end
|
|
93
102
|
|
|
94
103
|
def fetch(key, ttl: nil)
|
|
104
|
+
redis_ttl(ttl)
|
|
95
105
|
result = get(key)
|
|
96
106
|
return result unless result.nil? && block_given?
|
|
97
107
|
|
|
98
108
|
result = yield
|
|
99
|
-
|
|
109
|
+
begin
|
|
110
|
+
set(key, result, ttl: ttl)
|
|
111
|
+
rescue StandardError => e
|
|
112
|
+
handle_exception(e, level: :warn, handled: true, operation: :redis_fetch_store, key: key)
|
|
113
|
+
end
|
|
114
|
+
result
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def set_nx(key, value, ttl: nil)
|
|
118
|
+
effective_ttl = redis_ttl(ttl)
|
|
119
|
+
serialized = serialize_value(value)
|
|
120
|
+
args = effective_ttl ? { ex: effective_ttl } : {}
|
|
121
|
+
result = client.with { |conn| conn.set(redis_key(key), serialized, nx: true, **args) == 'OK' }
|
|
122
|
+
log.debug { "[cache] SET_NX #{key} ttl=#{effective_ttl.inspect} result=#{result}" }
|
|
100
123
|
result
|
|
124
|
+
rescue StandardError => e
|
|
125
|
+
cache_backend_failed(e)
|
|
126
|
+
handle_exception(e, level: :error, handled: false, operation: :redis_set_nx, key: key, ttl: effective_ttl)
|
|
127
|
+
raise
|
|
101
128
|
end
|
|
102
129
|
|
|
103
130
|
def set(key, value, ttl: nil, **)
|
|
@@ -105,42 +132,77 @@ module Legion
|
|
|
105
132
|
end
|
|
106
133
|
|
|
107
134
|
def set_sync(key, value, ttl: nil, **)
|
|
108
|
-
effective_ttl = ttl
|
|
135
|
+
effective_ttl = redis_ttl(ttl)
|
|
109
136
|
args = {}
|
|
110
137
|
args[:ex] = effective_ttl unless effective_ttl.nil?
|
|
111
138
|
serialized = serialize_value(value)
|
|
112
|
-
result = client.with { |conn| conn.set(key, serialized, **args) == 'OK' }
|
|
139
|
+
result = client.with { |conn| conn.set(redis_key(key), serialized, **args) == 'OK' }
|
|
113
140
|
log.debug { "[cache] SET #{key} ttl=#{effective_ttl.inspect} success=#{result}" }
|
|
114
141
|
result
|
|
115
142
|
rescue StandardError => e
|
|
143
|
+
cache_backend_failed(e)
|
|
116
144
|
handle_exception(e, level: :error, handled: false, operation: :redis_set_sync, key: key, ttl: effective_ttl)
|
|
117
145
|
raise
|
|
118
146
|
end
|
|
119
147
|
|
|
148
|
+
def cas(key, ttl: nil)
|
|
149
|
+
effective_ttl = redis_ttl(ttl)
|
|
150
|
+
args = effective_ttl ? { ex: effective_ttl } : {}
|
|
151
|
+
qualified = redis_key(key)
|
|
152
|
+
user_error = nil
|
|
153
|
+
client.with do |conn|
|
|
154
|
+
10.times do
|
|
155
|
+
value = nil
|
|
156
|
+
committed = conn.watch(qualified) do |watched|
|
|
157
|
+
current = deserialize_value(watched.get(qualified))
|
|
158
|
+
begin
|
|
159
|
+
value = yield current
|
|
160
|
+
rescue StandardError => e
|
|
161
|
+
user_error = e
|
|
162
|
+
raise
|
|
163
|
+
end
|
|
164
|
+
serialized = serialize_value(value)
|
|
165
|
+
watched.multi { |transaction| transaction.set(qualified, serialized, **args) }
|
|
166
|
+
end
|
|
167
|
+
return value unless committed.nil?
|
|
168
|
+
end
|
|
169
|
+
raise ::Redis::CommandError, 'Redis CAS conflicted after 10 attempts'
|
|
170
|
+
end
|
|
171
|
+
rescue StandardError => e
|
|
172
|
+
raise if e.equal?(user_error)
|
|
173
|
+
|
|
174
|
+
cache_backend_failed(e)
|
|
175
|
+
handle_exception(e, level: :error, handled: false, operation: :redis_cas, key: key)
|
|
176
|
+
raise
|
|
177
|
+
end
|
|
178
|
+
|
|
120
179
|
def delete(key, **)
|
|
121
180
|
delete_sync(key)
|
|
122
181
|
end
|
|
123
182
|
|
|
124
183
|
def delete_sync(key)
|
|
125
|
-
result = client.with { |conn| conn.del(key) == 1 }
|
|
184
|
+
result = client.with { |conn| conn.del(redis_key(key)) == 1 }
|
|
126
185
|
log.debug { "[cache] DELETE #{key} success=#{result}" }
|
|
127
186
|
result
|
|
128
187
|
rescue StandardError => e
|
|
188
|
+
cache_backend_failed(e)
|
|
129
189
|
handle_exception(e, level: :error, handled: false, operation: :redis_delete_sync, key: key)
|
|
130
190
|
raise
|
|
131
191
|
end
|
|
132
192
|
|
|
133
193
|
def flush
|
|
134
194
|
result = client.with do |conn|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
195
|
+
prefix = redis_key('')
|
|
196
|
+
raise ArgumentError, 'Redis flush requires a namespace' if prefix.empty?
|
|
197
|
+
|
|
198
|
+
pattern = prefix.gsub(/[\\*?\[\]]/) { |character| "\\#{character}" }
|
|
199
|
+
conn.scan_each(match: "#{pattern}*", count: 100) { |key| conn.del(key) }
|
|
200
|
+
true
|
|
140
201
|
end
|
|
141
202
|
log.debug { '[cache] FLUSH completed' }
|
|
142
203
|
result
|
|
143
204
|
rescue StandardError => e
|
|
205
|
+
cache_backend_failed(e)
|
|
144
206
|
handle_exception(e, level: :warn, handled: true, operation: :redis_flush)
|
|
145
207
|
nil
|
|
146
208
|
end
|
|
@@ -149,18 +211,19 @@ module Legion
|
|
|
149
211
|
keys = keys.flatten
|
|
150
212
|
return {} if keys.empty?
|
|
151
213
|
|
|
152
|
-
|
|
214
|
+
qualified = keys.map { |key| redis_key(key) }
|
|
215
|
+
values = client.with do |conn|
|
|
153
216
|
if cluster_mode?
|
|
154
|
-
cluster_mget(conn,
|
|
217
|
+
cluster_mget(conn, qualified).values_at(*qualified)
|
|
155
218
|
else
|
|
156
|
-
|
|
157
|
-
keys.zip(values).to_h
|
|
219
|
+
conn.mget(*qualified)
|
|
158
220
|
end
|
|
159
221
|
end
|
|
160
|
-
result =
|
|
222
|
+
result = keys.zip(values.map { |value| deserialize_value(value) }).to_h
|
|
161
223
|
log.debug { "[cache] MGET keys=#{keys.size}" }
|
|
162
224
|
result
|
|
163
225
|
rescue StandardError => e
|
|
226
|
+
cache_backend_failed(e)
|
|
164
227
|
handle_exception(e, level: :warn, handled: true, operation: :redis_mget, key_count: keys.size)
|
|
165
228
|
{}
|
|
166
229
|
end
|
|
@@ -209,12 +272,16 @@ module Legion
|
|
|
209
272
|
raw
|
|
210
273
|
end
|
|
211
274
|
|
|
212
|
-
def
|
|
213
|
-
return
|
|
275
|
+
def redis_settings
|
|
276
|
+
return @redis_settings if @redis_settings
|
|
277
|
+
return @cache_settings if @cache_settings
|
|
214
278
|
|
|
215
|
-
Legion::Settings
|
|
216
|
-
|
|
217
|
-
|
|
279
|
+
shared = defined?(Legion::Settings) ? Legion::Settings[:cache] : nil
|
|
280
|
+
Legion::Cache::Settings.default.merge(shared || {})
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def default_ttl
|
|
284
|
+
redis_settings[:default_ttl]
|
|
218
285
|
end
|
|
219
286
|
|
|
220
287
|
def cluster_mget(conn, keys)
|
|
@@ -227,61 +294,34 @@ module Legion
|
|
|
227
294
|
result
|
|
228
295
|
end
|
|
229
296
|
|
|
230
|
-
def cluster_mset(conn, hash)
|
|
231
|
-
groups = group_keys_by_slot(hash.keys)
|
|
232
|
-
groups.each_value do |group_keys|
|
|
233
|
-
pairs = group_keys.flat_map { |k| [k, hash[k]] }
|
|
234
|
-
conn.mset(*pairs)
|
|
235
|
-
end
|
|
236
|
-
true
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
def cluster_flush(conn)
|
|
240
|
-
node_info = conn.cluster('nodes')
|
|
241
|
-
primaries = node_info.lines.select { |l| l.include?('master') }.map { |l| l.split[1].split('@').first }
|
|
242
|
-
primaries.each do |addr|
|
|
243
|
-
host, port = Legion::Cache::Settings.parse_server_address(addr, default_port: 6379)
|
|
244
|
-
node = ::Redis.new(host: host, port: port.to_i, **(@connection_opts || {}))
|
|
245
|
-
node.flushdb
|
|
246
|
-
node.close
|
|
247
|
-
end
|
|
248
|
-
true
|
|
249
|
-
rescue StandardError => e
|
|
250
|
-
handle_exception(e, level: :warn, handled: true, operation: :cluster_flush, fallback: :single_flushdb)
|
|
251
|
-
conn.flushdb == 'OK'
|
|
252
|
-
end
|
|
253
|
-
|
|
254
297
|
def group_keys_by_slot(keys)
|
|
255
|
-
|
|
256
|
-
keys.group_by { |k| ::Redis::Cluster::KeySlotConverter.convert(k) }
|
|
257
|
-
else
|
|
258
|
-
{ 0 => keys }
|
|
259
|
-
end
|
|
298
|
+
keys.group_by { |key| ::RedisClient::Cluster::KeySlotConverter.convert(key) }
|
|
260
299
|
end
|
|
261
300
|
|
|
262
|
-
def
|
|
263
|
-
|
|
264
|
-
return
|
|
301
|
+
def redis_ttl(ttl)
|
|
302
|
+
value = ttl.nil? ? default_ttl : ttl
|
|
303
|
+
return nil if value.nil?
|
|
265
304
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
'unknown'
|
|
305
|
+
raise ArgumentError, 'TTL must not be negative' if value.negative?
|
|
306
|
+
|
|
307
|
+
value.zero? ? nil : value
|
|
270
308
|
end
|
|
271
309
|
|
|
272
310
|
def resolve_primary_port(server: nil, servers: [], cluster: nil)
|
|
273
|
-
|
|
274
|
-
return 6379 if
|
|
311
|
+
address = Array(cluster).compact.first || (Array(servers) + Array(server)).first
|
|
312
|
+
return address.fetch(:port, 6379).to_i if address.is_a?(Hash)
|
|
313
|
+
return URI.parse(address).port || 6379 if address.to_s.start_with?('redis://', 'rediss://')
|
|
275
314
|
|
|
276
|
-
|
|
277
|
-
_, port = Legion::Cache::Settings.parse_server_address(resolved.first, default_port: 6379)
|
|
278
|
-
port.to_i
|
|
279
|
-
rescue StandardError
|
|
280
|
-
6379
|
|
315
|
+
Legion::Cache::Settings.parse_server_address(address, default_port: 6379).last
|
|
281
316
|
end
|
|
282
317
|
|
|
283
318
|
def redis_tls_options(port:)
|
|
284
|
-
|
|
319
|
+
unless defined?(Legion::Crypt::TLS)
|
|
320
|
+
settings = cache_tls_settings
|
|
321
|
+
return {} if settings.empty? || settings[:enabled] == false
|
|
322
|
+
|
|
323
|
+
raise ArgumentError, 'Legion::Crypt::TLS is required for configured Redis TLS'
|
|
324
|
+
end
|
|
285
325
|
|
|
286
326
|
tls = Legion::Crypt::TLS.resolve(cache_tls_settings, port: port)
|
|
287
327
|
return {} unless tls[:enabled]
|
|
@@ -290,17 +330,14 @@ module Legion
|
|
|
290
330
|
verify_mode: tls[:verify] == :none ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
|
291
331
|
}
|
|
292
332
|
ssl_params[:ca_file] = tls[:ca] if tls[:ca]
|
|
333
|
+
ssl_params[:cert] = OpenSSL::X509::Certificate.new(File.read(tls[:cert])) if tls[:cert]
|
|
334
|
+
ssl_params[:key] = OpenSSL::PKey.read(File.read(tls[:key])) if tls[:key]
|
|
293
335
|
|
|
294
336
|
{ ssl: true, ssl_params: ssl_params }
|
|
295
337
|
end
|
|
296
338
|
|
|
297
339
|
def cache_tls_settings
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
Legion::Settings[:cache][:tls] || {}
|
|
301
|
-
rescue StandardError => e
|
|
302
|
-
handle_exception(e, level: :warn, handled: true, operation: :cache_tls_settings)
|
|
303
|
-
{}
|
|
340
|
+
redis_settings[:tls] || {}
|
|
304
341
|
end
|
|
305
342
|
end
|
|
306
343
|
end
|
|
@@ -28,7 +28,7 @@ module Legion
|
|
|
28
28
|
|
|
29
29
|
Legion::Cache.pool.with do |conn|
|
|
30
30
|
flat = hash.flat_map { |k, v| [k.to_s, v.to_s] }
|
|
31
|
-
conn.hset(key, *flat)
|
|
31
|
+
conn.hset(Legion::Cache.redis_key(key), *flat)
|
|
32
32
|
end
|
|
33
33
|
log.debug "[cache:redis_hash] HSET #{key} fields=#{hash.size}"
|
|
34
34
|
true
|
|
@@ -42,7 +42,7 @@ module Legion
|
|
|
42
42
|
return nil unless redis_available?
|
|
43
43
|
|
|
44
44
|
result = Legion::Cache.pool.with do |conn|
|
|
45
|
-
conn.hgetall(key)
|
|
45
|
+
conn.hgetall(Legion::Cache.redis_key(key))
|
|
46
46
|
end
|
|
47
47
|
log.debug "[cache:redis_hash] HGETALL #{key} fields=#{result.size}"
|
|
48
48
|
result
|
|
@@ -56,7 +56,7 @@ module Legion
|
|
|
56
56
|
return 0 unless redis_available?
|
|
57
57
|
|
|
58
58
|
result = Legion::Cache.pool.with do |conn|
|
|
59
|
-
conn.hdel(key, *fields)
|
|
59
|
+
conn.hdel(Legion::Cache.redis_key(key), *fields)
|
|
60
60
|
end
|
|
61
61
|
log.debug "[cache:redis_hash] HDEL #{key} fields=#{fields.size} removed=#{result}"
|
|
62
62
|
result
|
|
@@ -70,7 +70,7 @@ module Legion
|
|
|
70
70
|
return false unless redis_available?
|
|
71
71
|
|
|
72
72
|
Legion::Cache.pool.with do |conn|
|
|
73
|
-
conn.zadd(key, score.to_f, member.to_s)
|
|
73
|
+
conn.zadd(Legion::Cache.redis_key(key), score.to_f, member.to_s)
|
|
74
74
|
end
|
|
75
75
|
log.debug "[cache:redis_hash] ZADD #{key} member=#{member}"
|
|
76
76
|
true
|
|
@@ -88,7 +88,7 @@ module Legion
|
|
|
88
88
|
opts[:limit] = limit if limit
|
|
89
89
|
|
|
90
90
|
result = Legion::Cache.pool.with do |conn|
|
|
91
|
-
conn.zrangebyscore(key, min, max, **opts)
|
|
91
|
+
conn.zrangebyscore(Legion::Cache.redis_key(key), min, max, **opts)
|
|
92
92
|
end
|
|
93
93
|
log.debug "[cache:redis_hash] ZRANGEBYSCORE #{key} results=#{result.size}"
|
|
94
94
|
result
|
|
@@ -102,7 +102,7 @@ module Legion
|
|
|
102
102
|
return false unless redis_available?
|
|
103
103
|
|
|
104
104
|
Legion::Cache.pool.with do |conn|
|
|
105
|
-
conn.zrem(key, member.to_s)
|
|
105
|
+
conn.zrem(Legion::Cache.redis_key(key), member.to_s)
|
|
106
106
|
end
|
|
107
107
|
log.debug "[cache:redis_hash] ZREM #{key} member=#{member}"
|
|
108
108
|
true
|
|
@@ -113,19 +113,22 @@ module Legion
|
|
|
113
113
|
|
|
114
114
|
# Set a TTL (in seconds) on a key.
|
|
115
115
|
def expire(key, seconds)
|
|
116
|
+
raise ArgumentError, 'TTL must not be negative' if seconds.negative?
|
|
116
117
|
return false unless redis_available?
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
begin
|
|
120
|
+
Legion::Cache.pool.with do |conn|
|
|
121
|
+
qualified = Legion::Cache.redis_key(key)
|
|
122
|
+
seconds.zero? ? conn.persist(qualified) : conn.expire(qualified, seconds)
|
|
123
|
+
end
|
|
124
|
+
rescue StandardError => e
|
|
125
|
+
log_redis_error('expire', e)
|
|
126
|
+
false
|
|
120
127
|
end
|
|
121
|
-
log.debug "[cache:redis_hash] EXPIRE #{key} seconds=#{seconds} success=#{result}"
|
|
122
|
-
result
|
|
123
|
-
rescue StandardError => e
|
|
124
|
-
log_redis_error('expire', e)
|
|
125
|
-
false
|
|
126
128
|
end
|
|
127
129
|
|
|
128
130
|
def log_redis_error(method, error)
|
|
131
|
+
Legion::Cache.send(:cache_backend_failed, error)
|
|
129
132
|
handle_exception(error, level: :warn, handled: true, operation: method)
|
|
130
133
|
end
|
|
131
134
|
end
|
|
@@ -81,6 +81,14 @@ module Legion
|
|
|
81
81
|
|
|
82
82
|
DEFAULT_PORTS = { 'dalli' => 11_211, 'redis' => 6379 }.freeze
|
|
83
83
|
|
|
84
|
+
def self.connection_options(settings, overrides)
|
|
85
|
+
resolved = settings.merge(overrides)
|
|
86
|
+
resolved[:servers] = [] if overrides.key?(:server) && !overrides.key?(:servers)
|
|
87
|
+
resolved[:server] = nil if overrides.key?(:servers) && !overrides.key?(:server)
|
|
88
|
+
resolved[:cluster] = nil if (overrides.key?(:server) || overrides.key?(:servers)) && !overrides.key?(:cluster)
|
|
89
|
+
resolved
|
|
90
|
+
end
|
|
91
|
+
|
|
84
92
|
def self.resolve_servers(driver:, server: nil, servers: [], port: nil)
|
|
85
93
|
gem_driver = normalize_driver(driver)
|
|
86
94
|
port ||= DEFAULT_PORTS.fetch(gem_driver, 11_211)
|