redis-memo 0.1.0 → 1.0.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.
@@ -9,7 +9,9 @@ class RedisMemo::Middleware
9
9
  result = nil
10
10
 
11
11
  RedisMemo::Cache.with_local_cache do
12
- result = @app.call(env)
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
 
@@ -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,68 @@ 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
+ disable_all: false,
23
+ disable_cached_select: false,
24
+ disabled_models: Set.new
13
25
  )
26
+ @async = async
14
27
  @compress = compress.nil? ? true : compress
15
28
  @compress_threshold = compress_threshold || 1.kilobyte
16
29
  @redis_config = redis
17
- @redis_client = nil
30
+ @redis = nil
18
31
  @redis_error_handler = redis_error_handler
19
32
  @tracer = tracer
20
33
  @logger = logger
21
34
  @global_cache_key_version = global_cache_key_version
22
35
  @expires_in = expires_in
36
+ @max_connection_attempts = ENV['REDIS_MEMO_MAX_ATTEMPTS_PER_REQUEST']&.to_i || max_connection_attempts
37
+ @disable_all = ENV['REDIS_MEMO_DISABLE_ALL'] == 'true' || disable_all
38
+ @disable_cached_select = ENV['REDIS_MEMO_DISABLE_CACHED_SELECT'] == 'true' || disable_cached_select
39
+ @disabled_models = disabled_models
23
40
  end
24
41
 
42
+ # Retrieves the redis client, initializing it if it does not exist yet.
25
43
  def redis
26
- @redis_client ||= RedisMemo::Redis.new(redis_config)
44
+ @redis ||= RedisMemo::Redis.new(redis_config)
27
45
  end
28
46
 
47
+ # Retrieves the config values used to initialize the Redis client.
29
48
  def redis_config
30
49
  @redis_config || {}
31
50
  end
32
51
 
52
+ # Set configuration values to pass to the Redis client. If multiple configurations are passed
53
+ # to this method, we assume that the first config corresponds to the primary node, and subsequent
54
+ # configurations correspond to replica nodes.
55
+ #
56
+ # For example, if your urls are specified as <tt><url>,<url>...;<url>,...;...,</tt> where <tt>;</tt> delimits
57
+ # different clusters and <tt>,</tt> delimits primary and read replicas, then in your configuration:
58
+ #
59
+ # RedisMemo.configure do |config|
60
+ # config.redis = redis_urls.split(';').map do |urls|
61
+ # urls.split(',').map do |url|
62
+ # {
63
+ # url: url,
64
+ # # All timeout values are specified in seconds
65
+ # connect_timeout: ENV['REDIS_MEMO_CONNECT_TIMEOUT']&.to_f || 0.2,
66
+ # read_timeout: ENV['REDIS_MEMO_READ_TIMEOUT']&.to_f || 0.5,
67
+ # write_timeout: ENV['REDIS_MEMO_WRITE_TIMEOUT']&.to_f || 0.5,
68
+ # reconnect_attempts: ENV['REDIS_MEMO_RECONNECT_ATTEMPTS']&.to_i || 0
69
+ # }
70
+ # end
71
+ # end
72
+ # end
73
+ #
33
74
  def redis=(config)
34
75
  @redis_config = config
35
- @redis_client = nil
76
+ @redis = nil
36
77
  redis
37
78
  end
38
79
 
80
+ # Sets the tracer object. Allows the tracer to be dynamically determined at
81
+ # runtime if a blk is given.
39
82
  def tracer(&blk)
40
83
  if blk.nil?
41
84
  return @tracer if @tracer.respond_to?(:trace)
@@ -46,6 +89,8 @@ class RedisMemo::Options
46
89
  end
47
90
  end
48
91
 
92
+ # Sets the logger object in RedisMemo. Allows the logger to be dynamically
93
+ # determined at runtime if a blk is given.
49
94
  def logger(&blk)
50
95
  if blk.nil?
51
96
  return @logger if @logger.respond_to?(:warn)
@@ -56,6 +101,8 @@ class RedisMemo::Options
56
101
  end
57
102
  end
58
103
 
104
+ # Sets the global cache key version. Allows the logger to be dynamically
105
+ # determined at runtime if a blk is given.
59
106
  def global_cache_key_version(&blk)
60
107
  # this method takes a block to be consistent with the inline memo_method
61
108
  # API
@@ -71,16 +118,70 @@ class RedisMemo::Options
71
118
  end
72
119
  end
73
120
 
121
+ # Disables the model for caching and invalidation
122
+ def disable_model(model)
123
+ @disabled_models << model
124
+ end
125
+
126
+ # Checks if a model is disabled for redis memo caching
127
+ def model_disabled_for_caching?(model)
128
+ ENV["REDIS_MEMO_DISABLE_#{model.table_name.upcase}"] == 'true' || @disabled_models.include?(model)
129
+ end
130
+
131
+ # A handler used to asynchronously perform cache writes and invalidations. If no value is provided,
132
+ # RedisMemo will perform these operations synchronously.
74
133
  attr_accessor :async
134
+
135
+ # Specify the global sampled percentage of the chance to call the cache validation, a value between 0 to 100, when the value
136
+ # is 100, it will call the handler every time the cached result does not match the uncached result
137
+ # You can also specify inline cache validation sample percentage by memoize_method :method, cache_validation_sample_percentage: #{value}
138
+ attr_accessor :cache_validation_sample_percentage
139
+
140
+ # Handler called when the cached result does not match the uncached result (sampled at the
141
+ # `cache_validation_sample_percentage`). This might indicate that invalidation is happening too slowly or
142
+ # that there are incorrect dependencies specified on a cached method.
75
143
  attr_accessor :cache_out_of_date_handler
76
- attr_accessor :cache_validation_sampler
144
+
145
+ # Passed along to the Rails {RedisCacheStore}[https://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html], determines whether or not to compress entries before storing
146
+ # them. default: `true`
77
147
  attr_accessor :compress
148
+
149
+ # Passed along to the Rails {RedisCacheStore}[https://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html], the size threshold for which to compress cached entries.
150
+ # default: 1.kilobyte
78
151
  attr_accessor :compress_threshold
152
+
153
+ # Configuration values for connecting to RedisMemo using a connection pool. It's recommended to use a
154
+ # connection pool in multi-threaded applications, or when an async handler is set.
79
155
  attr_accessor :connection_pool
156
+
157
+ # Passed along to the Rails {RedisCacheStore}[https://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html], sets the TTL on cache entries in Redis.
80
158
  attr_accessor :expires_in
159
+
160
+ # The max number of failed connection attempts RedisMemo will make for a single request before bypassing
161
+ # the caching layer. This helps make RedisMemo resilient to errors and performance issues when there's
162
+ # an issue with the Redis cluster itself.
163
+ attr_accessor :max_connection_attempts
164
+
165
+ # Passed along to the Rails {RedisCacheStore}[https://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html], the error handler called for Redis related errors.
81
166
  attr_accessor :redis_error_handler
82
167
 
168
+ # A global kill switch to disable all RedisMemo operations.
169
+ attr_accessor :disable_all
170
+
171
+ # A kill switch to disable RedisMemo caching on database queries. This does not disable the invalidation
172
+ # after_save hooks that are installed on memoized models.
173
+ attr_accessor :disable_cached_select
174
+
175
+ # A kill switch to set the list of models to disable caching and invalidation after_save hooks on.
176
+ attr_accessor :disabled_models
177
+
178
+ # A global cache key version prepended to each cached entry. For example, the commit hash of the current
179
+ # version deployed to your application.
83
180
  attr_writer :global_cache_key_version
181
+
182
+ # Object used to trace RedisMemo operations to collect latency and error metrics, e.g. `Datadog.tracer`
84
183
  attr_writer :tracer
184
+
185
+ # Object used to log RedisMemo operations.
85
186
  attr_writer :logger
86
187
  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
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'redis'
3
4
  require 'redis/distributed'
4
5
 
@@ -7,7 +8,9 @@ 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(options={})
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|
@@ -30,6 +33,17 @@ class RedisMemo::Redis < Redis::Distributed
30
33
  super([], ring: hash_ring)
31
34
  end
32
35
 
36
+ def run_script(script_content, script_sha, *args)
37
+ begin
38
+ return evalsha(script_sha, *args) if script_sha
39
+ rescue Redis::CommandError => error
40
+ if error.message != 'NOSCRIPT No matching script. Please use EVAL.'
41
+ raise error
42
+ end
43
+ end
44
+ eval(script_content, *args) # rubocop: disable Security/Eval
45
+ end
46
+
33
47
  class WithReplicas < ::Redis
34
48
  def initialize(orig_options)
35
49
  options = orig_options.dup
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Redis memo can be flaky due to transient network errors (e.g. Redis connection errors), or when
4
+ # used with async handlers. This class allows users to override the default redis-memo behavior
5
+ # to be more robust when testing their code that uses redis-memo.
6
+ module RedisMemo
7
+ class Testing
8
+ class << self
9
+ attr_accessor :__test_mode
10
+ end
11
+
12
+ def self.enable_test_mode(&blk)
13
+ __set_test_mode(true, &blk)
14
+ end
15
+
16
+ def self.disable_test_mode(&blk)
17
+ __set_test_mode(false, &blk)
18
+ end
19
+
20
+ def self.enabled?
21
+ __test_mode
22
+ end
23
+
24
+ def self.__set_test_mode(mode, &blk)
25
+ if blk.nil?
26
+ __test_mode = mode
27
+ else
28
+ prev_mode = __test_mode
29
+ begin
30
+ __test_mode = mode
31
+ yield
32
+ ensure
33
+ __test_mode = prev_mode
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ module TestOverrides
40
+ def without_memoization?
41
+ if RedisMemo::Testing.enabled? && !RedisMemo::Memoizable::Invalidation.class_variable_get(:@@invalidation_queue).empty?
42
+ return true
43
+ end
44
+
45
+ super
46
+ end
47
+ end
48
+ singleton_class.prepend(TestOverrides)
49
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RedisMemo::ThreadLocalVar
4
+ def self.define(var_name) # :nodoc:
5
+ thread_key = :"__redis_memo_#{var_name}__"
6
+ const_set(var_name.to_s.upcase, thread_key)
7
+
8
+ define_singleton_method var_name do
9
+ Thread.current[thread_key]
10
+ end
11
+
12
+ define_singleton_method "#{var_name}=" do |var_val|
13
+ Thread.current[thread_key] = var_val
14
+ end
15
+ end
16
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'options'
3
4
 
4
5
  class RedisMemo::Tracer
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RedisMemo::Util
4
+ def self.checksum(serialized)
5
+ Digest::SHA1.base64digest(serialized)
6
+ end
7
+
8
+ def self.deep_sort_hash(orig_hash)
9
+ {}.tap do |new_hash|
10
+ orig_hash.sort.each do |k, v|
11
+ new_hash[k] = v.is_a?(Hash) ? deep_sort_hash(v) : v
12
+ end
13
+ end
14
+ end
15
+
16
+ def self.uuid
17
+ SecureRandom.uuid
18
+ end
19
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-memo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chan Zuckerberg Initiative
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: connection_pool
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.2.3
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: redis
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -39,19 +53,19 @@ dependencies:
39
53
  - !ruby/object:Gem::Version
40
54
  version: 4.0.1
41
55
  - !ruby/object:Gem::Dependency
42
- name: connection_pool
56
+ name: ruby2_keywords
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - ">="
46
60
  - !ruby/object:Gem::Version
47
- version: 2.2.3
61
+ version: '0'
48
62
  type: :runtime
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - ">="
53
67
  - !ruby/object:Gem::Version
54
- version: 2.2.3
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: activerecord
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +136,20 @@ dependencies:
122
136
  - - ">="
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: railties
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '5.2'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '5.2'
125
153
  - !ruby/object:Gem::Dependency
126
154
  name: rake
127
155
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +178,48 @@ dependencies:
150
178
  - - "~>"
151
179
  - !ruby/object:Gem::Version
152
180
  version: '3.2'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rubocop
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rubocop-performance
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: rubocop-rspec
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
153
223
  - !ruby/object:Gem::Dependency
154
224
  name: simplecov
155
225
  requirement: !ruby/object:Gem::Requirement
@@ -176,8 +246,10 @@ files:
176
246
  - lib/redis_memo/batch.rb
177
247
  - lib/redis_memo/cache.rb
178
248
  - lib/redis_memo/connection_pool.rb
249
+ - lib/redis_memo/errors.rb
179
250
  - lib/redis_memo/future.rb
180
251
  - lib/redis_memo/memoizable.rb
252
+ - lib/redis_memo/memoizable/bump_version.lua
181
253
  - lib/redis_memo/memoizable/dependency.rb
182
254
  - lib/redis_memo/memoizable/invalidation.rb
183
255
  - lib/redis_memo/memoize_method.rb
@@ -191,8 +263,12 @@ files:
191
263
  - lib/redis_memo/memoize_query/model_callback.rb
192
264
  - lib/redis_memo/middleware.rb
193
265
  - lib/redis_memo/options.rb
266
+ - lib/redis_memo/railtie.rb
194
267
  - lib/redis_memo/redis.rb
268
+ - lib/redis_memo/testing.rb
269
+ - lib/redis_memo/thread_local_var.rb
195
270
  - lib/redis_memo/tracer.rb
271
+ - lib/redis_memo/util.rb
196
272
  homepage: https://github.com/chanzuckerberg/redis-memo
197
273
  licenses:
198
274
  - MIT