redis-memo 0.1.1 → 1.1.0
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/lib/redis_memo.rb +48 -36
- data/lib/redis_memo/after_commit.rb +2 -2
- data/lib/redis_memo/batch.rb +36 -11
- data/lib/redis_memo/cache.rb +36 -19
- data/lib/redis_memo/connection_pool.rb +4 -3
- data/lib/redis_memo/errors.rb +9 -0
- data/lib/redis_memo/future.rb +22 -13
- data/lib/redis_memo/memoizable.rb +109 -72
- data/lib/redis_memo/memoizable/bump_version.lua +39 -0
- data/lib/redis_memo/memoizable/dependency.rb +10 -11
- data/lib/redis_memo/memoizable/invalidation.rb +68 -66
- data/lib/redis_memo/memoize_method.rb +169 -131
- data/lib/redis_memo/memoize_query.rb +135 -92
- data/lib/redis_memo/memoize_query/cached_select.rb +73 -62
- data/lib/redis_memo/memoize_query/cached_select/bind_params.rb +202 -70
- data/lib/redis_memo/memoize_query/cached_select/connection_adapter.rb +19 -10
- data/lib/redis_memo/memoize_query/invalidation.rb +22 -20
- data/lib/redis_memo/memoize_query/memoize_table_column.rb +1 -0
- data/lib/redis_memo/middleware.rb +3 -1
- data/lib/redis_memo/options.rb +111 -5
- data/lib/redis_memo/railtie.rb +11 -0
- data/lib/redis_memo/redis.rb +15 -5
- data/lib/redis_memo/testing.rb +49 -0
- data/lib/redis_memo/thread_local_var.rb +16 -0
- data/lib/redis_memo/tracer.rb +1 -0
- data/lib/redis_memo/util.rb +25 -0
- metadata +80 -4
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class RedisMemo::MemoizeQuery::CachedSelect
|
4
4
|
module ConnectionAdapter
|
5
|
-
def cacheable_query(*args)
|
5
|
+
ruby2_keywords def cacheable_query(*args)
|
6
6
|
query, binds = super(*args)
|
7
7
|
|
8
8
|
# Persist the arel object to StatementCache#execute
|
@@ -11,24 +11,33 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
11
11
|
[query, binds]
|
12
12
|
end
|
13
13
|
|
14
|
-
def exec_query(*args)
|
14
|
+
ruby2_keywords def exec_query(*args)
|
15
15
|
# An Arel AST in Thread local is set prior to supported query methods
|
16
|
-
if !RedisMemo.
|
16
|
+
if !RedisMemo.without_memoization? &&
|
17
17
|
RedisMemo::MemoizeQuery::CachedSelect.extract_bind_params(args[0])
|
18
|
+
|
19
|
+
time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
20
|
+
ret = super(*args)
|
21
|
+
time_end = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
22
|
+
|
18
23
|
# [Reids $model Load] $sql $binds
|
19
|
-
RedisMemo::DefaultOptions.logger&.
|
20
|
-
"[Redis] \u001b[36;1m#{
|
21
|
-
args[
|
22
|
-
}
|
24
|
+
RedisMemo::DefaultOptions.logger&.debug(
|
25
|
+
"[Redis] \u001b[36;1m#{
|
26
|
+
args[1] || 'SQL' # model name
|
27
|
+
} (#{format('%.1f', (time_end - time_start) * 1000.0)}ms) \u001b[34;1m#{
|
28
|
+
args[0] # sql
|
29
|
+
}\u001b[0m #{
|
30
|
+
args[2].map { |bind| [bind.name, bind.value_for_database] } # binds
|
31
|
+
}",
|
23
32
|
)
|
24
33
|
|
25
|
-
|
34
|
+
ret
|
26
35
|
else
|
27
|
-
RedisMemo.
|
36
|
+
RedisMemo.without_memoization { super(*args) }
|
28
37
|
end
|
29
38
|
end
|
30
39
|
|
31
|
-
def select_all(*args)
|
40
|
+
ruby2_keywords def select_all(*args)
|
32
41
|
if args[0].is_a?(Arel::SelectManager)
|
33
42
|
RedisMemo::MemoizeQuery::CachedSelect.current_query = args[0]
|
34
43
|
end
|
@@ -17,28 +17,29 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
17
17
|
@redis_memo_class_memoizable ||= RedisMemo::MemoizeQuery.create_memo(self)
|
18
18
|
end
|
19
19
|
|
20
|
-
%i
|
20
|
+
%i[delete decrement! increment!].each do |method_name|
|
21
21
|
alias_method :"without_redis_memo_invalidation_#{method_name}", method_name
|
22
22
|
|
23
23
|
define_method method_name do |*args|
|
24
|
-
result =
|
24
|
+
result = __send__(:"without_redis_memo_invalidation_#{method_name}", *args)
|
25
25
|
|
26
26
|
RedisMemo::MemoizeQuery.invalidate(self)
|
27
27
|
|
28
28
|
result
|
29
29
|
end
|
30
|
+
ruby2_keywords method_name
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
33
34
|
# Methods that won't trigger model callbacks
|
34
35
|
# https://guides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks
|
35
|
-
%i
|
36
|
+
%i[
|
36
37
|
decrement_counter
|
37
38
|
delete_all delete_by
|
38
39
|
increment_counter
|
39
40
|
touch_all
|
40
41
|
update_column update_columns update_all update_counters
|
41
|
-
|
42
|
+
].each do |method_name|
|
42
43
|
# Example: Model.update_all
|
43
44
|
rewrite_default_method(
|
44
45
|
model_class,
|
@@ -56,27 +57,27 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
56
57
|
)
|
57
58
|
end
|
58
59
|
|
59
|
-
%i
|
60
|
+
%i[
|
60
61
|
insert insert! insert_all insert_all!
|
61
|
-
|
62
|
+
].each do |method_name|
|
62
63
|
rewrite_insert_method(
|
63
64
|
model_class,
|
64
65
|
method_name,
|
65
66
|
)
|
66
67
|
end
|
67
68
|
|
68
|
-
%i
|
69
|
+
%i[
|
69
70
|
upsert upsert_all
|
70
|
-
|
71
|
+
].each do |method_name|
|
71
72
|
rewrite_upsert_method(
|
72
73
|
model_class,
|
73
74
|
method_name,
|
74
75
|
)
|
75
76
|
end
|
76
77
|
|
77
|
-
%i
|
78
|
+
%i[
|
78
79
|
import import!
|
79
|
-
|
80
|
+
].each do |method_name|
|
80
81
|
rewrite_import_method(
|
81
82
|
model_class,
|
82
83
|
method_name,
|
@@ -116,8 +117,6 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
116
117
|
result
|
117
118
|
end
|
118
119
|
|
119
|
-
private
|
120
|
-
|
121
120
|
#
|
122
121
|
# There’s no good way to perform fine-grind cache invalidation when
|
123
122
|
# operations are bulk update operations such as update_all, and delete_all
|
@@ -127,17 +126,18 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
127
126
|
#
|
128
127
|
def self.rewrite_default_method(model_class, klass, method_name, class_method:)
|
129
128
|
methods = class_method ? :methods : :instance_methods
|
130
|
-
return unless klass.
|
129
|
+
return unless klass.__send__(methods).include?(method_name)
|
131
130
|
|
132
131
|
klass = klass.singleton_class if class_method
|
133
132
|
klass.class_eval do
|
134
133
|
alias_method :"#{method_name}_without_redis_memo_invalidation", method_name
|
135
134
|
|
136
135
|
define_method method_name do |*args|
|
137
|
-
result =
|
136
|
+
result = __send__(:"#{method_name}_without_redis_memo_invalidation", *args)
|
138
137
|
RedisMemo::MemoizeQuery.invalidate_all(model_class)
|
139
138
|
result
|
140
139
|
end
|
140
|
+
ruby2_keywords method_name
|
141
141
|
end
|
142
142
|
end
|
143
143
|
|
@@ -149,9 +149,10 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
149
149
|
|
150
150
|
define_method method_name do |*args, &blk|
|
151
151
|
RedisMemo::MemoizeQuery::Invalidation.invalidate_new_records(model_class) do
|
152
|
-
|
152
|
+
__send__(:"#{method_name}_without_redis_memo_invalidation", *args, &blk)
|
153
153
|
end
|
154
154
|
end
|
155
|
+
ruby2_keywords method_name
|
155
156
|
end
|
156
157
|
end
|
157
158
|
|
@@ -169,7 +170,7 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
169
170
|
# HEAD (6.1.3)
|
170
171
|
conflict_target: nil,
|
171
172
|
) do
|
172
|
-
|
173
|
+
__send__(
|
173
174
|
:"#{method_name}_without_redis_memo_invalidation",
|
174
175
|
attributes,
|
175
176
|
unique_by: unique_by,
|
@@ -216,9 +217,10 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
216
217
|
records: records,
|
217
218
|
conflict_target: conflict_target,
|
218
219
|
) do
|
219
|
-
|
220
|
+
__send__(:"#{method_name}_without_redis_memo_invalidation", *args, &blk)
|
220
221
|
end
|
221
222
|
end
|
223
|
+
ruby2_keywords method_name
|
222
224
|
end
|
223
225
|
end
|
224
226
|
|
@@ -228,7 +230,7 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
228
230
|
records.each do |record|
|
229
231
|
conditions = {}
|
230
232
|
conflict_target.each do |column|
|
231
|
-
conditions[column] = record.
|
233
|
+
conditions[column] = record.__send__(column)
|
232
234
|
end
|
233
235
|
if or_chain
|
234
236
|
or_chain = or_chain.or(model_class.where(conditions))
|
@@ -245,7 +247,7 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
245
247
|
'redis_memo.memoize_query.invalidation',
|
246
248
|
"#{__method__}##{model_class.name}",
|
247
249
|
) do
|
248
|
-
RedisMemo.
|
250
|
+
RedisMemo.without_memoization do
|
249
251
|
model_class.where(
|
250
252
|
model_class.arel_table[model_class.primary_key].gt(target_id),
|
251
253
|
).to_a
|
@@ -260,7 +262,7 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
260
262
|
'redis_memo.memoize_query.invalidation',
|
261
263
|
"#{__method__}##{model_class.name}",
|
262
264
|
) do
|
263
|
-
RedisMemo.
|
265
|
+
RedisMemo.without_memoization { relation.reload }
|
264
266
|
end
|
265
267
|
end
|
266
268
|
end
|
@@ -9,7 +9,9 @@ class RedisMemo::Middleware
|
|
9
9
|
result = nil
|
10
10
|
|
11
11
|
RedisMemo::Cache.with_local_cache do
|
12
|
-
|
12
|
+
RedisMemo.with_max_connection_attempts(RedisMemo::DefaultOptions.max_connection_attempts) do
|
13
|
+
result = @app.call(env)
|
14
|
+
end
|
13
15
|
end
|
14
16
|
RedisMemo::Memoizable::Invalidation.drain_invalidation_queue
|
15
17
|
|
data/lib/redis_memo/options.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
##
|
4
|
+
# This class allows users to configure various RedisMemo options. Options can be set in
|
5
|
+
# your initializer +config/initializers/redis_memo.rb+
|
6
|
+
# RedisMemo.configure do |config|
|
7
|
+
# config.expires_in = 3.hours
|
8
|
+
# config.global_cache_key_version = SecureRandom.uuid
|
9
|
+
# end
|
10
|
+
#
|
3
11
|
class RedisMemo::Options
|
4
12
|
def initialize(
|
5
13
|
async: nil,
|
@@ -9,33 +17,70 @@ class RedisMemo::Options
|
|
9
17
|
redis_error_handler: nil,
|
10
18
|
tracer: nil,
|
11
19
|
global_cache_key_version: nil,
|
12
|
-
expires_in: nil
|
20
|
+
expires_in: nil,
|
21
|
+
max_connection_attempts: nil,
|
22
|
+
max_query_dependency_size: 5000,
|
23
|
+
disable_all: false,
|
24
|
+
disable_cached_select: false,
|
25
|
+
disabled_models: Set.new
|
13
26
|
)
|
27
|
+
@async = async
|
14
28
|
@compress = compress.nil? ? true : compress
|
15
29
|
@compress_threshold = compress_threshold || 1.kilobyte
|
16
30
|
@redis_config = redis
|
17
|
-
@
|
31
|
+
@redis = nil
|
18
32
|
@redis_error_handler = redis_error_handler
|
19
33
|
@tracer = tracer
|
20
34
|
@logger = logger
|
21
35
|
@global_cache_key_version = global_cache_key_version
|
22
36
|
@expires_in = expires_in
|
37
|
+
@max_connection_attempts = ENV['REDIS_MEMO_MAX_ATTEMPTS_PER_REQUEST']&.to_i || max_connection_attempts
|
38
|
+
@max_query_dependency_size = ENV['REDIS_MEMO_MAX_QUERY_DEPENDENCY_SIZE']&.to_i || max_query_dependency_size
|
39
|
+
@disable_all = ENV['REDIS_MEMO_DISABLE_ALL'] == 'true' || disable_all
|
40
|
+
@disable_cached_select = ENV['REDIS_MEMO_DISABLE_CACHED_SELECT'] == 'true' || disable_cached_select
|
41
|
+
@disabled_models = disabled_models
|
23
42
|
end
|
24
43
|
|
44
|
+
# Retrieves the redis client, initializing it if it does not exist yet.
|
25
45
|
def redis
|
26
|
-
@
|
46
|
+
@redis ||= RedisMemo::Redis.new(redis_config)
|
27
47
|
end
|
28
48
|
|
49
|
+
# Retrieves the config values used to initialize the Redis client.
|
29
50
|
def redis_config
|
30
51
|
@redis_config || {}
|
31
52
|
end
|
32
53
|
|
54
|
+
# Set configuration values to pass to the Redis client. If multiple configurations are passed
|
55
|
+
# to this method, we assume that the first config corresponds to the primary node, and subsequent
|
56
|
+
# configurations correspond to replica nodes.
|
57
|
+
#
|
58
|
+
# For example, if your urls are specified as <tt><url>,<url>...;<url>,...;...,</tt> where <tt>;</tt> delimits
|
59
|
+
# different clusters and <tt>,</tt> delimits primary and read replicas, then in your configuration:
|
60
|
+
#
|
61
|
+
# RedisMemo.configure do |config|
|
62
|
+
# config.redis = redis_urls.split(';').map do |urls|
|
63
|
+
# urls.split(',').map do |url|
|
64
|
+
# {
|
65
|
+
# url: url,
|
66
|
+
# # All timeout values are specified in seconds
|
67
|
+
# connect_timeout: ENV['REDIS_MEMO_CONNECT_TIMEOUT']&.to_f || 0.2,
|
68
|
+
# read_timeout: ENV['REDIS_MEMO_READ_TIMEOUT']&.to_f || 0.5,
|
69
|
+
# write_timeout: ENV['REDIS_MEMO_WRITE_TIMEOUT']&.to_f || 0.5,
|
70
|
+
# reconnect_attempts: ENV['REDIS_MEMO_RECONNECT_ATTEMPTS']&.to_i || 0
|
71
|
+
# }
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
33
76
|
def redis=(config)
|
34
77
|
@redis_config = config
|
35
|
-
@
|
78
|
+
@redis = nil
|
36
79
|
redis
|
37
80
|
end
|
38
81
|
|
82
|
+
# Sets the tracer object. Allows the tracer to be dynamically determined at
|
83
|
+
# runtime if a blk is given.
|
39
84
|
def tracer(&blk)
|
40
85
|
if blk.nil?
|
41
86
|
return @tracer if @tracer.respond_to?(:trace)
|
@@ -46,6 +91,8 @@ class RedisMemo::Options
|
|
46
91
|
end
|
47
92
|
end
|
48
93
|
|
94
|
+
# Sets the logger object in RedisMemo. Allows the logger to be dynamically
|
95
|
+
# determined at runtime if a blk is given.
|
49
96
|
def logger(&blk)
|
50
97
|
if blk.nil?
|
51
98
|
return @logger if @logger.respond_to?(:warn)
|
@@ -56,6 +103,8 @@ class RedisMemo::Options
|
|
56
103
|
end
|
57
104
|
end
|
58
105
|
|
106
|
+
# Sets the global cache key version. Allows the logger to be dynamically
|
107
|
+
# determined at runtime if a blk is given.
|
59
108
|
def global_cache_key_version(&blk)
|
60
109
|
# this method takes a block to be consistent with the inline memo_method
|
61
110
|
# API
|
@@ -71,16 +120,73 @@ class RedisMemo::Options
|
|
71
120
|
end
|
72
121
|
end
|
73
122
|
|
123
|
+
# Disables the model for caching and invalidation
|
124
|
+
def disable_model(model)
|
125
|
+
@disabled_models << model
|
126
|
+
end
|
127
|
+
|
128
|
+
# Checks if a model is disabled for redis memo caching
|
129
|
+
def model_disabled_for_caching?(model)
|
130
|
+
ENV["REDIS_MEMO_DISABLE_#{model.table_name.upcase}"] == 'true' || @disabled_models.include?(model)
|
131
|
+
end
|
132
|
+
|
133
|
+
# A handler used to asynchronously perform cache writes and invalidations. If no value is provided,
|
134
|
+
# RedisMemo will perform these operations synchronously.
|
74
135
|
attr_accessor :async
|
136
|
+
|
137
|
+
# Specify the global sampled percentage of the chance to call the cache validation, a value between 0 to 100, when the value
|
138
|
+
# is 100, it will call the handler every time the cached result does not match the uncached result
|
139
|
+
# You can also specify inline cache validation sample percentage by memoize_method :method, cache_validation_sample_percentage: #{value}
|
140
|
+
attr_accessor :cache_validation_sample_percentage
|
141
|
+
|
142
|
+
# Handler called when the cached result does not match the uncached result (sampled at the
|
143
|
+
# `cache_validation_sample_percentage`). This might indicate that invalidation is happening too slowly or
|
144
|
+
# that there are incorrect dependencies specified on a cached method.
|
75
145
|
attr_accessor :cache_out_of_date_handler
|
76
|
-
|
146
|
+
|
147
|
+
# Passed along to the Rails {RedisCacheStore}[https://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html], determines whether or not to compress entries before storing
|
148
|
+
# them. default: `true`
|
77
149
|
attr_accessor :compress
|
150
|
+
|
151
|
+
# Passed along to the Rails {RedisCacheStore}[https://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html], the size threshold for which to compress cached entries.
|
152
|
+
# default: 1.kilobyte
|
78
153
|
attr_accessor :compress_threshold
|
154
|
+
|
155
|
+
# Configuration values for connecting to RedisMemo using a connection pool. It's recommended to use a
|
156
|
+
# connection pool in multi-threaded applications, or when an async handler is set.
|
79
157
|
attr_accessor :connection_pool
|
158
|
+
|
159
|
+
# Passed along to the Rails {RedisCacheStore}[https://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html], sets the TTL on cache entries in Redis.
|
80
160
|
attr_accessor :expires_in
|
161
|
+
|
162
|
+
# The max number of failed connection attempts RedisMemo will make for a single request before bypassing
|
163
|
+
# the caching layer. This helps make RedisMemo resilient to errors and performance issues when there's
|
164
|
+
# an issue with the Redis cluster itself.
|
165
|
+
attr_accessor :max_connection_attempts
|
166
|
+
|
167
|
+
# Only cache a SQL query when the max number of dependency is smaller or equal to this number. Configurable via an ENV var REDIS_MEMO_MAX_QUERY_DEPENDENCY_SIZE. Default at 5000.
|
168
|
+
attr_accessor :max_query_dependency_size
|
169
|
+
|
170
|
+
# Passed along to the Rails {RedisCacheStore}[https://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html], the error handler called for Redis related errors.
|
81
171
|
attr_accessor :redis_error_handler
|
82
172
|
|
173
|
+
# A global kill switch to disable all RedisMemo operations.
|
174
|
+
attr_accessor :disable_all
|
175
|
+
|
176
|
+
# A kill switch to disable RedisMemo caching on database queries. This does not disable the invalidation
|
177
|
+
# after_save hooks that are installed on memoized models.
|
178
|
+
attr_accessor :disable_cached_select
|
179
|
+
|
180
|
+
# A kill switch to set the list of models to disable caching and invalidation after_save hooks on.
|
181
|
+
attr_accessor :disabled_models
|
182
|
+
|
183
|
+
# A global cache key version prepended to each cached entry. For example, the commit hash of the current
|
184
|
+
# version deployed to your application.
|
83
185
|
attr_writer :global_cache_key_version
|
186
|
+
|
187
|
+
# Object used to trace RedisMemo operations to collect latency and error metrics, e.g. `Datadog.tracer`
|
84
188
|
attr_writer :tracer
|
189
|
+
|
190
|
+
# Object used to log RedisMemo operations.
|
85
191
|
attr_writer :logger
|
86
192
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RedisMemo::Railtie < Rails::Railtie
|
4
|
+
initializer 'request_store.insert_middleware' do |app|
|
5
|
+
if ActionDispatch.const_defined? :RequestId
|
6
|
+
app.config.middleware.insert_after ActionDispatch::RequestId, RedisMemo::Middleware
|
7
|
+
else
|
8
|
+
app.config.middleware.insert_after Rack::MethodOverride, RedisMemo::Middleware
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/redis_memo/redis.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'redis'
|
3
4
|
require 'redis/distributed'
|
4
5
|
|
@@ -7,19 +8,19 @@ require_relative 'options'
|
|
7
8
|
# Redis::Distributed does not support reading from multiple read replicas. This
|
8
9
|
# class adds this functionality
|
9
10
|
class RedisMemo::Redis < Redis::Distributed
|
10
|
-
def initialize(
|
11
|
+
def initialize(
|
12
|
+
options = {} # rubocop: disable Style/OptionHash
|
13
|
+
)
|
11
14
|
clients =
|
12
15
|
if options.is_a?(Array)
|
13
16
|
options.map do |option|
|
14
17
|
if option.is_a?(Array)
|
15
18
|
RedisMemo::Redis::WithReplicas.new(option)
|
16
19
|
else
|
17
|
-
option[:logger] ||= RedisMemo::DefaultOptions.logger
|
18
20
|
::Redis.new(option)
|
19
21
|
end
|
20
22
|
end
|
21
23
|
else
|
22
|
-
options[:logger] ||= RedisMemo::DefaultOptions.logger
|
23
24
|
[::Redis.new(options)]
|
24
25
|
end
|
25
26
|
|
@@ -30,16 +31,25 @@ class RedisMemo::Redis < Redis::Distributed
|
|
30
31
|
super([], ring: hash_ring)
|
31
32
|
end
|
32
33
|
|
34
|
+
def run_script(script_content, script_sha, *args)
|
35
|
+
begin
|
36
|
+
return evalsha(script_sha, *args) if script_sha
|
37
|
+
rescue Redis::CommandError => error
|
38
|
+
if error.message != 'NOSCRIPT No matching script. Please use EVAL.'
|
39
|
+
raise error
|
40
|
+
end
|
41
|
+
end
|
42
|
+
eval(script_content, *args) # rubocop: disable Security/Eval
|
43
|
+
end
|
44
|
+
|
33
45
|
class WithReplicas < ::Redis
|
34
46
|
def initialize(orig_options)
|
35
47
|
options = orig_options.dup
|
36
48
|
primary_option = options.shift
|
37
49
|
@replicas = options.map do |option|
|
38
|
-
option[:logger] ||= RedisMemo::DefaultOptions.logger
|
39
50
|
::Redis.new(option)
|
40
51
|
end
|
41
52
|
|
42
|
-
primary_option[:logger] ||= RedisMemo::DefaultOptions.logger
|
43
53
|
super(primary_option)
|
44
54
|
end
|
45
55
|
|