legion-cache 1.4.2 → 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 +90 -26
- data/lib/legion/cache/memory.rb +220 -104
- data/lib/legion/cache/pool.rb +32 -15
- data/lib/legion/cache/reconnector.rb +50 -11
- data/lib/legion/cache/redis.rb +146 -120
- 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 +220 -356
- metadata +35 -3
data/lib/legion/cache/helper.rb
CHANGED
|
@@ -310,10 +310,11 @@ module Legion
|
|
|
310
310
|
end
|
|
311
311
|
|
|
312
312
|
def memcached_hash_merge(full_key, new_fields)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
313
|
+
result = Legion::Cache.cas(full_key, ttl: cache_default_ttl) do |raw|
|
|
314
|
+
current = raw.nil? ? {} : Legion::JSON.load(raw).transform_keys(&:to_s)
|
|
315
|
+
Legion::JSON.dump(current.merge(new_fields.transform_keys(&:to_s)))
|
|
316
|
+
end
|
|
317
|
+
!!result
|
|
317
318
|
end
|
|
318
319
|
|
|
319
320
|
def memcached_hash_load(full_key)
|
|
@@ -329,14 +330,15 @@ module Legion
|
|
|
329
330
|
end
|
|
330
331
|
|
|
331
332
|
def memcached_hash_delete_fields(full_key, fields)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
333
|
+
removed = 0
|
|
334
|
+
result = Legion::Cache.cas(full_key, ttl: cache_default_ttl) do |raw|
|
|
335
|
+
current = raw.nil? ? {} : Legion::JSON.load(raw).transform_keys(&:to_s)
|
|
336
|
+
str_fields = fields.map(&:to_s).uniq
|
|
337
|
+
removed = str_fields.count { |field| current.key?(field) }
|
|
338
|
+
str_fields.each { |field| current.delete(field) }
|
|
339
|
+
Legion::JSON.dump(current)
|
|
340
|
+
end
|
|
341
|
+
result ? removed : 0
|
|
340
342
|
end
|
|
341
343
|
|
|
342
344
|
def raise_sorted_set_unsupported(method)
|
data/lib/legion/cache/local.rb
CHANGED
|
@@ -3,55 +3,58 @@
|
|
|
3
3
|
require 'concurrent'
|
|
4
4
|
require 'legion/logging/helper'
|
|
5
5
|
require 'legion/cache/settings'
|
|
6
|
+
require 'legion/cache/async_writer'
|
|
7
|
+
require 'legion/cache/pool'
|
|
6
8
|
|
|
7
9
|
module Legion
|
|
8
10
|
module Cache
|
|
9
11
|
module Local
|
|
10
12
|
@connected = Concurrent::AtomicBoolean.new(false)
|
|
13
|
+
@async_writer = Legion::Cache::AsyncWriter.new(settings_key: :cache_local)
|
|
11
14
|
|
|
12
15
|
class << self
|
|
13
16
|
include Legion::Logging::Helper
|
|
17
|
+
include Legion::Cache::Pool
|
|
14
18
|
|
|
15
|
-
def setup(**)
|
|
16
|
-
return unless enabled?
|
|
17
|
-
return if connected?
|
|
18
|
-
|
|
19
|
-
settings = local_settings
|
|
20
|
-
return unless settings[:enabled]
|
|
19
|
+
def setup(**opts)
|
|
20
|
+
return false unless enabled?
|
|
21
|
+
return true if connected?
|
|
21
22
|
|
|
23
|
+
@connection_options = Legion::Cache::Settings.connection_options(@connection_options || {}, opts)
|
|
24
|
+
settings = Legion::Cache::Settings.connection_options(local_settings, @connection_options)
|
|
22
25
|
driver_name = settings[:driver] || Legion::Cache::Settings.driver
|
|
23
26
|
@driver_name = Legion::Cache::Settings.normalize_driver(driver_name)
|
|
24
27
|
@driver = build_driver(driver_name)
|
|
25
|
-
@driver.
|
|
26
|
-
@
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
@driver.instance_variable_set(:@cache_settings, settings)
|
|
29
|
+
@driver.instance_variable_set(:@cache_failure_callback, ->(error) { cache_backend_failed(error) })
|
|
30
|
+
@driver.client(**settings, logger: log)
|
|
31
|
+
@async_writer.start
|
|
32
|
+
cache_mark_connected(true)
|
|
33
|
+
true
|
|
29
34
|
rescue StandardError => e
|
|
35
|
+
@driver&.close
|
|
36
|
+
@driver = nil
|
|
37
|
+
cache_mark_connected(false)
|
|
30
38
|
handle_exception(e, level: :warn, handled: true, operation: :cache_local_setup, driver: driver_name)
|
|
31
|
-
|
|
39
|
+
raise unless cache_connection_error?(e)
|
|
40
|
+
|
|
41
|
+
false
|
|
32
42
|
end
|
|
33
43
|
|
|
34
44
|
def shutdown
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
log.info 'Shutting down Legion::Cache::Local'
|
|
45
|
+
@async_writer.stop
|
|
38
46
|
@driver&.close
|
|
39
47
|
@driver = nil
|
|
40
48
|
@driver_name = nil
|
|
41
|
-
|
|
49
|
+
cache_mark_connected(false)
|
|
42
50
|
end
|
|
43
51
|
|
|
44
52
|
def enabled?
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Legion::Settings.dig(:cache_local, :enabled) != false
|
|
48
|
-
rescue StandardError => e
|
|
49
|
-
handle_exception(e, level: :warn, handled: true, operation: :cache_local_enabled)
|
|
50
|
-
true
|
|
53
|
+
!defined?(Legion::Settings) || Legion::Settings.dig(:cache_local, :enabled) != false
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
def connected?
|
|
54
|
-
|
|
57
|
+
enabled? && @connected.value
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
def driver_name
|
|
@@ -59,154 +62,160 @@ module Legion
|
|
|
59
62
|
end
|
|
60
63
|
|
|
61
64
|
def get(key)
|
|
62
|
-
|
|
63
|
-
log.debug { "[cache:local] GET #{key} hit=#{!result.nil?}" }
|
|
64
|
-
result
|
|
65
|
-
rescue StandardError => e
|
|
66
|
-
handle_exception(e, level: :warn, handled: true, operation: :cache_local_get, key: key)
|
|
67
|
-
nil
|
|
65
|
+
local_operation(:get, key, failure: nil)
|
|
68
66
|
end
|
|
69
67
|
|
|
70
|
-
def set(key, value, ttl: nil, **)
|
|
71
|
-
|
|
68
|
+
def set(key, value, ttl: nil, async: true, phi: false, **) # rubocop:disable Metrics/ParameterLists
|
|
69
|
+
return false unless enabled? && connected?
|
|
70
|
+
|
|
71
|
+
ttl = local_write_ttl(ttl, phi: phi)
|
|
72
|
+
if async
|
|
73
|
+
@async_writer.enqueue(key: key) { set_sync(key, value, ttl: ttl) }
|
|
74
|
+
else
|
|
75
|
+
set_sync(key, value, ttl: ttl)
|
|
76
|
+
end
|
|
72
77
|
end
|
|
73
78
|
|
|
74
|
-
def set_sync(key, value, ttl: nil, **)
|
|
75
|
-
|
|
76
|
-
result = @driver.set_sync(key, value, ttl: effective_ttl)
|
|
77
|
-
log.debug { "[cache:local] SET #{key} ttl=#{effective_ttl} success=#{result}" }
|
|
78
|
-
result
|
|
79
|
-
rescue StandardError => e
|
|
80
|
-
handle_exception(e, level: :error, handled: false, operation: :cache_local_set_sync, key: key, ttl: effective_ttl)
|
|
81
|
-
raise
|
|
79
|
+
def set_sync(key, value, ttl: nil, phi: false, **)
|
|
80
|
+
local_operation(:set_sync, key, value, ttl: local_write_ttl(ttl, phi: phi), failure: false)
|
|
82
81
|
end
|
|
83
82
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
def set_nx(key, value, ttl: nil, phi: false)
|
|
84
|
+
local_operation(:set_nx, key, value, ttl: local_write_ttl(ttl, phi: phi), failure: false)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def cas(key, ttl: nil, phi: false, &)
|
|
88
|
+
local_operation(:cas, key, ttl: local_write_ttl(ttl, phi: phi), failure: false, &)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def fetch(key, ttl: nil)
|
|
92
|
+
cached = get(key)
|
|
93
|
+
return cached unless cached.nil?
|
|
94
|
+
return nil unless block_given?
|
|
95
|
+
|
|
96
|
+
value = yield
|
|
97
|
+
set_sync(key, value, ttl: ttl)
|
|
98
|
+
value
|
|
91
99
|
end
|
|
92
100
|
|
|
93
|
-
def delete(key, **)
|
|
94
|
-
|
|
101
|
+
def delete(key, async: true, **)
|
|
102
|
+
return false unless enabled? && connected?
|
|
103
|
+
|
|
104
|
+
if async
|
|
105
|
+
@async_writer.enqueue(key: key) { delete_sync(key) }
|
|
106
|
+
else
|
|
107
|
+
delete_sync(key)
|
|
108
|
+
end
|
|
95
109
|
end
|
|
96
110
|
|
|
97
111
|
def delete_sync(key)
|
|
98
|
-
|
|
99
|
-
log.debug { "[cache:local] DELETE #{key} success=#{result}" }
|
|
100
|
-
result
|
|
101
|
-
rescue StandardError => e
|
|
102
|
-
handle_exception(e, level: :error, handled: false, operation: :cache_local_delete_sync, key: key)
|
|
103
|
-
raise
|
|
112
|
+
local_operation(:delete_sync, key, failure: false)
|
|
104
113
|
end
|
|
105
114
|
|
|
106
115
|
def flush
|
|
107
|
-
|
|
108
|
-
log.debug { '[cache:local] FLUSH completed' }
|
|
109
|
-
result
|
|
110
|
-
rescue StandardError => e
|
|
111
|
-
handle_exception(e, level: :warn, handled: true, operation: :cache_local_flush)
|
|
112
|
-
nil
|
|
116
|
+
local_operation(:flush, failure: false)
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
def mget(*keys)
|
|
116
|
-
|
|
117
|
-
return {} if keys.empty?
|
|
120
|
+
return {} unless enabled?
|
|
118
121
|
|
|
119
|
-
keys.to_h { |key| [key, get(key)] }
|
|
122
|
+
keys.flatten.to_h { |key| [key, get(key)] }
|
|
120
123
|
end
|
|
121
124
|
|
|
122
|
-
def mset(hash, ttl: nil, **)
|
|
123
|
-
return
|
|
125
|
+
def mset(hash, ttl: nil, async: true, phi: false, **)
|
|
126
|
+
return false unless enabled?
|
|
124
127
|
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
accepted = true
|
|
129
|
+
hash.each do |key, value|
|
|
130
|
+
result = set(key, value, ttl: ttl, async: async, phi: phi)
|
|
131
|
+
accepted = false unless result
|
|
132
|
+
end
|
|
133
|
+
accepted
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def mset_sync(hash, ttl: nil, phi: false, **)
|
|
137
|
+
mset(hash, ttl: ttl, async: false, phi: phi)
|
|
127
138
|
end
|
|
128
139
|
|
|
129
140
|
def stats
|
|
130
|
-
{
|
|
131
|
-
driver: driver_name,
|
|
132
|
-
servers: local_servers,
|
|
133
|
-
enabled: enabled?,
|
|
134
|
-
connected: connected?
|
|
135
|
-
}.freeze
|
|
136
|
-
rescue StandardError => e
|
|
137
|
-
handle_exception(e, level: :warn, handled: true, operation: :cache_local_stats)
|
|
138
|
-
{ error: e.message }.freeze
|
|
141
|
+
{ driver: driver_name, servers: Array(local_settings[:servers]), enabled: enabled?, connected: connected? }.freeze
|
|
139
142
|
end
|
|
140
143
|
|
|
141
|
-
def client
|
|
142
|
-
|
|
144
|
+
def client(**)
|
|
145
|
+
return nil unless enabled?
|
|
146
|
+
|
|
147
|
+
setup(**) unless @driver
|
|
148
|
+
@driver&.client if connected?
|
|
143
149
|
end
|
|
144
150
|
|
|
145
151
|
def close
|
|
146
|
-
|
|
147
|
-
@driver = nil
|
|
148
|
-
@driver_name = nil
|
|
149
|
-
@connected.make_false
|
|
150
|
-
log.info 'Legion::Cache::Local pool closed'
|
|
151
|
-
@connected
|
|
152
|
+
shutdown
|
|
152
153
|
end
|
|
153
154
|
|
|
154
|
-
def restart(**
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
@connected.make_true
|
|
158
|
-
log.info 'Legion::Cache::Local pool restarted'
|
|
159
|
-
@connected
|
|
155
|
+
def restart(**)
|
|
156
|
+
shutdown
|
|
157
|
+
setup(**)
|
|
160
158
|
end
|
|
161
159
|
|
|
162
160
|
def size
|
|
163
|
-
@driver.size
|
|
161
|
+
connected? ? @driver.size : 0
|
|
164
162
|
end
|
|
165
163
|
|
|
166
164
|
def available
|
|
167
|
-
@driver.available
|
|
165
|
+
connected? ? @driver.available : 0
|
|
168
166
|
end
|
|
169
167
|
|
|
170
168
|
def pool_size
|
|
171
|
-
@driver.pool_size
|
|
169
|
+
connected? ? @driver.pool_size : 0
|
|
172
170
|
end
|
|
173
171
|
|
|
174
172
|
def timeout
|
|
175
|
-
@driver.timeout
|
|
173
|
+
connected? ? @driver.timeout : 0
|
|
176
174
|
end
|
|
177
175
|
|
|
178
176
|
def reset!
|
|
179
|
-
|
|
180
|
-
@
|
|
181
|
-
@connected.make_false
|
|
182
|
-
log.debug 'Legion::Cache::Local state reset'
|
|
183
|
-
@connected
|
|
177
|
+
shutdown
|
|
178
|
+
@connection_options = nil
|
|
184
179
|
end
|
|
185
180
|
|
|
186
181
|
private
|
|
187
182
|
|
|
188
|
-
def
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
183
|
+
def local_operation(operation, *, failure:, **, &block)
|
|
184
|
+
return failure unless enabled? && connected? && @driver
|
|
185
|
+
|
|
186
|
+
user_error = nil
|
|
187
|
+
guarded = if block
|
|
188
|
+
proc do |value|
|
|
189
|
+
block.call(value)
|
|
190
|
+
rescue StandardError => e
|
|
191
|
+
user_error = e
|
|
192
|
+
raise
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
@driver.public_send(operation, *, **, &guarded)
|
|
196
|
+
rescue StandardError => e
|
|
197
|
+
raise if e.equal?(user_error) || !cache_connection_error?(e)
|
|
198
|
+
|
|
199
|
+
cache_backend_failed(e)
|
|
200
|
+
handle_exception(e, level: :warn, handled: true, operation: operation)
|
|
201
|
+
failure
|
|
192
202
|
end
|
|
193
203
|
|
|
194
|
-
def
|
|
195
|
-
|
|
204
|
+
def local_write_ttl(ttl, phi:)
|
|
205
|
+
effective = ttl || local_settings[:default_ttl] || 21_600
|
|
206
|
+
return effective unless phi
|
|
196
207
|
|
|
197
|
-
Legion::
|
|
198
|
-
rescue StandardError
|
|
199
|
-
21_600
|
|
208
|
+
Legion::Cache.enforce_phi_ttl(effective, phi: phi)
|
|
200
209
|
end
|
|
201
210
|
|
|
202
211
|
def build_driver(driver_name)
|
|
203
212
|
case Legion::Cache::Settings.normalize_driver(driver_name)
|
|
204
213
|
when 'redis'
|
|
205
214
|
require 'legion/cache/redis'
|
|
206
|
-
Legion::Cache::Redis
|
|
215
|
+
Object.new.extend(Legion::Cache::Redis, Legion::Logging::Helper)
|
|
207
216
|
else
|
|
208
217
|
require 'legion/cache/memcached'
|
|
209
|
-
Legion::Cache::Memcached
|
|
218
|
+
Object.new.extend(Legion::Cache::Memcached, Legion::Logging::Helper)
|
|
210
219
|
end
|
|
211
220
|
end
|
|
212
221
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'openssl'
|
|
4
|
+
require 'timeout'
|
|
4
5
|
require 'dalli'
|
|
5
6
|
require 'legion/logging/helper'
|
|
6
7
|
require 'legion/cache/pool'
|
|
@@ -16,12 +17,17 @@ module Legion
|
|
|
16
17
|
username: nil, password: nil, logger: nil, **opts)
|
|
17
18
|
return @client unless @client.nil?
|
|
18
19
|
|
|
19
|
-
settings = defined?(Legion::Settings) ? Legion::Settings[:cache] : {}
|
|
20
|
-
|
|
20
|
+
settings = @cache_settings || (defined?(Legion::Settings) ? Legion::Settings[:cache] : {})
|
|
21
|
+
if server.nil? && servers.nil?
|
|
22
|
+
server = settings[:server]
|
|
23
|
+
servers = settings[:servers]
|
|
24
|
+
end
|
|
21
25
|
@component_logger = logger || log
|
|
22
26
|
|
|
23
27
|
@pool_size = pool_size || settings[:pool_size] || 10
|
|
24
28
|
@timeout = timeout || settings[:timeout] || 5
|
|
29
|
+
health_timeout = Float(@timeout)
|
|
30
|
+
raise ArgumentError, 'memcached timeout must be finite and positive' unless health_timeout.finite? && health_timeout.positive?
|
|
25
31
|
|
|
26
32
|
resolved = Legion::Cache::Settings.resolve_servers(
|
|
27
33
|
driver: 'memcached', server: server, servers: Array(servers)
|
|
@@ -33,22 +39,29 @@ module Legion
|
|
|
33
39
|
cache_opts[:serializer] ||= Legion::JSON
|
|
34
40
|
cache_opts[:username] = username unless username.nil?
|
|
35
41
|
cache_opts[:password] = password unless password.nil?
|
|
42
|
+
cache_opts[:socket_timeout] ||= health_timeout
|
|
43
|
+
@memcached_settings = cache_opts
|
|
36
44
|
|
|
37
45
|
tls_ctx = memcached_tls_context(port: resolved.first.split(':').last.to_i)
|
|
38
|
-
cache_opts[:ssl_context]
|
|
46
|
+
cache_opts[:ssl_context] ||= tls_ctx if tls_ctx
|
|
39
47
|
|
|
40
48
|
checkout_timeout = opts[:pool_checkout_timeout] || settings[:pool_checkout_timeout] || @timeout
|
|
41
|
-
|
|
49
|
+
pool = ConnectionPool.new(size: @pool_size, timeout: checkout_timeout) do
|
|
42
50
|
Dalli::Client.new(resolved, cache_opts)
|
|
43
51
|
end
|
|
44
52
|
|
|
45
|
-
|
|
53
|
+
Timeout.timeout(health_timeout) { pool.with(&:alive!) }
|
|
54
|
+
@client = pool
|
|
55
|
+
cache_mark_connected(true)
|
|
46
56
|
log.info "Memcached connected to #{resolved.join(', ')}"
|
|
47
57
|
@client
|
|
48
58
|
rescue StandardError => e
|
|
49
59
|
handle_exception(e, level: :error, handled: false, operation: :memcached_client,
|
|
50
60
|
server: server, servers: Array(servers))
|
|
51
|
-
|
|
61
|
+
cache_mark_connected(false)
|
|
62
|
+
cache_backend_failed(e)
|
|
63
|
+
memcached_discard_pool(pool)
|
|
64
|
+
@client = nil
|
|
52
65
|
raise
|
|
53
66
|
end
|
|
54
67
|
|
|
@@ -57,31 +70,50 @@ module Legion
|
|
|
57
70
|
log.debug { "[cache] GET #{key} hit=#{!result.nil?}" }
|
|
58
71
|
result
|
|
59
72
|
rescue StandardError => e
|
|
73
|
+
cache_backend_failed(e)
|
|
60
74
|
handle_exception(e, level: :warn, handled: true, operation: :memcached_get, key: key)
|
|
61
75
|
nil
|
|
62
76
|
end
|
|
63
77
|
|
|
64
|
-
def fetch(key, ttl: nil
|
|
65
|
-
result =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
def fetch(key, ttl: nil)
|
|
79
|
+
result = get(key)
|
|
80
|
+
return result unless result.nil? && block_given?
|
|
81
|
+
|
|
82
|
+
result = yield
|
|
83
|
+
begin
|
|
84
|
+
set_nx(key, result, ttl: ttl)
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
handle_exception(e, level: :warn, handled: true, operation: :memcached_fetch, key: key, ttl: ttl)
|
|
71
87
|
end
|
|
72
|
-
log.debug { "[cache] FETCH #{key} hit=#{!result.nil?}" }
|
|
73
88
|
result
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def cas(key, ttl: nil)
|
|
92
|
+
raise ArgumentError, 'a block is required' unless block_given?
|
|
93
|
+
|
|
94
|
+
effective_ttl = ttl || default_ttl
|
|
95
|
+
20.times do
|
|
96
|
+
value, token = memcached_cas_request(key) { |conn| conn.get_cas(key) }
|
|
97
|
+
updated = yield value
|
|
98
|
+
committed = memcached_cas_request(key) do |conn|
|
|
99
|
+
if token.nil?
|
|
100
|
+
conn.add(key, updated, effective_ttl)
|
|
101
|
+
else
|
|
102
|
+
conn.set_cas(key, updated, token, effective_ttl)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
return updated if committed
|
|
106
|
+
end
|
|
107
|
+
raise 'Memcached CAS contention exceeded the retry limit'
|
|
77
108
|
end
|
|
78
109
|
|
|
79
110
|
def set_nx(key, value, ttl: nil)
|
|
80
111
|
effective_ttl = ttl || default_ttl
|
|
81
|
-
result = client.with { |conn| conn.add(key, value, effective_ttl)
|
|
112
|
+
result = client.with { |conn| conn.add(key, value, effective_ttl) }
|
|
82
113
|
log.debug { "[cache] SET_NX #{key} ttl=#{effective_ttl.inspect} result=#{result}" }
|
|
83
|
-
result
|
|
114
|
+
!!result
|
|
84
115
|
rescue StandardError => e
|
|
116
|
+
cache_backend_failed(e)
|
|
85
117
|
handle_exception(e, level: :error, handled: false, operation: :memcached_set_nx, key: key, ttl: effective_ttl)
|
|
86
118
|
raise
|
|
87
119
|
end
|
|
@@ -92,10 +124,11 @@ module Legion
|
|
|
92
124
|
|
|
93
125
|
def set_sync(key, value, ttl: nil, **)
|
|
94
126
|
effective_ttl = ttl || default_ttl
|
|
95
|
-
result = client.with { |conn| conn.set(key, value, effective_ttl)
|
|
127
|
+
result = client.with { |conn| conn.set(key, value, effective_ttl) }
|
|
96
128
|
log.debug { "[cache] SET #{key} ttl=#{effective_ttl} success=#{result} value_class=#{value.class}" }
|
|
97
|
-
result
|
|
129
|
+
!!result
|
|
98
130
|
rescue StandardError => e
|
|
131
|
+
cache_backend_failed(e)
|
|
99
132
|
handle_exception(e, level: :error, handled: false, operation: :memcached_set_sync, key: key, ttl: effective_ttl)
|
|
100
133
|
raise
|
|
101
134
|
end
|
|
@@ -109,6 +142,7 @@ module Legion
|
|
|
109
142
|
log.debug { "[cache] DELETE #{key} success=#{result}" }
|
|
110
143
|
result
|
|
111
144
|
rescue StandardError => e
|
|
145
|
+
cache_backend_failed(e)
|
|
112
146
|
handle_exception(e, level: :error, handled: false, operation: :memcached_delete_sync, key: key)
|
|
113
147
|
raise
|
|
114
148
|
end
|
|
@@ -118,6 +152,7 @@ module Legion
|
|
|
118
152
|
log.debug { '[cache] FLUSH completed' }
|
|
119
153
|
result
|
|
120
154
|
rescue StandardError => e
|
|
155
|
+
cache_backend_failed(e)
|
|
121
156
|
handle_exception(e, level: :warn, handled: true, operation: :memcached_flush)
|
|
122
157
|
nil
|
|
123
158
|
end
|
|
@@ -130,6 +165,7 @@ module Legion
|
|
|
130
165
|
log.debug { "[cache] MGET keys=#{keys.size} hits=#{result.size}" }
|
|
131
166
|
result
|
|
132
167
|
rescue StandardError => e
|
|
168
|
+
cache_backend_failed(e)
|
|
133
169
|
handle_exception(e, level: :warn, handled: true, operation: :memcached_mget, key_count: keys.size)
|
|
134
170
|
{}
|
|
135
171
|
end
|
|
@@ -138,24 +174,49 @@ module Legion
|
|
|
138
174
|
mset_sync(hash, ttl: ttl)
|
|
139
175
|
end
|
|
140
176
|
|
|
141
|
-
def mset_sync(hash, ttl: nil, **)
|
|
177
|
+
def mset_sync(hash, ttl: nil, **)
|
|
142
178
|
return true if hash.empty?
|
|
143
179
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
180
|
+
effective_ttl = ttl || default_ttl
|
|
181
|
+
# Dalli 3 has no set_multi; newer quiet batches do not acknowledge each write.
|
|
182
|
+
result = client.with do |conn|
|
|
183
|
+
success = true
|
|
184
|
+
hash.each { |key, value| success = false unless conn.set(key, value, effective_ttl) }
|
|
185
|
+
success
|
|
186
|
+
end
|
|
187
|
+
log.debug { "[cache] MSET keys=#{hash.size} success=#{result}" }
|
|
188
|
+
result
|
|
147
189
|
rescue StandardError => e
|
|
190
|
+
cache_backend_failed(e)
|
|
148
191
|
handle_exception(e, level: :error, handled: false, operation: :memcached_mset_sync, key_count: hash.size)
|
|
149
192
|
raise
|
|
150
193
|
end
|
|
151
194
|
|
|
152
195
|
private
|
|
153
196
|
|
|
197
|
+
def memcached_discard_pool(pool)
|
|
198
|
+
pool&.shutdown(&:close)
|
|
199
|
+
rescue StandardError => e
|
|
200
|
+
handle_exception(e, level: :warn, handled: true, operation: :memcached_close_failed_pool)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def memcached_cas_request(key, &)
|
|
204
|
+
client.with(&)
|
|
205
|
+
rescue StandardError => e
|
|
206
|
+
cache_backend_failed(e)
|
|
207
|
+
handle_exception(e, level: :error, handled: false, operation: :memcached_cas, key: key)
|
|
208
|
+
raise
|
|
209
|
+
end
|
|
210
|
+
|
|
154
211
|
def default_ttl
|
|
212
|
+
return @memcached_settings[:default_ttl] || 3600 if @memcached_settings
|
|
213
|
+
return @cache_settings[:default_ttl] || 3600 if @cache_settings
|
|
214
|
+
|
|
155
215
|
return 3600 unless defined?(Legion::Settings)
|
|
156
216
|
|
|
157
217
|
Legion::Settings.dig(:cache, :default_ttl) || 3600
|
|
158
|
-
rescue StandardError
|
|
218
|
+
rescue StandardError => e
|
|
219
|
+
handle_exception(e, level: :warn, handled: true, operation: :memcached_default_ttl)
|
|
159
220
|
3600
|
|
160
221
|
end
|
|
161
222
|
|
|
@@ -172,6 +233,9 @@ module Legion
|
|
|
172
233
|
end
|
|
173
234
|
|
|
174
235
|
def memcached_tls_settings
|
|
236
|
+
return @memcached_settings[:tls] || {} if @memcached_settings
|
|
237
|
+
return @cache_settings[:tls] || {} if @cache_settings
|
|
238
|
+
|
|
175
239
|
return {} unless defined?(Legion::Settings)
|
|
176
240
|
|
|
177
241
|
Legion::Settings[:cache][:tls] || {}
|