redis-memo 0.0.0.beta → 0.0.0.beta.5
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 +4 -0
- data/lib/redis_memo.rb +52 -3
- data/lib/redis_memo/after_commit.rb +6 -1
- data/lib/redis_memo/cache.rb +18 -5
- data/lib/redis_memo/connection_pool.rb +27 -0
- data/lib/redis_memo/future.rb +3 -3
- data/lib/redis_memo/memoizable.rb +7 -4
- data/lib/redis_memo/memoizable/dependency.rb +42 -12
- data/lib/redis_memo/memoizable/invalidation.rb +39 -20
- data/lib/redis_memo/memoize_method.rb +53 -13
- data/lib/redis_memo/memoize_query.rb +151 -0
- data/lib/redis_memo/{memoize_records → memoize_query}/cached_select.rb +80 -199
- data/lib/redis_memo/memoize_query/cached_select/bind_params.rb +127 -0
- data/lib/redis_memo/memoize_query/cached_select/connection_adapter.rb +41 -0
- data/lib/redis_memo/memoize_query/cached_select/statement_cache.rb +16 -0
- data/lib/redis_memo/memoize_query/invalidation.rb +211 -0
- data/lib/redis_memo/memoize_query/memoize_table_column.rb +5 -0
- data/lib/redis_memo/{memoize_records → memoize_query}/model_callback.rb +3 -3
- data/lib/redis_memo/options.rb +17 -18
- data/lib/redis_memo/redis.rb +2 -1
- data/lib/redis_memo/tracer.rb +4 -2
- metadata +46 -14
- data/lib/redis_memo/memoize_method.rbi +0 -10
- data/lib/redis_memo/memoize_records.rb +0 -146
- data/lib/redis_memo/memoize_records/invalidation.rb +0 -85
data/lib/redis_memo/redis.rb
CHANGED
@@ -31,7 +31,8 @@ class RedisMemo::Redis < Redis::Distributed
|
|
31
31
|
end
|
32
32
|
|
33
33
|
class WithReplicas < ::Redis
|
34
|
-
def initialize(
|
34
|
+
def initialize(orig_options)
|
35
|
+
options = orig_options.dup
|
35
36
|
primary_option = options.shift
|
36
37
|
@replicas = options.map do |option|
|
37
38
|
option[:logger] ||= RedisMemo::DefaultOptions.logger
|
data/lib/redis_memo/tracer.rb
CHANGED
@@ -11,13 +11,15 @@ class RedisMemo::Tracer
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
def self.set_tag(
|
14
|
+
def self.set_tag(**tags)
|
15
15
|
tracer = RedisMemo::DefaultOptions.tracer
|
16
16
|
return if tracer.nil? || !tracer.respond_to?(:active_span)
|
17
17
|
|
18
18
|
active_span = tracer.active_span
|
19
19
|
return if !active_span.respond_to?(:set_tag)
|
20
20
|
|
21
|
-
|
21
|
+
tags.each do |name, value|
|
22
|
+
active_span.set_tag(name, value)
|
23
|
+
end
|
22
24
|
end
|
23
25
|
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.beta
|
4
|
+
version: 0.0.0.beta.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chan Zuckerberg Initiative
|
@@ -14,44 +14,72 @@ dependencies:
|
|
14
14
|
name: activesupport
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '5.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '5.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: redis
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 4.0.1
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 4.0.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: connection_pool
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.2.3
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
54
|
+
version: 2.2.3
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: activerecord
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
|
-
- - "
|
59
|
+
- - ">="
|
46
60
|
- !ruby/object:Gem::Version
|
47
61
|
version: '5.2'
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
|
-
- - "
|
66
|
+
- - ">="
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '5.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activerecord-import
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: codecov
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -147,16 +175,20 @@ files:
|
|
147
175
|
- lib/redis_memo/after_commit.rb
|
148
176
|
- lib/redis_memo/batch.rb
|
149
177
|
- lib/redis_memo/cache.rb
|
178
|
+
- lib/redis_memo/connection_pool.rb
|
150
179
|
- lib/redis_memo/future.rb
|
151
180
|
- lib/redis_memo/memoizable.rb
|
152
181
|
- lib/redis_memo/memoizable/dependency.rb
|
153
182
|
- lib/redis_memo/memoizable/invalidation.rb
|
154
183
|
- lib/redis_memo/memoize_method.rb
|
155
|
-
- lib/redis_memo/
|
156
|
-
- lib/redis_memo/
|
157
|
-
- lib/redis_memo/
|
158
|
-
- lib/redis_memo/
|
159
|
-
- lib/redis_memo/
|
184
|
+
- lib/redis_memo/memoize_query.rb
|
185
|
+
- lib/redis_memo/memoize_query/cached_select.rb
|
186
|
+
- lib/redis_memo/memoize_query/cached_select/bind_params.rb
|
187
|
+
- lib/redis_memo/memoize_query/cached_select/connection_adapter.rb
|
188
|
+
- lib/redis_memo/memoize_query/cached_select/statement_cache.rb
|
189
|
+
- lib/redis_memo/memoize_query/invalidation.rb
|
190
|
+
- lib/redis_memo/memoize_query/memoize_table_column.rb
|
191
|
+
- lib/redis_memo/memoize_query/model_callback.rb
|
160
192
|
- lib/redis_memo/middleware.rb
|
161
193
|
- lib/redis_memo/options.rb
|
162
194
|
- lib/redis_memo/redis.rb
|
@@ -1,146 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require_relative 'memoize_method'
|
3
|
-
|
4
|
-
#
|
5
|
-
# Automatically invalidate memoizable when modifying ActiveRecords objects.
|
6
|
-
# You still need to invalidate memos when you are using SQL queries to perform
|
7
|
-
# update / delete (does not trigger record callbacks)
|
8
|
-
#
|
9
|
-
module RedisMemo::MemoizeRecords
|
10
|
-
require_relative 'memoize_records/cached_select'
|
11
|
-
require_relative 'memoize_records/invalidation'
|
12
|
-
require_relative 'memoize_records/model_callback'
|
13
|
-
|
14
|
-
# TODO: MemoizeRecords -> MemoizeQuery
|
15
|
-
def memoize_records
|
16
|
-
RedisMemo::MemoizeRecords.using_active_record!(self)
|
17
|
-
|
18
|
-
memoize_table_column(primary_key.to_sym, editable: false)
|
19
|
-
end
|
20
|
-
|
21
|
-
# Only editable columns will be used to create memos that are invalidatable
|
22
|
-
# after each record save
|
23
|
-
def memoize_table_column(*raw_columns, editable: true)
|
24
|
-
RedisMemo::MemoizeRecords.using_active_record!(self)
|
25
|
-
|
26
|
-
columns = raw_columns.map(&:to_sym).sort
|
27
|
-
|
28
|
-
RedisMemo::MemoizeRecords.memoized_columns(self, editable_only: true) << columns if editable
|
29
|
-
RedisMemo::MemoizeRecords.memoized_columns(self, editable_only: false) << columns
|
30
|
-
|
31
|
-
RedisMemo::MemoizeRecords::ModelCallback.install(self)
|
32
|
-
RedisMemo::MemoizeRecords::Invalidation.install(self)
|
33
|
-
|
34
|
-
if ENV['REDIS_MEMO_DISABLE_CACHED_SELECT'] != 'true'
|
35
|
-
RedisMemo::MemoizeRecords::CachedSelect.install(ActiveRecord::Base.connection)
|
36
|
-
end
|
37
|
-
|
38
|
-
columns.each do |column|
|
39
|
-
unless self.columns_hash.include?(column.to_s)
|
40
|
-
raise(
|
41
|
-
RedisMemo::ArgumentError,
|
42
|
-
"'#{self.name}' does not contain column '#{column}'",
|
43
|
-
)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
rescue ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid
|
47
|
-
# no-opts: models with memoize_table_column decleared might be loaded in
|
48
|
-
# rake tasks that are used to create databases
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.using_active_record!(model_class)
|
52
|
-
unless model_class.respond_to?(:<) && model_class < ActiveRecord::Base
|
53
|
-
raise RedisMemo::ArgumentError, "'#{model_class.name}' does not use ActiveRecord"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
@@memoized_columns = Hash.new { |h, k| h[k] = [Set.new, Set.new] }
|
58
|
-
|
59
|
-
def self.memoized_columns(model_or_table, editable_only: false)
|
60
|
-
table = model_or_table.is_a?(Class) ? model_or_table.table_name : model_or_table
|
61
|
-
@@memoized_columns[table.to_sym][editable_only ? 1 : 0]
|
62
|
-
end
|
63
|
-
|
64
|
-
# extra_props are considered as AND conditions on the model class
|
65
|
-
def self.create_memo(model_class, **extra_props)
|
66
|
-
RedisMemo::MemoizeRecords.using_active_record!(model_class)
|
67
|
-
|
68
|
-
keys = extra_props.keys.sort
|
69
|
-
if !keys.empty? && !RedisMemo::MemoizeRecords.memoized_columns(model_class).include?(keys)
|
70
|
-
raise(
|
71
|
-
RedisMemo::ArgumentError,
|
72
|
-
"'#{model_class.name}' has not memoized columns: #{keys}",
|
73
|
-
)
|
74
|
-
end
|
75
|
-
|
76
|
-
extra_props.each do |key, values|
|
77
|
-
# The data type is ensured by the database, thus we don't need to cast
|
78
|
-
# types here for better performance
|
79
|
-
column_name = key.to_s
|
80
|
-
values = [values] unless values.is_a?(Enumerable)
|
81
|
-
extra_props[key] =
|
82
|
-
if model_class.defined_enums.include?(column_name)
|
83
|
-
enum_mapping = model_class.defined_enums[column_name]
|
84
|
-
values.map do |value|
|
85
|
-
# Assume a value is a converted enum if it does not exist in the
|
86
|
-
# enum mapping
|
87
|
-
(enum_mapping[value.to_s] || value).to_s
|
88
|
-
end
|
89
|
-
else
|
90
|
-
values.map(&:to_s)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
RedisMemo::Memoizable.new(
|
95
|
-
__redis_memo_memoize_records_model_class_name__: model_class.name,
|
96
|
-
**extra_props,
|
97
|
-
)
|
98
|
-
end
|
99
|
-
|
100
|
-
def self.invalidate_all(model_class)
|
101
|
-
RedisMemo::Tracer.trace(
|
102
|
-
'redis_memo.memoizable.invalidate_all',
|
103
|
-
model_class.name,
|
104
|
-
) do
|
105
|
-
RedisMemo::Memoizable.invalidate([model_class.redis_memo_class_memoizable])
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def self.invalidate(record)
|
110
|
-
# Invalidate memos with current values
|
111
|
-
memos_to_invalidate = memoized_columns(record.class).map do |columns|
|
112
|
-
props = {}
|
113
|
-
columns.each do |column|
|
114
|
-
props[column] = record.send(column)
|
115
|
-
end
|
116
|
-
|
117
|
-
RedisMemo::MemoizeRecords.create_memo(record.class, **props)
|
118
|
-
end
|
119
|
-
|
120
|
-
# Create memos with previous values if
|
121
|
-
# - there are saved changes
|
122
|
-
# - this is not creating a new record
|
123
|
-
if !record.saved_changes.empty? && !record.saved_changes.include?(record.class.primary_key)
|
124
|
-
previous_values = {}
|
125
|
-
record.saved_changes.each do |column, (previous_value, _)|
|
126
|
-
previous_values[column.to_sym] = previous_value
|
127
|
-
end
|
128
|
-
|
129
|
-
memoized_columns(record.class, editable_only: true).each do |columns|
|
130
|
-
props = previous_values.slice(*columns)
|
131
|
-
next if props.empty?
|
132
|
-
|
133
|
-
# Fill the column values that have not changed
|
134
|
-
columns.each do |column|
|
135
|
-
next if props.include?(column)
|
136
|
-
|
137
|
-
props[column] = record.send(column)
|
138
|
-
end
|
139
|
-
|
140
|
-
memos_to_invalidate << RedisMemo::MemoizeRecords.create_memo(record.class, **props)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
RedisMemo::Memoizable.invalidate(memos_to_invalidate)
|
145
|
-
end
|
146
|
-
end
|
@@ -1,85 +0,0 @@
|
|
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
|