redis-memo 0.0.0.alpha → 0.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []