redis-memo 0.0.0.beta.2 → 0.0.0.beta.3
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 +50 -2
- data/lib/redis_memo/after_commit.rb +6 -1
- data/lib/redis_memo/cache.rb +9 -2
- data/lib/redis_memo/memoizable.rb +2 -3
- data/lib/redis_memo/memoizable/dependency.rb +18 -0
- data/lib/redis_memo/memoizable/invalidation.rb +33 -18
- data/lib/redis_memo/memoize_method.rb +12 -2
- data/lib/redis_memo/memoize_query.rb +5 -1
- data/lib/redis_memo/memoize_query/cached_select.rb +13 -0
- data/lib/redis_memo/memoize_query/invalidation.rb +92 -10
- data/lib/redis_memo/tracer.rb +4 -2
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd8d075c12b20782f27bdfe2488cba1c0f31f5fb537241a9cedcc72f484588b9
|
4
|
+
data.tar.gz: 0eef678b83746557ba84aca013e660ecc31a028042cf052606673179e185d61e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8f573eb77832f3dfb0600ab607c8d60090969082c86a9403a98b784899931da60d66f1b761bf863fd4007492ff47dee2d7880ec2f10cf5143fce33bc791684b
|
7
|
+
data.tar.gz: 4f9e44c13d2b4918f69f143f78eb4d80ad306b43abb650c03c481d727a88938f70c3ef89854e5c67afe92cc656df82ff44df84bb07878904bcdfbb2b86c15275
|
data/lib/redis-memo.rb
CHANGED
data/lib/redis_memo.rb
CHANGED
@@ -1,18 +1,53 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'active_support/all'
|
3
4
|
require 'digest'
|
4
5
|
require 'json'
|
6
|
+
require 'securerandom'
|
5
7
|
|
6
8
|
module RedisMemo
|
7
9
|
require 'redis_memo/memoize_method'
|
8
10
|
require 'redis_memo/memoize_query'
|
9
11
|
|
12
|
+
# A process-level +RedisMemo::Options+ instance that stores the global
|
13
|
+
# options. This object can be modified by +RedisMemo.configure+.
|
14
|
+
#
|
15
|
+
# +memoize_method+ allows users to provide method-level configuration.
|
16
|
+
# When no callsite-level configuration specified we will use the values in
|
17
|
+
# +DefaultOptions+ as the default value.
|
10
18
|
DefaultOptions = RedisMemo::Options.new
|
11
19
|
|
20
|
+
# @todo Move thread keys to +RedisMemo::ThreadKey+
|
21
|
+
THREAD_KEY_WITHOUT_MEMO = :__redis_memo_without_memo__
|
22
|
+
|
23
|
+
# Configure global-level default options. Those options will be used unless
|
24
|
+
# some options specified at +memoize_method+ callsite level. See
|
25
|
+
# +RedisMemo::Options+ for all the possible options.
|
26
|
+
#
|
27
|
+
# @yieldparam [RedisMemo::Options] default_options
|
28
|
+
# +RedisMemo::DefaultOptions+
|
29
|
+
# @return [void]
|
12
30
|
def self.configure(&blk)
|
13
31
|
blk.call(DefaultOptions)
|
14
32
|
end
|
15
33
|
|
34
|
+
# Batch Redis calls triggered by +memoize_method+ to minimize the round trips
|
35
|
+
# to Redis.
|
36
|
+
# - Batches cannot be nested
|
37
|
+
# - When a batch is still open (while still in the +RedisMemo.batch+ block)
|
38
|
+
# the return value of any memoized method is a +RedisMemo::Future+ instead of
|
39
|
+
# the actual method value
|
40
|
+
# - The actual method values are returned as a list, in the same order as
|
41
|
+
# invoking, after exiting the block
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# results = RedisMemo.batch do
|
45
|
+
# 5.times { |i| memoized_calculation(i) }
|
46
|
+
# nil # Not the return value of the block
|
47
|
+
# end
|
48
|
+
# results.size == 5 # true
|
49
|
+
#
|
50
|
+
# See +RedisMemo::Batch+ for more information.
|
16
51
|
def self.batch(&blk)
|
17
52
|
RedisMemo::Batch.open
|
18
53
|
blk.call
|
@@ -21,10 +56,17 @@ module RedisMemo
|
|
21
56
|
RedisMemo::Batch.close
|
22
57
|
end
|
23
58
|
|
59
|
+
# @todo Move this method out of the top namespace
|
24
60
|
def self.checksum(serialized)
|
25
61
|
Digest::SHA1.base64digest(serialized)
|
26
62
|
end
|
27
63
|
|
64
|
+
# @todo Move this method out of the top namespace
|
65
|
+
def self.uuid
|
66
|
+
SecureRandom.uuid
|
67
|
+
end
|
68
|
+
|
69
|
+
# @todo Move this method out of the top namespace
|
28
70
|
def self.deep_sort_hash(orig_hash)
|
29
71
|
{}.tap do |new_hash|
|
30
72
|
orig_hash.sort.each do |k, v|
|
@@ -33,12 +75,17 @@ module RedisMemo
|
|
33
75
|
end
|
34
76
|
end
|
35
77
|
|
36
|
-
|
37
|
-
|
78
|
+
# Whether the current execution context has been configured to skip
|
79
|
+
# memoization and use the uncached code path.
|
80
|
+
#
|
81
|
+
# @return [Boolean]
|
38
82
|
def self.without_memo?
|
39
83
|
Thread.current[THREAD_KEY_WITHOUT_MEMO] == true
|
40
84
|
end
|
41
85
|
|
86
|
+
# Configure the wrapped code in the block to skip memoization.
|
87
|
+
#
|
88
|
+
# @yield [] no_args The block of code to skip memoization.
|
42
89
|
def self.without_memo
|
43
90
|
prev_value = Thread.current[THREAD_KEY_WITHOUT_MEMO]
|
44
91
|
Thread.current[THREAD_KEY_WITHOUT_MEMO] = true
|
@@ -47,6 +94,7 @@ module RedisMemo
|
|
47
94
|
Thread.current[THREAD_KEY_WITHOUT_MEMO] = prev_value
|
48
95
|
end
|
49
96
|
|
97
|
+
# @todo Move errors to a separate file errors.rb
|
50
98
|
class ArgumentError < ::ArgumentError; end
|
51
99
|
class RuntimeError < ::RuntimeError; end
|
52
100
|
end
|
@@ -66,7 +66,12 @@ class RedisMemo::AfterCommit
|
|
66
66
|
@@pending_memo_versions.each do |key, version|
|
67
67
|
invalidation_queue =
|
68
68
|
RedisMemo::Memoizable::Invalidation.class_variable_get(:@@invalidation_queue)
|
69
|
-
|
69
|
+
|
70
|
+
invalidation_queue << RedisMemo::Memoizable::Invalidation::Task.new(
|
71
|
+
key,
|
72
|
+
version,
|
73
|
+
@@previous_memo_versions[key],
|
74
|
+
)
|
70
75
|
end
|
71
76
|
|
72
77
|
RedisMemo::Memoizable::Invalidation.drain_invalidation_queue
|
data/lib/redis_memo/cache.rb
CHANGED
@@ -5,8 +5,9 @@ require_relative 'redis'
|
|
5
5
|
class RedisMemo::Cache < ActiveSupport::Cache::RedisCacheStore
|
6
6
|
class Rescuable < Exception; end
|
7
7
|
|
8
|
-
THREAD_KEY_LOCAL_CACHE
|
9
|
-
|
8
|
+
THREAD_KEY_LOCAL_CACHE = :__redis_memo_cache_local_cache__
|
9
|
+
THREAD_KEY_LOCAL_DEPENDENCY_CACHE = :__redis_memo_local_cache_dependency_cache__
|
10
|
+
THREAD_KEY_RAISE_ERROR = :__redis_memo_cache_raise_error__
|
10
11
|
|
11
12
|
@@redis = nil
|
12
13
|
@@redis_store = nil
|
@@ -44,12 +45,18 @@ class RedisMemo::Cache < ActiveSupport::Cache::RedisCacheStore
|
|
44
45
|
Thread.current[THREAD_KEY_LOCAL_CACHE]
|
45
46
|
end
|
46
47
|
|
48
|
+
def self.local_dependency_cache
|
49
|
+
Thread.current[THREAD_KEY_LOCAL_DEPENDENCY_CACHE]
|
50
|
+
end
|
51
|
+
|
47
52
|
class << self
|
48
53
|
def with_local_cache(&blk)
|
49
54
|
Thread.current[THREAD_KEY_LOCAL_CACHE] = {}
|
55
|
+
Thread.current[THREAD_KEY_LOCAL_DEPENDENCY_CACHE] = {}
|
50
56
|
blk.call
|
51
57
|
ensure
|
52
58
|
Thread.current[THREAD_KEY_LOCAL_CACHE] = nil
|
59
|
+
Thread.current[THREAD_KEY_LOCAL_DEPENDENCY_CACHE] = nil
|
53
60
|
end
|
54
61
|
|
55
62
|
# RedisCacheStore doesn't read from the local cache before reading from redis
|
@@ -1,5 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'securerandom'
|
3
2
|
|
4
3
|
class RedisMemo::Memoizable
|
5
4
|
require_relative 'memoizable/dependency'
|
@@ -52,7 +51,7 @@ class RedisMemo::Memoizable
|
|
52
51
|
cache_key = instance.cache_key
|
53
52
|
RedisMemo::Memoizable::Invalidation.bump_version_later(
|
54
53
|
cache_key,
|
55
|
-
|
54
|
+
RedisMemo.uuid,
|
56
55
|
)
|
57
56
|
end
|
58
57
|
|
@@ -96,7 +95,7 @@ class RedisMemo::Memoizable
|
|
96
95
|
# cached result.
|
97
96
|
need_to_bump_versions = true
|
98
97
|
|
99
|
-
new_version =
|
98
|
+
new_version = RedisMemo.uuid
|
100
99
|
RedisMemo::Memoizable::Invalidation.bump_version_later(
|
101
100
|
key,
|
102
101
|
new_version,
|
@@ -25,6 +25,9 @@ class RedisMemo::Memoizable::Dependency
|
|
25
25
|
# Extract dependencies from the current memoizable and recurse
|
26
26
|
instance_exec(&memo.depends_on)
|
27
27
|
end
|
28
|
+
when ActiveRecord::Relation
|
29
|
+
extracted = extract_dependencies_for_relation(dependency)
|
30
|
+
nodes.merge!(extracted.nodes)
|
28
31
|
when UsingActiveRecord
|
29
32
|
[
|
30
33
|
dependency.redis_memo_class_memoizable,
|
@@ -40,6 +43,21 @@ class RedisMemo::Memoizable::Dependency
|
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
46
|
+
def extract_dependencies_for_relation(relation)
|
47
|
+
# Extract the dependent memos of an Arel without calling exec_query to actually execute the query
|
48
|
+
RedisMemo::MemoizeQuery::CachedSelect.with_new_query_context do
|
49
|
+
connection = ActiveRecord::Base.connection
|
50
|
+
query, binds, _ = connection.send(:to_sql_and_binds, relation.arel)
|
51
|
+
RedisMemo::MemoizeQuery::CachedSelect.current_query = relation.arel
|
52
|
+
is_query_cached = RedisMemo::MemoizeQuery::CachedSelect.extract_bind_params(query)
|
53
|
+
raise(
|
54
|
+
RedisMemo::ArgumentError,
|
55
|
+
"Invalid Arel dependency. Query is not enabled for RedisMemo caching."
|
56
|
+
) unless is_query_cached
|
57
|
+
extracted_dependency = connection.dependency_of(:exec_query, query, nil, binds)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
43
61
|
class UsingActiveRecord
|
44
62
|
def self.===(dependency)
|
45
63
|
RedisMemo::MemoizeQuery.using_active_record?(dependency)
|
@@ -3,12 +3,29 @@ require_relative '../after_commit'
|
|
3
3
|
require_relative '../cache'
|
4
4
|
|
5
5
|
module RedisMemo::Memoizable::Invalidation
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
class Task
|
7
|
+
attr_reader :key
|
8
|
+
attr_reader :version
|
9
|
+
attr_reader :previous_version
|
10
|
+
|
11
|
+
def initialize(key, version, previous_version)
|
12
|
+
@key = key
|
13
|
+
@version = version
|
14
|
+
@previous_version = previous_version
|
15
|
+
@created_at = current_timestamp
|
16
|
+
end
|
17
|
+
|
18
|
+
def current_timestamp
|
19
|
+
Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
|
20
|
+
end
|
21
|
+
|
22
|
+
def duration
|
23
|
+
current_timestamp - @created_at
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# This is a thread safe data structure to handle transient network errors
|
28
|
+
# during cache invalidation
|
12
29
|
#
|
13
30
|
# When an invalidation call arrives at Redis, we only bump to the specified
|
14
31
|
# version (so the cached results using that version will become visible) if
|
@@ -28,8 +45,6 @@ module RedisMemo::Memoizable::Invalidation
|
|
28
45
|
@@invalidation_queue = Queue.new
|
29
46
|
|
30
47
|
def self.bump_version_later(key, version, previous_version: nil)
|
31
|
-
RedisMemo::DefaultOptions.logger&.info("[received] Bump memo key #{key}")
|
32
|
-
|
33
48
|
if RedisMemo::AfterCommit.in_transaction?
|
34
49
|
previous_version ||= RedisMemo::AfterCommit.pending_memo_versions[key]
|
35
50
|
end
|
@@ -52,7 +67,7 @@ module RedisMemo::Memoizable::Invalidation
|
|
52
67
|
previous_version: previous_version,
|
53
68
|
)
|
54
69
|
else
|
55
|
-
@@invalidation_queue <<
|
70
|
+
@@invalidation_queue << Task.new(key, version, previous_version)
|
56
71
|
end
|
57
72
|
end
|
58
73
|
|
@@ -89,31 +104,31 @@ module RedisMemo::Memoizable::Invalidation
|
|
89
104
|
return redis.call('set', key, new_version, unpack(px))
|
90
105
|
LUA
|
91
106
|
|
92
|
-
def self.bump_version(
|
93
|
-
RedisMemo::Tracer.trace('redis_memo.memoizable.bump_version',
|
107
|
+
def self.bump_version(task)
|
108
|
+
RedisMemo::Tracer.trace('redis_memo.memoizable.bump_version', task.key) do
|
94
109
|
ttl = RedisMemo::DefaultOptions.expires_in
|
95
110
|
ttl = (ttl * 1000.0).to_i if ttl
|
96
111
|
RedisMemo::Cache.redis.eval(
|
97
112
|
LUA_BUMP_VERSION,
|
98
|
-
keys: [
|
99
|
-
argv: [previous_version, version,
|
113
|
+
keys: [task.key],
|
114
|
+
argv: [task.previous_version, task.version, RedisMemo.uuid, ttl],
|
100
115
|
)
|
116
|
+
RedisMemo::Tracer.set_tag(enqueue_to_finish: task.duration)
|
101
117
|
end
|
102
|
-
RedisMemo::DefaultOptions.logger&.info("[performed] Bump memo key #{cache_key}")
|
103
118
|
end
|
104
119
|
|
105
120
|
def self.drain_invalidation_queue_now
|
106
121
|
retry_queue = []
|
107
122
|
until @@invalidation_queue.empty?
|
108
|
-
|
123
|
+
task = @@invalidation_queue.pop
|
109
124
|
begin
|
110
|
-
bump_version(
|
125
|
+
bump_version(task)
|
111
126
|
rescue SignalException, Redis::BaseConnectionError
|
112
|
-
retry_queue <<
|
127
|
+
retry_queue << task
|
113
128
|
end
|
114
129
|
end
|
115
130
|
ensure
|
116
|
-
retry_queue.each { |
|
131
|
+
retry_queue.each { |task| @@invalidation_queue << task }
|
117
132
|
end
|
118
133
|
|
119
134
|
at_exit do
|
@@ -52,7 +52,7 @@ module RedisMemo::MemoizeMethod
|
|
52
52
|
"#{method_name} is not a memoized method"
|
53
53
|
)
|
54
54
|
end
|
55
|
-
RedisMemo::MemoizeMethod.
|
55
|
+
RedisMemo::MemoizeMethod.get_or_extract_dependencies(self, *method_args, &method_depends_on)
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -71,11 +71,21 @@ module RedisMemo::MemoizeMethod
|
|
71
71
|
dependency
|
72
72
|
end
|
73
73
|
|
74
|
+
def self.get_or_extract_dependencies(ref, *method_args, &depends_on)
|
75
|
+
if RedisMemo::Cache.local_dependency_cache
|
76
|
+
RedisMemo::Cache.local_dependency_cache[ref] ||= {}
|
77
|
+
RedisMemo::Cache.local_dependency_cache[ref][depends_on] ||= {}
|
78
|
+
RedisMemo::Cache.local_dependency_cache[ref][depends_on][method_args] ||= extract_dependencies(ref, *method_args, &depends_on)
|
79
|
+
else
|
80
|
+
extract_dependencies(ref, *method_args, &depends_on)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
74
84
|
def self.method_cache_keys(future_contexts)
|
75
85
|
memos = Array.new(future_contexts.size)
|
76
86
|
future_contexts.each_with_index do |(ref, _, method_args, depends_on), i|
|
77
87
|
if depends_on
|
78
|
-
dependency =
|
88
|
+
dependency = get_or_extract_dependencies(ref, *method_args, &depends_on)
|
79
89
|
memos[i] = dependency.memos
|
80
90
|
end
|
81
91
|
end
|
@@ -103,6 +103,10 @@ if defined?(ActiveRecord)
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def self.invalidate(record)
|
106
|
+
RedisMemo::Memoizable.invalidate(to_memos(record))
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.to_memos(record)
|
106
110
|
# Invalidate memos with current values
|
107
111
|
memos_to_invalidate = memoized_columns(record.class).map do |columns|
|
108
112
|
props = {}
|
@@ -137,7 +141,7 @@ if defined?(ActiveRecord)
|
|
137
141
|
end
|
138
142
|
end
|
139
143
|
|
140
|
-
|
144
|
+
memos_to_invalidate
|
141
145
|
end
|
142
146
|
end
|
143
147
|
end
|
@@ -187,6 +187,19 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
187
187
|
Thread.current[THREAD_KEY_AREL_BIND_PARAMS] = nil
|
188
188
|
end
|
189
189
|
|
190
|
+
def self.with_new_query_context
|
191
|
+
prev_arel = Thread.current[THREAD_KEY_AREL]
|
192
|
+
prev_substitutes = Thread.current[THREAD_KEY_SUBSTITUTES]
|
193
|
+
prev_bind_params = Thread.current[THREAD_KEY_AREL_BIND_PARAMS]
|
194
|
+
RedisMemo::MemoizeQuery::CachedSelect.reset_current_query
|
195
|
+
|
196
|
+
yield
|
197
|
+
ensure
|
198
|
+
Thread.current[THREAD_KEY_AREL] = prev_arel
|
199
|
+
Thread.current[THREAD_KEY_SUBSTITUTES] = prev_substitutes
|
200
|
+
Thread.current[THREAD_KEY_AREL_BIND_PARAMS] = prev_bind_params
|
201
|
+
end
|
202
|
+
|
190
203
|
private
|
191
204
|
|
192
205
|
# A pre-order Depth First Search
|
@@ -33,7 +33,6 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
33
33
|
# Methods that won't trigger model callbacks
|
34
34
|
# https://guides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks
|
35
35
|
%i(
|
36
|
-
import import!
|
37
36
|
decrement_counter
|
38
37
|
delete_all delete_by
|
39
38
|
increment_counter
|
@@ -43,7 +42,7 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
43
42
|
upsert upsert_all
|
44
43
|
).each do |method_name|
|
45
44
|
# Example: Model.update_all
|
46
|
-
|
45
|
+
rewrite_default_method(
|
47
46
|
model_class,
|
48
47
|
model_class,
|
49
48
|
method_name,
|
@@ -51,7 +50,7 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
51
50
|
)
|
52
51
|
|
53
52
|
# Example: Model.where(...).update_all
|
54
|
-
|
53
|
+
rewrite_default_method(
|
55
54
|
model_class,
|
56
55
|
model_class.const_get(:ActiveRecord_Relation),
|
57
56
|
method_name,
|
@@ -59,20 +58,28 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
59
58
|
)
|
60
59
|
end
|
61
60
|
|
61
|
+
%i(
|
62
|
+
import import!
|
63
|
+
).each do |method_name|
|
64
|
+
rewrite_import_method(
|
65
|
+
model_class,
|
66
|
+
method_name,
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
62
70
|
model_class.class_variable_set(var_name, true)
|
63
71
|
end
|
64
72
|
|
65
73
|
private
|
66
74
|
|
67
75
|
#
|
68
|
-
# There’s no good way to perform fine-grind cache invalidation when
|
69
|
-
# are bulk update operations such as
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
# bulk_updates.
|
76
|
+
# There’s no good way to perform fine-grind cache invalidation when
|
77
|
+
# operations are bulk update operations such as update_all, and delete_all
|
78
|
+
# witout fetching additional data from the database, which might lead to
|
79
|
+
# performance degradation. Thus, by default, we simply invalidate all
|
80
|
+
# existing cached records after each bulk_updates.
|
74
81
|
#
|
75
|
-
def self.
|
82
|
+
def self.rewrite_default_method(model_class, klass, method_name, class_method:)
|
76
83
|
methods = class_method ? :methods : :instance_methods
|
77
84
|
return unless klass.send(methods).include?(method_name)
|
78
85
|
|
@@ -87,4 +94,79 @@ class RedisMemo::MemoizeQuery::Invalidation
|
|
87
94
|
end
|
88
95
|
end
|
89
96
|
end
|
97
|
+
|
98
|
+
def self.rewrite_import_method(model_class, method_name)
|
99
|
+
# This optimization to avoid over-invalidation only works on postgres
|
100
|
+
unless ActiveRecord::Base.connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
101
|
+
rewrite_default_method(model_class, model_class, method_name, class_method: true)
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
model_class.singleton_class.class_eval do
|
106
|
+
alias_method :"#{method_name}_without_redis_memo_invalidation", method_name
|
107
|
+
|
108
|
+
# For the args format, see
|
109
|
+
# https://github.com/zdennis/activerecord-import/blob/master/lib/activerecord-import/import.rb#L128
|
110
|
+
define_method method_name do |*args, &blk|
|
111
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
112
|
+
records = args[args.last.is_a?(Hash) ? -2 : -1]
|
113
|
+
unique_by = options[:on_duplicate_key_update]
|
114
|
+
if unique_by.is_a?(Hash)
|
115
|
+
unique_by = unique_by[:columns]
|
116
|
+
end
|
117
|
+
|
118
|
+
if records.last.is_a?(Hash)
|
119
|
+
records.map! { |hash| model_class.new(hash) }
|
120
|
+
end
|
121
|
+
|
122
|
+
# Invalidate the records before and after the import to resolve
|
123
|
+
# - default values filled by the database
|
124
|
+
# - updates on conflict conditions
|
125
|
+
records_to_invalidate =
|
126
|
+
if unique_by
|
127
|
+
RedisMemo::MemoizeQuery::Invalidation.send(
|
128
|
+
:select_by_uniq_index,
|
129
|
+
records,
|
130
|
+
unique_by,
|
131
|
+
)
|
132
|
+
else
|
133
|
+
[]
|
134
|
+
end
|
135
|
+
|
136
|
+
result = send(:"#{method_name}_without_redis_memo_invalidation", *args, &blk)
|
137
|
+
|
138
|
+
records_to_invalidate += RedisMemo.without_memo do
|
139
|
+
# Not all databases support "RETURNING", which is useful when
|
140
|
+
# invaldating records after bulk creation
|
141
|
+
model_class.where(model_class.primary_key => result.ids).to_a
|
142
|
+
end
|
143
|
+
|
144
|
+
memos_to_invalidate = records_to_invalidate.map do |record|
|
145
|
+
RedisMemo::MemoizeQuery.to_memos(record)
|
146
|
+
end
|
147
|
+
RedisMemo::Memoizable.invalidate(memos_to_invalidate.flatten)
|
148
|
+
|
149
|
+
result
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.select_by_uniq_index(records, unique_by)
|
155
|
+
model_class = records.first.class
|
156
|
+
or_chain = nil
|
157
|
+
|
158
|
+
records.each do |record|
|
159
|
+
conditions = {}
|
160
|
+
unique_by.each do |column|
|
161
|
+
conditions[column] = record.send(column)
|
162
|
+
end
|
163
|
+
if or_chain
|
164
|
+
or_chain = or_chain.or(model_class.where(conditions))
|
165
|
+
else
|
166
|
+
or_chain = model_class.where(conditions)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
RedisMemo.without_memo { or_chain.to_a }
|
171
|
+
end
|
90
172
|
end
|
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chan Zuckerberg Initiative
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '5.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activerecord-import
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: codecov
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|