redis-memo 0.0.0.alpha → 0.0.0.beta

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.
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RedisMemo::MemoizeRecords::Invalidation
4
+ def self.install(model_class)
5
+ var_name = :@@__redis_memo_memoize_records_invalidation_installed__
6
+ return if model_class.class_variable_defined?(var_name)
7
+
8
+ model_class.class_eval do
9
+ # A memory-persistent memoizable used for invalidating all queries of a
10
+ # particular model
11
+ def self.redis_memo_class_memoizable
12
+ @redis_memo_class_memoizable ||= RedisMemo::MemoizeRecords.create_memo(self)
13
+ end
14
+
15
+ %i(delete decrement! increment!).each do |method_name|
16
+ alias_method :"without_redis_memo_invalidation_#{method_name}", method_name
17
+
18
+ define_method method_name do |*args|
19
+ result = send(:"without_redis_memo_invalidation_#{method_name}", *args)
20
+
21
+ RedisMemo::MemoizeRecords.invalidate(self)
22
+
23
+ result
24
+ end
25
+ end
26
+ end
27
+
28
+ # Methods that won't trigger model callbacks
29
+ # https://guides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks
30
+ %i(
31
+ import
32
+ decrement_counter
33
+ delete_all delete_by
34
+ increment_counter
35
+ insert insert! insert_all insert_all!
36
+ touch_all
37
+ update_column update_columns update_all update_counters
38
+ upsert upsert_all
39
+ ).each do |method_name|
40
+ # Example: Model.update_all
41
+ rewrite_bulk_update_method(
42
+ model_class,
43
+ model_class,
44
+ method_name,
45
+ class_method: true,
46
+ )
47
+
48
+ # Example: Model.where(...).update_all
49
+ rewrite_bulk_update_method(
50
+ model_class,
51
+ model_class.const_get(:ActiveRecord_Relation),
52
+ method_name,
53
+ class_method: false,
54
+ )
55
+ end
56
+
57
+ model_class.class_variable_set(var_name, true)
58
+ end
59
+
60
+ private
61
+
62
+ #
63
+ # There’s no good way to perform fine-grind cache invalidation when operations
64
+ # are bulk update operations such as import, update_all, and destroy_all:
65
+ # Performing fine-grind cache invalidation would require the applications to
66
+ # fetch additional data from the database, which might lead to performance
67
+ # degradation. Thus we simply invalidate all existing cached records after each
68
+ # bulk_updates.
69
+ #
70
+ def self.rewrite_bulk_update_method(model_class, klass, method_name, class_method:)
71
+ methods = class_method ? :methods : :instance_methods
72
+ return unless klass.send(methods).include?(method_name)
73
+
74
+ klass = klass.singleton_class if class_method
75
+ klass.class_eval do
76
+ alias_method :"#{method_name}_without_redis_memo_invalidation", method_name
77
+
78
+ define_method method_name do |*args|
79
+ result = send(:"#{method_name}_without_redis_memo_invalidation", *args)
80
+ RedisMemo::MemoizeRecords.invalidate_all(model_class)
81
+ result
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RedisMemo::MemoizeRecords::ModelCallback
4
+ def self.install(model_class)
5
+ var_name = :@@__redis_memo_memoize_record_after_save_callback_installed__
6
+ return if model_class.class_variable_defined?(var_name)
7
+
8
+ model_class.after_save(new)
9
+ model_class.after_destroy(new)
10
+
11
+ model_class.class_variable_set(var_name, true)
12
+ end
13
+
14
+ def after_save(record)
15
+ RedisMemo::MemoizeRecords.invalidate(record)
16
+ end
17
+
18
+ def after_destroy(record)
19
+ RedisMemo::MemoizeRecords.invalidate(record)
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RedisMemo::Middleware
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ result = nil
10
+
11
+ RedisMemo::Cache.with_local_cache do
12
+ result = @app.call(env)
13
+ end
14
+ RedisMemo::Memoizable::Invalidation.drain_invalidation_queue
15
+
16
+ result
17
+ end
18
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RedisMemo::Options
4
+ def initialize(
5
+ async: nil,
6
+ compress: nil,
7
+ compress_threshold: nil,
8
+ redis: nil,
9
+ redis_error_handler: nil,
10
+ tracer: nil,
11
+ global_cache_key_version: nil,
12
+ expires_in: nil
13
+ )
14
+ @compress = compress.nil? ? true : compress
15
+ @compress_threshold = compress_threshold || 1.kilobyte
16
+ @redis = redis
17
+ @redis_client = nil
18
+ @redis_error_handler = redis_error_handler
19
+ @tracer = tracer
20
+ @logger = logger
21
+ @global_cache_key_version = global_cache_key_version
22
+ @expires_in = expires_in
23
+ end
24
+
25
+ def redis(&blk)
26
+ if blk.nil?
27
+ return @redis_client if @redis_client.is_a?(RedisMemo::Redis)
28
+
29
+ if @redis.respond_to?(:call)
30
+ @redis_client = RedisMemo::Redis.new(@redis.call)
31
+ elsif @redis
32
+ @redis_client = RedisMemo::Redis.new(@redis)
33
+ else
34
+ @redis_client = RedisMemo::Redis.new
35
+ end
36
+ else
37
+ @redis = blk
38
+ end
39
+ end
40
+
41
+ def tracer(&blk)
42
+ if blk.nil?
43
+ return @tracer if @tracer.respond_to?(:trace)
44
+
45
+ @tracer&.call
46
+ else
47
+ @tracer = blk
48
+ end
49
+ end
50
+
51
+ def logger(&blk)
52
+ if blk.nil?
53
+ return @logger if @logger.respond_to?(:warn)
54
+
55
+ @logger&.call
56
+ else
57
+ @logger = blk
58
+ end
59
+ end
60
+
61
+ def global_cache_key_version(&blk)
62
+ # this method takes a block to be consistent with the inline memo_method
63
+ # API
64
+ if blk.nil?
65
+ if !@global_cache_key_version.respond_to?(:call)
66
+ return @global_cache_key_version
67
+ end
68
+
69
+ @global_cache_key_version&.call
70
+ else
71
+ # save the global cache_key_version eagerly
72
+ @global_cache_key_version = blk
73
+ end
74
+ end
75
+
76
+ attr_accessor :async
77
+ attr_accessor :compress
78
+ attr_accessor :compress_threshold
79
+ attr_accessor :redis_error_handler
80
+ attr_accessor :expires_in
81
+ attr_accessor :cache_validation_sampler
82
+ attr_accessor :cache_out_of_date_handler
83
+
84
+ attr_writer :global_cache_key_version
85
+ attr_writer :redis
86
+ attr_writer :tracer
87
+ attr_writer :logger
88
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ require 'redis'
3
+ require 'redis/distributed'
4
+
5
+ require_relative 'options'
6
+
7
+ # Redis::Distributed does not support reading from multiple read replicas. This
8
+ # class adds this functionality
9
+ class RedisMemo::Redis < Redis::Distributed
10
+ def initialize(options={})
11
+ clients =
12
+ if options.is_a?(Array)
13
+ options.map do |option|
14
+ if option.is_a?(Array)
15
+ RedisMemo::Redis::WithReplicas.new(option)
16
+ else
17
+ option[:logger] ||= RedisMemo::DefaultOptions.logger
18
+ ::Redis.new(option)
19
+ end
20
+ end
21
+ else
22
+ options[:logger] ||= RedisMemo::DefaultOptions.logger
23
+ [::Redis.new(options)]
24
+ end
25
+
26
+ # Pass in our own hash ring to use the clients with multi-read-replica
27
+ # support
28
+ hash_ring = Redis::HashRing.new(clients)
29
+
30
+ super([], ring: hash_ring)
31
+ end
32
+
33
+ class WithReplicas < ::Redis
34
+ def initialize(options)
35
+ primary_option = options.shift
36
+ @replicas = options.map do |option|
37
+ option[:logger] ||= RedisMemo::DefaultOptions.logger
38
+ ::Redis.new(option)
39
+ end
40
+
41
+ primary_option[:logger] ||= RedisMemo::DefaultOptions.logger
42
+ super(primary_option)
43
+ end
44
+
45
+ alias_method :get_primary, :get
46
+ alias_method :mget_primary, :mget
47
+ alias_method :mapped_mget_primary, :mapped_mget
48
+
49
+ def get(key)
50
+ return get_primary(key) if @replicas.empty?
51
+
52
+ @replicas.sample(1).first.get(key)
53
+ end
54
+
55
+ def mget(*keys, &blk)
56
+ return mget_primary(*keys, &blk) if @replicas.empty?
57
+
58
+ @replicas.sample(1).first.mget(*keys)
59
+ end
60
+
61
+ def mapped_mget(*keys)
62
+ return mapped_mget_primary(*keys) if @replicas.empty?
63
+
64
+ @replicas.sample(1).first.mapped_mget(*keys)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'options'
3
+
4
+ class RedisMemo::Tracer
5
+ def self.trace(span_name, method_id, &blk)
6
+ tracer = RedisMemo::DefaultOptions.tracer
7
+ return blk.call if tracer.nil?
8
+
9
+ tracer.trace(span_name, resource: method_id, service: 'redis_memo') do
10
+ blk.call
11
+ end
12
+ end
13
+
14
+ def self.set_tag(cache_hit:)
15
+ tracer = RedisMemo::DefaultOptions.tracer
16
+ return if tracer.nil? || !tracer.respond_to?(:active_span)
17
+
18
+ active_span = tracer.active_span
19
+ return if !active_span.respond_to?(:set_tag)
20
+
21
+ active_span.set_tag('cache_hit', cache_hit)
22
+ end
23
+ 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.0.0.alpha
4
+ version: 0.0.0.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chan Zuckerberg Initiative
@@ -10,6 +10,20 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2020-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: redis
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -25,35 +39,63 @@ dependencies:
25
39
  - !ruby/object:Gem::Version
26
40
  version: '4'
27
41
  - !ruby/object:Gem::Dependency
28
- name: sorbet
42
+ name: activerecord
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: codecov
29
57
  requirement: !ruby/object:Gem::Requirement
30
58
  requirements:
31
59
  - - ">="
32
60
  - !ruby/object:Gem::Version
33
- version: 0.4.4704
61
+ version: '0'
34
62
  type: :development
35
63
  prerelease: false
36
64
  version_requirements: !ruby/object:Gem::Requirement
37
65
  requirements:
38
66
  - - ">="
39
67
  - !ruby/object:Gem::Version
40
- version: 0.4.4704
68
+ version: '0'
41
69
  - !ruby/object:Gem::Dependency
42
- name: sorbet-static
70
+ name: database_cleaner-active_record
43
71
  requirement: !ruby/object:Gem::Requirement
44
72
  requirements:
45
73
  - - ">="
46
74
  - !ruby/object:Gem::Version
47
- version: 0.4.4704
75
+ version: '0'
48
76
  type: :development
49
77
  prerelease: false
50
78
  version_requirements: !ruby/object:Gem::Requirement
51
79
  requirements:
52
80
  - - ">="
53
81
  - !ruby/object:Gem::Version
54
- version: 0.4.4704
82
+ version: '0'
55
83
  - !ruby/object:Gem::Dependency
56
- name: codecov
84
+ name: pg
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
57
99
  requirement: !ruby/object:Gem::Requirement
58
100
  requirements:
59
101
  - - ">="
@@ -100,7 +142,25 @@ executables: []
100
142
  extensions: []
101
143
  extra_rdoc_files: []
102
144
  files:
145
+ - lib/redis-memo.rb
103
146
  - lib/redis_memo.rb
147
+ - lib/redis_memo/after_commit.rb
148
+ - lib/redis_memo/batch.rb
149
+ - lib/redis_memo/cache.rb
150
+ - lib/redis_memo/future.rb
151
+ - lib/redis_memo/memoizable.rb
152
+ - lib/redis_memo/memoizable/dependency.rb
153
+ - lib/redis_memo/memoizable/invalidation.rb
154
+ - lib/redis_memo/memoize_method.rb
155
+ - lib/redis_memo/memoize_method.rbi
156
+ - lib/redis_memo/memoize_records.rb
157
+ - lib/redis_memo/memoize_records/cached_select.rb
158
+ - lib/redis_memo/memoize_records/invalidation.rb
159
+ - lib/redis_memo/memoize_records/model_callback.rb
160
+ - lib/redis_memo/middleware.rb
161
+ - lib/redis_memo/options.rb
162
+ - lib/redis_memo/redis.rb
163
+ - lib/redis_memo/tracer.rb
104
164
  homepage: https://github.com/chanzuckerberg/redis-memo
105
165
  licenses:
106
166
  - MIT
@@ -120,9 +180,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
180
  - !ruby/object:Gem::Version
121
181
  version: 1.3.1
122
182
  requirements: []
123
- rubyforge_project:
124
- rubygems_version: 2.7.6.2
183
+ rubygems_version: 3.0.8
125
184
  signing_key:
126
185
  specification_version: 4
127
- summary: Redis based memoization
186
+ summary: A Redis-based version-addressable caching system. Memoize pure functions,
187
+ aggregated database queries, and 3rd party API calls.
128
188
  test_files: []