redis-memo 0.0.0.alpha → 0.0.0.beta.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/redis-memo.rb +5 -0
- data/lib/redis_memo.rb +96 -1
- data/lib/redis_memo/after_commit.rb +119 -0
- data/lib/redis_memo/batch.rb +54 -0
- data/lib/redis_memo/cache.rb +108 -0
- data/lib/redis_memo/connection_pool.rb +27 -0
- data/lib/redis_memo/future.rb +125 -0
- data/lib/redis_memo/memoizable.rb +119 -0
- data/lib/redis_memo/memoizable/dependency.rb +66 -0
- data/lib/redis_memo/memoizable/invalidation.rb +142 -0
- data/lib/redis_memo/memoize_method.rb +133 -0
- data/lib/redis_memo/memoize_query.rb +151 -0
- data/lib/redis_memo/memoize_query/cached_select.rb +380 -0
- 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_query/model_callback.rb +21 -0
- data/lib/redis_memo/middleware.rb +18 -0
- data/lib/redis_memo/options.rb +87 -0
- data/lib/redis_memo/redis.rb +67 -0
- data/lib/redis_memo/tracer.rb +25 -0
- metadata +106 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72bcf0679b811825222a6b38de0fcdadf4546640d2f61b18ebc827e37137dc41
|
4
|
+
data.tar.gz: 7a55c7bbd084a24afe085e85bc5cd5c486cf240094b64ca09d48e9e0ce126384
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a3af887512e4b6571829fcfda719e43abb917eb4773407bd27788b0b671a42263ec31c2f6286f0a1279a354851c8c8cb48eb9701cba59863e500ca47419080e
|
7
|
+
data.tar.gz: feeb72b177ec07953b839e6e0759cbdf42dfd4eddd19f89599e721589462e849e7685a659b03128504d670764223f1805402cb3ee8a96411b834f229ba837d99
|
data/lib/redis-memo.rb
ADDED
data/lib/redis_memo.rb
CHANGED
@@ -1,6 +1,101 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'active_support/all'
|
4
|
+
require 'digest'
|
5
|
+
require 'json'
|
6
|
+
require 'securerandom'
|
4
7
|
|
5
8
|
module RedisMemo
|
9
|
+
require 'redis_memo/memoize_method'
|
10
|
+
require 'redis_memo/memoize_query'
|
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.
|
18
|
+
DefaultOptions = RedisMemo::Options.new
|
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]
|
30
|
+
def self.configure(&blk)
|
31
|
+
blk.call(DefaultOptions)
|
32
|
+
end
|
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.
|
51
|
+
def self.batch(&blk)
|
52
|
+
RedisMemo::Batch.open
|
53
|
+
blk.call
|
54
|
+
RedisMemo::Batch.execute
|
55
|
+
ensure
|
56
|
+
RedisMemo::Batch.close
|
57
|
+
end
|
58
|
+
|
59
|
+
# @todo Move this method out of the top namespace
|
60
|
+
def self.checksum(serialized)
|
61
|
+
Digest::SHA1.base64digest(serialized)
|
62
|
+
end
|
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
|
70
|
+
def self.deep_sort_hash(orig_hash)
|
71
|
+
{}.tap do |new_hash|
|
72
|
+
orig_hash.sort.each do |k, v|
|
73
|
+
new_hash[k] = v.is_a?(Hash) ? deep_sort_hash(v) : v
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Whether the current execution context has been configured to skip
|
79
|
+
# memoization and use the uncached code path.
|
80
|
+
#
|
81
|
+
# @return [Boolean]
|
82
|
+
def self.without_memo?
|
83
|
+
Thread.current[THREAD_KEY_WITHOUT_MEMO] == true
|
84
|
+
end
|
85
|
+
|
86
|
+
# Configure the wrapped code in the block to skip memoization.
|
87
|
+
#
|
88
|
+
# @yield [] no_args The block of code to skip memoization.
|
89
|
+
def self.without_memo
|
90
|
+
prev_value = Thread.current[THREAD_KEY_WITHOUT_MEMO]
|
91
|
+
Thread.current[THREAD_KEY_WITHOUT_MEMO] = true
|
92
|
+
yield
|
93
|
+
ensure
|
94
|
+
Thread.current[THREAD_KEY_WITHOUT_MEMO] = prev_value
|
95
|
+
end
|
96
|
+
|
97
|
+
# @todo Move errors to a separate file errors.rb
|
98
|
+
class ArgumentError < ::ArgumentError; end
|
99
|
+
class RuntimeError < ::RuntimeError; end
|
100
|
+
class WithoutMemoization < Exception; end
|
6
101
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# TODO: -> RedisMemo::Memoizable::AfterCommit
|
3
|
+
|
4
|
+
class RedisMemo::AfterCommit
|
5
|
+
# We assume there's only one ActiveRecord DB connection used for opening
|
6
|
+
# transactions
|
7
|
+
@@callback_added = false
|
8
|
+
@@pending_memo_versions = {}
|
9
|
+
@@previous_memo_versions = {}
|
10
|
+
|
11
|
+
def self.connection
|
12
|
+
ActiveRecord::Base.connection
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.pending_memo_versions
|
16
|
+
# In DB transactions, the pending memo version should be used immediately
|
17
|
+
# as part of method checksums. method_cache_keys made of
|
18
|
+
# pending_memo_versions are not referencable until we bump the
|
19
|
+
# pending_memo_versions after commiting the current transaction
|
20
|
+
@@pending_memo_versions
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.bump_memo_version_after_commit(key, version, previous_version:)
|
24
|
+
@@pending_memo_versions[key] = version
|
25
|
+
@@previous_memo_versions[key] = previous_version
|
26
|
+
|
27
|
+
reset_after_transaction
|
28
|
+
end
|
29
|
+
|
30
|
+
# https://github.com/Envek/after_commit_everywhere/blob/be8602f9fbb8e40b0fc8a04a47e4c2bc6b560ad5/lib/after_commit_everywhere.rb#L93
|
31
|
+
# Helper method to determine whether we're currently in transaction or not
|
32
|
+
def self.in_transaction?
|
33
|
+
# service transactions (tests and database_cleaner) are not joinable
|
34
|
+
connection.transaction_open? && connection.current_transaction.joinable?
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def self.after_commit(&blk)
|
40
|
+
connection.add_transaction_record(
|
41
|
+
RedisMemo::AfterCommit::Callback.new(connection, committed: blk),
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.after_rollback(&blk)
|
46
|
+
connection.add_transaction_record(
|
47
|
+
RedisMemo::AfterCommit::Callback.new(connection, rolledback: blk),
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.reset_after_transaction
|
52
|
+
return if @@callback_added
|
53
|
+
@@callback_added = true
|
54
|
+
|
55
|
+
after_commit do
|
56
|
+
reset(commited: true)
|
57
|
+
end
|
58
|
+
|
59
|
+
after_rollback do
|
60
|
+
reset(commited: false)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.reset(commited:)
|
65
|
+
if commited
|
66
|
+
@@pending_memo_versions.each do |key, version|
|
67
|
+
invalidation_queue =
|
68
|
+
RedisMemo::Memoizable::Invalidation.class_variable_get(:@@invalidation_queue)
|
69
|
+
|
70
|
+
invalidation_queue << RedisMemo::Memoizable::Invalidation::Task.new(
|
71
|
+
key,
|
72
|
+
version,
|
73
|
+
@@previous_memo_versions[key],
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
RedisMemo::Memoizable::Invalidation.drain_invalidation_queue
|
78
|
+
else
|
79
|
+
@@pending_memo_versions.each_key do |key|
|
80
|
+
RedisMemo::Cache.local_cache&.delete(key)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
@@callback_added = false
|
84
|
+
@@pending_memo_versions.clear
|
85
|
+
@@previous_memo_versions.clear
|
86
|
+
end
|
87
|
+
|
88
|
+
# https://github.com/Envek/after_commit_everywhere/blob/master/lib/after_commit_everywhere/wrap.rb
|
89
|
+
class Callback
|
90
|
+
def initialize(connection, committed: nil, rolledback: nil)
|
91
|
+
@connection = connection
|
92
|
+
@committed = committed
|
93
|
+
@rolledback = rolledback
|
94
|
+
end
|
95
|
+
|
96
|
+
def has_transactional_callbacks?
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
def trigger_transactional_callbacks?
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
104
|
+
def committed!(*)
|
105
|
+
@committed&.call
|
106
|
+
end
|
107
|
+
|
108
|
+
# Required for +transaction(requires_new: true)+
|
109
|
+
def add_to_transaction(*)
|
110
|
+
@connection.add_transaction_record(self)
|
111
|
+
end
|
112
|
+
|
113
|
+
def before_committed!(*); end
|
114
|
+
|
115
|
+
def rolledback!(*)
|
116
|
+
@rolledback&.call
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'cache'
|
3
|
+
require_relative 'tracer'
|
4
|
+
|
5
|
+
class RedisMemo::Batch
|
6
|
+
THREAD_KEY = :__redis_memo_current_batch__
|
7
|
+
|
8
|
+
def self.open
|
9
|
+
if current
|
10
|
+
raise RedisMemo::RuntimeError, 'Batch can not be nested'
|
11
|
+
end
|
12
|
+
|
13
|
+
Thread.current[THREAD_KEY] = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.close
|
17
|
+
if current
|
18
|
+
futures = current
|
19
|
+
Thread.current[THREAD_KEY] = nil
|
20
|
+
futures
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.current
|
25
|
+
Thread.current[THREAD_KEY]
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.execute
|
29
|
+
futures = close
|
30
|
+
return unless futures
|
31
|
+
|
32
|
+
cached_results = {}
|
33
|
+
method_cache_keys = nil
|
34
|
+
|
35
|
+
RedisMemo::Tracer.trace('redis_memo.cache.batch.read', nil) do
|
36
|
+
method_cache_keys = RedisMemo::MemoizeMethod.method_cache_keys(
|
37
|
+
futures.map(&:context),
|
38
|
+
)
|
39
|
+
|
40
|
+
if method_cache_keys
|
41
|
+
cached_results = RedisMemo::Cache.read_multi(*method_cache_keys)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
RedisMemo::Tracer.trace('redis_memo.cache.batch.execute', nil) do
|
46
|
+
results = Array.new(futures.size)
|
47
|
+
futures.each_with_index do |future, i|
|
48
|
+
future.method_cache_key = method_cache_keys ? method_cache_keys[i] : ''
|
49
|
+
results[i] = future.execute(cached_results)
|
50
|
+
end
|
51
|
+
results
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'options'
|
3
|
+
require_relative 'redis'
|
4
|
+
require_relative 'connection_pool'
|
5
|
+
|
6
|
+
class RedisMemo::Cache < ActiveSupport::Cache::RedisCacheStore
|
7
|
+
class Rescuable < Exception; end
|
8
|
+
|
9
|
+
THREAD_KEY_LOCAL_CACHE = :__redis_memo_cache_local_cache__
|
10
|
+
THREAD_KEY_LOCAL_DEPENDENCY_CACHE = :__redis_memo_local_cache_dependency_cache__
|
11
|
+
THREAD_KEY_RAISE_ERROR = :__redis_memo_cache_raise_error__
|
12
|
+
|
13
|
+
@@redis = nil
|
14
|
+
@@redis_store = nil
|
15
|
+
|
16
|
+
@@redis_store_error_handler = proc do |method:, exception:, returning:|
|
17
|
+
RedisMemo::DefaultOptions.redis_error_handler&.call(exception, method)
|
18
|
+
RedisMemo::DefaultOptions.logger&.warn(exception.full_message)
|
19
|
+
|
20
|
+
if Thread.current[THREAD_KEY_RAISE_ERROR]
|
21
|
+
raise RedisMemo::Cache::Rescuable
|
22
|
+
else
|
23
|
+
returning
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.redis
|
28
|
+
@@redis ||=
|
29
|
+
if RedisMemo::DefaultOptions.connection_pool
|
30
|
+
RedisMemo::ConnectionPool.new(**RedisMemo::DefaultOptions.connection_pool)
|
31
|
+
else
|
32
|
+
RedisMemo::DefaultOptions.redis
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.redis_store
|
37
|
+
@@redis_store ||= new(
|
38
|
+
compress: RedisMemo::DefaultOptions.compress,
|
39
|
+
compress_threshold: RedisMemo::DefaultOptions.compress_threshold,
|
40
|
+
error_handler: @@redis_store_error_handler,
|
41
|
+
expires_in: RedisMemo::DefaultOptions.expires_in,
|
42
|
+
redis: redis,
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
# We use our own local_cache instead of the one from RedisCacheStore, because
|
47
|
+
# the local_cache in RedisCacheStore stores a dumped
|
48
|
+
# ActiveSupport::Cache::Entry object -- which is slower comparing to a simple
|
49
|
+
# hash storing object references
|
50
|
+
def self.local_cache
|
51
|
+
Thread.current[THREAD_KEY_LOCAL_CACHE]
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.local_dependency_cache
|
55
|
+
Thread.current[THREAD_KEY_LOCAL_DEPENDENCY_CACHE]
|
56
|
+
end
|
57
|
+
|
58
|
+
class << self
|
59
|
+
def with_local_cache(&blk)
|
60
|
+
Thread.current[THREAD_KEY_LOCAL_CACHE] = {}
|
61
|
+
Thread.current[THREAD_KEY_LOCAL_DEPENDENCY_CACHE] = {}
|
62
|
+
blk.call
|
63
|
+
ensure
|
64
|
+
Thread.current[THREAD_KEY_LOCAL_CACHE] = nil
|
65
|
+
Thread.current[THREAD_KEY_LOCAL_DEPENDENCY_CACHE] = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# RedisCacheStore doesn't read from the local cache before reading from redis
|
69
|
+
def read_multi(*keys, raw: false, raise_error: false)
|
70
|
+
return {} if keys.empty?
|
71
|
+
|
72
|
+
Thread.current[THREAD_KEY_RAISE_ERROR] = raise_error
|
73
|
+
|
74
|
+
local_entries = local_cache&.slice(*keys) || {}
|
75
|
+
|
76
|
+
keys_to_fetch = keys
|
77
|
+
keys_to_fetch -= local_entries.keys unless local_entries.empty?
|
78
|
+
return local_entries if keys_to_fetch.empty?
|
79
|
+
|
80
|
+
remote_entries = redis_store.read_multi(*keys_to_fetch, raw: raw)
|
81
|
+
local_cache&.merge!(remote_entries)
|
82
|
+
|
83
|
+
if local_entries.empty?
|
84
|
+
remote_entries
|
85
|
+
else
|
86
|
+
local_entries.merge!(remote_entries)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def write(key, value, disable_async: false, raise_error: false, **options)
|
91
|
+
Thread.current[THREAD_KEY_RAISE_ERROR] = raise_error
|
92
|
+
|
93
|
+
if local_cache
|
94
|
+
local_cache[key] = value
|
95
|
+
end
|
96
|
+
|
97
|
+
async = RedisMemo::DefaultOptions.async
|
98
|
+
if async.nil? || disable_async
|
99
|
+
redis_store.write(key, value, **options)
|
100
|
+
else
|
101
|
+
async.call do
|
102
|
+
Thread.current[THREAD_KEY_RAISE_ERROR] = raise_error
|
103
|
+
redis_store.write(key, value, **options)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'connection_pool'
|
3
|
+
require_relative 'redis'
|
4
|
+
|
5
|
+
class RedisMemo::ConnectionPool
|
6
|
+
def initialize(**options)
|
7
|
+
@connection_pool = ::ConnectionPool.new(**options) do
|
8
|
+
# Construct a new client every time the block gets called
|
9
|
+
RedisMemo::Redis.new(RedisMemo::DefaultOptions.redis_config)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Avoid method_missing when possible for better performance
|
14
|
+
%i(get mget mapped_mget set eval).each do |method_name|
|
15
|
+
define_method method_name do |*args, &blk|
|
16
|
+
@connection_pool.with do |redis|
|
17
|
+
redis.send(method_name, *args, &blk)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(method_name, *args, &blk)
|
23
|
+
@connection_pool.with do |redis|
|
24
|
+
redis.send(method_name, *args, &blk)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'cache'
|
3
|
+
require_relative 'tracer'
|
4
|
+
|
5
|
+
class RedisMemo::Future
|
6
|
+
attr_writer :method_cache_key
|
7
|
+
|
8
|
+
def initialize(
|
9
|
+
ref,
|
10
|
+
method_id,
|
11
|
+
method_args,
|
12
|
+
dependent_memos,
|
13
|
+
cache_options,
|
14
|
+
method_name_without_memo
|
15
|
+
)
|
16
|
+
@ref = ref
|
17
|
+
@method_id = method_id
|
18
|
+
@method_args = method_args
|
19
|
+
@dependent_memos = dependent_memos
|
20
|
+
@cache_options = cache_options
|
21
|
+
@method_name_without_memo = method_name_without_memo
|
22
|
+
@method_cache_key = nil
|
23
|
+
@cache_hit = false
|
24
|
+
@cached_result = nil
|
25
|
+
@computed_cached_result = false
|
26
|
+
@fresh_result = nil
|
27
|
+
@computed_fresh_result = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def context
|
31
|
+
[@method_id, @method_args, @dependent_memos]
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_cache_key
|
35
|
+
@method_cache_key ||=
|
36
|
+
RedisMemo::MemoizeMethod.method_cache_keys([context])&.first || ''
|
37
|
+
end
|
38
|
+
|
39
|
+
def execute(cached_results=nil)
|
40
|
+
if RedisMemo::Batch.current
|
41
|
+
raise RedisMemo::RuntimeError, 'Cannot execute future when a batch is still open'
|
42
|
+
end
|
43
|
+
|
44
|
+
if cache_hit?(cached_results)
|
45
|
+
validate_cache_result =
|
46
|
+
RedisMemo::DefaultOptions.cache_validation_sampler&.call(@method_id)
|
47
|
+
|
48
|
+
if validate_cache_result && cached_result != fresh_result
|
49
|
+
RedisMemo::DefaultOptions.cache_out_of_date_handler&.call(
|
50
|
+
@ref,
|
51
|
+
@method_id,
|
52
|
+
@method_args,
|
53
|
+
cached_result,
|
54
|
+
fresh_result,
|
55
|
+
)
|
56
|
+
return fresh_result
|
57
|
+
end
|
58
|
+
|
59
|
+
return cached_result
|
60
|
+
end
|
61
|
+
|
62
|
+
fresh_result
|
63
|
+
end
|
64
|
+
|
65
|
+
def result
|
66
|
+
unless @computed_cached_result
|
67
|
+
raise RedisMemo::RuntimeError, 'Future has not been executed'
|
68
|
+
end
|
69
|
+
|
70
|
+
@fresh_result || @cached_result
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def cache_hit?(cached_results=nil)
|
76
|
+
cached_result(cached_results)
|
77
|
+
|
78
|
+
@cache_hit
|
79
|
+
end
|
80
|
+
|
81
|
+
def cached_result(cached_results=nil)
|
82
|
+
return @cached_result if @computed_cached_result
|
83
|
+
|
84
|
+
@cache_hit = false
|
85
|
+
RedisMemo::Tracer.trace('redis_memo.cache.read', @method_id) do
|
86
|
+
# Calculate the method_cache_key now, or use the method_cache_key obtained
|
87
|
+
# from batching previously
|
88
|
+
if !method_cache_key.empty?
|
89
|
+
cached_results ||= RedisMemo::Cache.read_multi(method_cache_key)
|
90
|
+
@cache_hit = cached_results.include?(method_cache_key)
|
91
|
+
@cached_result = cached_results[method_cache_key]
|
92
|
+
end
|
93
|
+
RedisMemo::Tracer.set_tag(cache_hit: @cache_hit)
|
94
|
+
end
|
95
|
+
|
96
|
+
@computed_cached_result = true
|
97
|
+
@cached_result
|
98
|
+
end
|
99
|
+
|
100
|
+
def fresh_result
|
101
|
+
return @fresh_result if @computed_fresh_result
|
102
|
+
|
103
|
+
RedisMemo::Tracer.trace('redis_memo.cache.write', @method_id) do
|
104
|
+
# cache miss
|
105
|
+
@fresh_result = @ref.send(@method_name_without_memo, *@method_args)
|
106
|
+
if @cache_options.include?(:expires_in) && @cache_options[:expires_in].respond_to?(:call)
|
107
|
+
@cache_options[:expires_in] = @cache_options[:expires_in].call(@fresh_result)
|
108
|
+
end
|
109
|
+
|
110
|
+
if !method_cache_key.empty? && (
|
111
|
+
# Write back fresh result if cache miss
|
112
|
+
!@cache_hit || (
|
113
|
+
# or cached result is out of date (sampled to validate the cache
|
114
|
+
# result)
|
115
|
+
@cache_hit && @cached_result != @fresh_result
|
116
|
+
)
|
117
|
+
)
|
118
|
+
RedisMemo::Cache.write(method_cache_key, @fresh_result, **@cache_options)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
@computed_fresh_result = true
|
123
|
+
@fresh_result
|
124
|
+
end
|
125
|
+
end
|