identity_cache 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/.travis.yml +4 -2
- data/CHANGELOG +5 -0
- data/README.md +9 -1
- data/Rakefile +5 -1
- data/identity_cache.gemspec +14 -6
- data/lib/identity_cache.rb +17 -34
- data/lib/identity_cache/cache_hash.rb +36 -0
- data/lib/identity_cache/cache_key_generation.rb +34 -6
- data/lib/identity_cache/configuration_dsl.rb +4 -6
- data/lib/identity_cache/memoized_cache_proxy.rb +19 -17
- data/lib/identity_cache/query_api.rb +105 -11
- data/lib/identity_cache/version.rb +2 -1
- data/performance/cache_runner.rb +44 -15
- data/performance/cpu.rb +3 -0
- data/performance/externals.rb +4 -0
- data/performance/profile.rb +4 -0
- data/test/attribute_cache_test.rb +5 -3
- data/test/cache_hash_test.rb +16 -0
- data/test/fetch_multi_test.rb +32 -13
- data/test/fetch_multi_with_batched_associations_test.rb +6 -4
- data/test/fetch_test.rb +28 -7
- data/test/fixtures/serialized_record +0 -0
- data/test/helpers/active_record_objects.rb +25 -1
- data/test/helpers/database_connection.rb +7 -6
- data/test/helpers/serialization_format.rb +38 -0
- data/test/helpers/update_serialization_format.rb +28 -0
- data/test/index_cache_test.rb +5 -3
- data/test/normalized_has_many_test.rb +10 -0
- data/test/save_test.rb +16 -14
- data/test/schema_change_test.rb +16 -0
- data/test/serialization_format_change_test.rb +16 -0
- data/test/test_helper.rb +1 -1
- metadata +51 -40
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YmQyZjllMTVlOTNiMmUxMDkxMzJlNzYzYjY0ODZlMmQ4Y2E0YTNkNA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OGQ2MDc5Zjk2NmY3MTcwMmIxNDkzYjczNTZjMmJhNjMwMWI1ZjJjYg==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MmM4NmJmMTY2MDRkNDdkYzRjMDg3NTQ4OTRlMzgyOWJiYzY4Y2RjMDQxZDQ0
|
10
|
+
NDA2NzYwZGIyN2UzMjVkNGU1MTI1ZTViNDU4YThkNTAxNGYyY2UxMWU5Y2U4
|
11
|
+
MzhjN2JjMDkyMWVmYjhkMWRmMmIyMmVmZjI4NzJkYTExZGZiYmM=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
Yjg4MTdkOGI0OTJmY2ZkMWEwMDY0NjViMDE2MjdiMDg4NTkzZTc0MmU1YjEy
|
14
|
+
NzI1Nzc0ZjQ0ODk1YWY0M2ExMDdjNWNjMjQ5OGY3Y2YyMTcyNzQ3MjA3OTY3
|
15
|
+
YWMxMTlkZTgxNjQ0OTgyYmY3YzY5OWYzY2QyY2NlZjZkNzUwYTQ=
|
data/.travis.yml
CHANGED
data/CHANGELOG
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
0.0.4
|
2
|
+
* Fix: only marshal attributes, embedded associations and normalized association IDs
|
3
|
+
* Add cache version number to cache keys
|
4
|
+
* Add test case to ensure version number is updated when the marshalled format changes
|
5
|
+
|
1
6
|
0.0.3
|
2
7
|
* Fix: memoization for multi hits actually work
|
3
8
|
* Fix: quotes SELECT projection elements on cache misses
|
data/README.md
CHANGED
@@ -11,6 +11,7 @@ Add this line to your application's Gemfile:
|
|
11
11
|
|
12
12
|
```ruby
|
13
13
|
gem 'identity_cache'
|
14
|
+
gem 'cityhash' # optional, for faster hashing (C-Ruby only)
|
14
15
|
```
|
15
16
|
|
16
17
|
And then execute:
|
@@ -205,12 +206,18 @@ class ApplicationController < ActionController::Base
|
|
205
206
|
end
|
206
207
|
```
|
207
208
|
|
209
|
+
## Versioning
|
210
|
+
|
211
|
+
Cache keys include a version number by default, specified in `IdentityCache::CACHE_VERSION`. This version number is updated whenever the storage format for cache values is modified. If you modify the cache value format, you must run `rake update_serialization_format` in order to pass the unit tests, and include the modified `test/fixtures/serialized_record` file in your pull request.
|
212
|
+
|
208
213
|
## Caveats
|
209
214
|
|
210
215
|
A word of warning. Some versions of rails will silently rescue all exceptions in `after_commit` hooks. If an `after_commit` fails before the cache expiry `after_commit` the cache will not be expired and you will be left with stale data.
|
211
216
|
|
212
217
|
Since everything is being marshalled and unmarshalled from Memcached changing Ruby or Rails versions could mean your objects cannot be unmarshalled from Memcached. There are a number of ways to get around this such as namespacing keys when you upgrade or rescuing marshal load errors and treating it as a cache miss. Just something to be aware of if you are using IdentityCache and upgrade Ruby or Rails.
|
213
218
|
|
219
|
+
IdentityCache is also very much _opt-in_ by deliberate design. This means IdentityCache does not mess with the way normal Rails associations work, and including it in a model won't change any clients of that model until you switch them to use `fetch` instead of `find`. This is because there is no way IdentityCache is ever going to be 100% consistent. Processes die, execeptions happen, and network blips occur, which means there is a chance that some database transaction might commit but the corresponding memcached DEL operation does not make it. This means that you need to think carefully about when you use `fetch` and when you use `find`. For example, at Shopify, we never use any `fetch`ers on the path which moves money around, because IdentityCache could simply be wrong, and we want to charge people the right amount of money. We do however use the fetchers on performance critical paths where absolute correctness isn't the most important thing, and this is what IdentityCache is intended for.
|
220
|
+
|
214
221
|
## Contributing
|
215
222
|
|
216
223
|
Caching is hard. Chances are that if some feature was left out, it was left out on purpose because it didn't make sense to cache in that way. This is used in production at Shopify so we are very opinionated about the types of features we're going to add. Please start the discussion early, before even adding code, so that we can talk about the feature you are proposing and decide if it makes sense in IdentityCache.
|
@@ -236,4 +243,5 @@ Tom Burns (@boourns)
|
|
236
243
|
Harry Brundage (@hornairs)
|
237
244
|
Dylan Smith (@dylanahsmith)
|
238
245
|
Tobias Lütke (@tobi)
|
239
|
-
John Duff (@jduff)
|
246
|
+
John Duff (@jduff)
|
247
|
+
Francis Bogsany (@fbogsany)
|
data/Rakefile
CHANGED
@@ -15,6 +15,11 @@ Rake::TestTask.new(:test) do |t|
|
|
15
15
|
t.verbose = true
|
16
16
|
end
|
17
17
|
|
18
|
+
desc 'Update serialization format test fixture.'
|
19
|
+
task :update_serialization_format do
|
20
|
+
ruby './test/helpers/update_serialization_format.rb'
|
21
|
+
end
|
22
|
+
|
18
23
|
namespace :benchmark do
|
19
24
|
desc "Run the identity cache CPU benchmark"
|
20
25
|
task :cpu do
|
@@ -32,4 +37,3 @@ namespace :profile do
|
|
32
37
|
ruby "./performance/profile.rb"
|
33
38
|
end
|
34
39
|
end
|
35
|
-
|
data/identity_cache.gemspec
CHANGED
@@ -17,13 +17,21 @@ Gem::Specification.new do |gem|
|
|
17
17
|
|
18
18
|
|
19
19
|
gem.add_dependency('ar_transaction_changes', '0.0.1')
|
20
|
-
gem.add_dependency('activerecord', '3.2.
|
21
|
-
gem.add_dependency('activesupport', '3.2.
|
22
|
-
|
20
|
+
gem.add_dependency('activerecord', '~> 3.2.12')
|
21
|
+
gem.add_dependency('activesupport', '~> 3.2.12')
|
22
|
+
|
23
23
|
gem.add_development_dependency('memcache-client')
|
24
24
|
gem.add_development_dependency('rake')
|
25
25
|
gem.add_development_dependency('mocha', '0.14.0')
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
|
27
|
+
if RUBY_PLATFORM == 'java'
|
28
|
+
gem.add_development_dependency 'jruby-openssl'
|
29
|
+
gem.add_development_dependency 'activerecord-jdbcmysql-adapter'
|
30
|
+
gem.add_development_dependency 'jdbc-mysql'
|
31
|
+
else
|
32
|
+
gem.add_development_dependency('debugger')
|
33
|
+
gem.add_development_dependency('ruby-prof')
|
34
|
+
gem.add_development_dependency('cityhash', '0.6.0')
|
35
|
+
gem.add_development_dependency('mysql2')
|
36
|
+
end
|
29
37
|
end
|
data/lib/identity_cache.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
require '
|
1
|
+
require 'active_record'
|
2
2
|
require 'ar_transaction_changes'
|
3
|
+
|
3
4
|
require "identity_cache/version"
|
4
5
|
require 'identity_cache/memoized_cache_proxy'
|
5
6
|
require 'identity_cache/belongs_to_caching'
|
@@ -7,6 +8,7 @@ require 'identity_cache/cache_key_generation'
|
|
7
8
|
require 'identity_cache/configuration_dsl'
|
8
9
|
require 'identity_cache/parent_model_expiration'
|
9
10
|
require 'identity_cache/query_api'
|
11
|
+
require "identity_cache/cache_hash"
|
10
12
|
|
11
13
|
module IdentityCache
|
12
14
|
CACHED_NIL = :idc_cached_nil
|
@@ -19,9 +21,20 @@ module IdentityCache
|
|
19
21
|
end
|
20
22
|
|
21
23
|
class << self
|
24
|
+
include IdentityCache::CacheHash
|
25
|
+
|
26
|
+
attr_accessor :readonly
|
27
|
+
attr_writer :logger
|
22
28
|
|
23
|
-
|
24
|
-
|
29
|
+
def included(base) #:nodoc:
|
30
|
+
raise AlreadyIncludedError if base.respond_to? :cache_indexes
|
31
|
+
|
32
|
+
base.send(:include, ArTransactionChanges) unless base.include?(ArTransactionChanges)
|
33
|
+
base.send(:include, IdentityCache::BelongsToCaching)
|
34
|
+
base.send(:include, IdentityCache::CacheKeyGeneration)
|
35
|
+
base.send(:include, IdentityCache::ConfigurationDSL)
|
36
|
+
base.send(:include, IdentityCache::QueryAPI)
|
37
|
+
end
|
25
38
|
|
26
39
|
# Sets the cache adaptor IdentityCache will be using
|
27
40
|
#
|
@@ -30,7 +43,7 @@ module IdentityCache
|
|
30
43
|
# +cache_adaptor+ - A ActiveSupport::Cache::Store
|
31
44
|
#
|
32
45
|
def cache_backend=(cache_adaptor)
|
33
|
-
cache.
|
46
|
+
cache.cache_backend = cache_adaptor
|
34
47
|
end
|
35
48
|
|
36
49
|
def cache
|
@@ -73,7 +86,6 @@ module IdentityCache
|
|
73
86
|
value.nil? ? IdentityCache::CACHED_NIL : value
|
74
87
|
end
|
75
88
|
|
76
|
-
|
77
89
|
def unmap_cached_nil_for(value)
|
78
90
|
value == IdentityCache::CACHED_NIL ? nil : value
|
79
91
|
end
|
@@ -113,34 +125,5 @@ module IdentityCache
|
|
113
125
|
|
114
126
|
result
|
115
127
|
end
|
116
|
-
|
117
|
-
def schema_to_string(columns)
|
118
|
-
columns.sort_by(&:name).map{|c| "#{c.name}:#{c.type}"}.join(',')
|
119
|
-
end
|
120
|
-
|
121
|
-
def denormalized_schema_hash(klass)
|
122
|
-
schema_string = schema_to_string(klass.columns)
|
123
|
-
if klass.respond_to?(:all_cached_associations_needing_population) && !(embeded_associations = klass.all_cached_associations_needing_population).empty?
|
124
|
-
embedded_schema = embeded_associations.map do |name, options|
|
125
|
-
"#{name}:(#{denormalized_schema_hash(options[:association_class])})"
|
126
|
-
end.sort.join(',')
|
127
|
-
schema_string << "," << embedded_schema
|
128
|
-
end
|
129
|
-
IdentityCache.memcache_hash(schema_string)
|
130
|
-
end
|
131
|
-
|
132
|
-
def included(base) #:nodoc:
|
133
|
-
raise AlreadyIncludedError if base.respond_to? :cache_indexes
|
134
|
-
|
135
|
-
base.send(:include, ArTransactionChanges) unless base.include?(ArTransactionChanges)
|
136
|
-
base.send(:include, IdentityCache::BelongsToCaching)
|
137
|
-
base.send(:include, IdentityCache::CacheKeyGeneration)
|
138
|
-
base.send(:include, IdentityCache::ConfigurationDSL)
|
139
|
-
base.send(:include, IdentityCache::QueryAPI)
|
140
|
-
end
|
141
|
-
|
142
|
-
def memcache_hash(key) #:nodoc:
|
143
|
-
CityHash.hash64(key)
|
144
|
-
end
|
145
128
|
end
|
146
129
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Use CityHash for fast hashing if it is available; use Digest::MD5 otherwise
|
2
|
+
begin
|
3
|
+
require 'cityhash'
|
4
|
+
rescue LoadError
|
5
|
+
unless RUBY_PLATFORM == 'java'
|
6
|
+
warn <<-NOTICE
|
7
|
+
** Notice: CityHash was not loaded. **
|
8
|
+
|
9
|
+
For optimal performance, use of the cityhash gem is recommended.
|
10
|
+
|
11
|
+
Run the following command, or add it to your Gemfile:
|
12
|
+
|
13
|
+
gem install cityhash
|
14
|
+
NOTICE
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'digest/md5'
|
18
|
+
end
|
19
|
+
|
20
|
+
module IdentityCache
|
21
|
+
module CacheHash
|
22
|
+
|
23
|
+
if defined?(CityHash)
|
24
|
+
|
25
|
+
def memcache_hash(key) #:nodoc:
|
26
|
+
CityHash.hash64(key)
|
27
|
+
end
|
28
|
+
else
|
29
|
+
|
30
|
+
def memcache_hash(key) #:nodoc:
|
31
|
+
a = Digest::MD5.digest(key).unpack('LL')
|
32
|
+
(a[0] << 32) | a[1]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,26 +1,54 @@
|
|
1
1
|
module IdentityCache
|
2
2
|
module CacheKeyGeneration
|
3
3
|
extend ActiveSupport::Concern
|
4
|
+
DEFAULT_NAMESPACE = "IDC:#{CACHE_VERSION}:".freeze
|
5
|
+
|
6
|
+
def self.schema_to_string(columns)
|
7
|
+
columns.sort_by(&:name).map{|c| "#{c.name}:#{c.type}"}.join(',')
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.denormalized_schema_hash(klass)
|
11
|
+
schema_string = schema_to_string(klass.columns)
|
12
|
+
if !(associations = embedded_associations(klass)).empty?
|
13
|
+
embedded_schema = associations.map do |name, options|
|
14
|
+
"#{name}:(#{denormalized_schema_hash(options[:association_class])})"
|
15
|
+
end.sort.join(',')
|
16
|
+
schema_string << "," << embedded_schema
|
17
|
+
end
|
18
|
+
IdentityCache.memcache_hash(schema_string)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.embedded_associations(klass)
|
22
|
+
if klass.respond_to?(:all_cached_associations_needing_population)
|
23
|
+
klass.all_cached_associations_needing_population
|
24
|
+
else
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
end
|
4
28
|
|
5
29
|
module ClassMethods
|
6
30
|
def rails_cache_key(id)
|
7
|
-
rails_cache_key_prefix
|
31
|
+
"#{rails_cache_key_prefix}#{id}"
|
8
32
|
end
|
9
33
|
|
10
34
|
def rails_cache_key_prefix
|
11
|
-
@rails_cache_key_prefix ||=
|
12
|
-
|
13
|
-
end
|
35
|
+
@rails_cache_key_prefix ||= IdentityCache::CacheKeyGeneration.denormalized_schema_hash(self)
|
36
|
+
"#{rails_cache_key_namespace}blob:#{base_class.name}:#{@rails_cache_key_prefix}:"
|
14
37
|
end
|
15
38
|
|
16
39
|
def rails_cache_index_key_for_fields_and_values(fields, values)
|
17
|
-
"
|
40
|
+
"#{rails_cache_key_namespace}index:#{base_class.name}:#{rails_cache_string_for_fields_and_values(fields, values)}"
|
18
41
|
end
|
19
42
|
|
20
43
|
def rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, values)
|
21
|
-
"
|
44
|
+
"#{rails_cache_key_namespace}attribute:#{base_class.name}:#{attribute}:#{rails_cache_string_for_fields_and_values(fields, values)}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def rails_cache_key_namespace
|
48
|
+
DEFAULT_NAMESPACE
|
22
49
|
end
|
23
50
|
|
51
|
+
private
|
24
52
|
def rails_cache_string_for_fields_and_values(fields, values)
|
25
53
|
"#{fields.join('/')}:#{IdentityCache.memcache_hash(values.join('/'))}"
|
26
54
|
end
|
@@ -183,7 +183,7 @@ module IdentityCache
|
|
183
183
|
end
|
184
184
|
|
185
185
|
def identity_cache_single_value_dynamic_fetcher(fields, values) # :nodoc:
|
186
|
-
sql_on_miss = "SELECT
|
186
|
+
sql_on_miss = "SELECT #{quoted_primary_key} FROM #{quoted_table_name} WHERE #{identity_cache_sql_conditions(fields, values)} LIMIT 1"
|
187
187
|
cache_key = rails_cache_index_key_for_fields_and_values(fields, values)
|
188
188
|
id = IdentityCache.fetch(cache_key) { connection.select_value(sql_on_miss) }
|
189
189
|
unless id.nil?
|
@@ -195,7 +195,7 @@ module IdentityCache
|
|
195
195
|
end
|
196
196
|
|
197
197
|
def identity_cache_multiple_value_dynamic_fetcher(fields, values) # :nodoc
|
198
|
-
sql_on_miss = "SELECT
|
198
|
+
sql_on_miss = "SELECT #{quoted_primary_key} FROM #{quoted_table_name} WHERE #{identity_cache_sql_conditions(fields, values)}"
|
199
199
|
cache_key = rails_cache_index_key_for_fields_and_values(fields, values)
|
200
200
|
ids = IdentityCache.fetch(cache_key) { connection.select_values(sql_on_miss) }
|
201
201
|
|
@@ -267,8 +267,7 @@ module IdentityCache
|
|
267
267
|
|
268
268
|
def attribute_dynamic_fetcher(attribute, fields, values) #:nodoc:
|
269
269
|
cache_key = rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, values)
|
270
|
-
sql_on_miss = "SELECT
|
271
|
-
|
270
|
+
sql_on_miss = "SELECT #{connection.quote_column_name(attribute)} FROM #{quoted_table_name} WHERE #{identity_cache_sql_conditions(fields, values)} LIMIT 1"
|
272
271
|
IdentityCache.fetch(cache_key) { connection.select_value(sql_on_miss) }
|
273
272
|
end
|
274
273
|
|
@@ -278,7 +277,6 @@ module IdentityCache
|
|
278
277
|
raise InverseAssociationError unless child_association
|
279
278
|
foreign_key = child_association.association_foreign_key
|
280
279
|
parent_class ||= self.name
|
281
|
-
new_parent = options[:inverse_name]
|
282
280
|
|
283
281
|
child_class.send(:include, ArTransactionChanges) unless child_class.include?(ArTransactionChanges)
|
284
282
|
child_class.send(:include, ParentModelExpiration) unless child_class.include?(ParentModelExpiration)
|
@@ -294,7 +292,7 @@ module IdentityCache
|
|
294
292
|
end
|
295
293
|
|
296
294
|
def identity_cache_sql_conditions(fields, values)
|
297
|
-
fields.each_with_index.collect { |f, i| "
|
295
|
+
fields.each_with_index.collect { |f, i| "#{connection.quote_column_name(f)} = #{quote_value(values[i])}" }.join(" AND ")
|
298
296
|
end
|
299
297
|
end
|
300
298
|
end
|
@@ -2,10 +2,10 @@ require 'monitor'
|
|
2
2
|
|
3
3
|
module IdentityCache
|
4
4
|
class MemoizedCacheProxy
|
5
|
-
|
5
|
+
attr_accessor :cache_backend
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@
|
7
|
+
def initialize(cache_backend = nil)
|
8
|
+
@cache_backend = cache_backend || Rails.cache
|
9
9
|
@key_value_maps = Hash.new {|h, k| h[k] = {} }
|
10
10
|
end
|
11
11
|
|
@@ -23,27 +23,27 @@ module IdentityCache
|
|
23
23
|
|
24
24
|
def write(key, value)
|
25
25
|
memoized_key_values[key] = value if memoizing?
|
26
|
-
@
|
26
|
+
@cache_backend.write(key, value)
|
27
27
|
end
|
28
28
|
|
29
29
|
def read(key)
|
30
|
-
|
30
|
+
used_cache_backend = true
|
31
31
|
|
32
32
|
result = if memoizing?
|
33
|
-
|
33
|
+
used_cache_backend = false
|
34
34
|
mkv = memoized_key_values
|
35
35
|
|
36
36
|
mkv.fetch(key) do
|
37
|
-
|
38
|
-
mkv[key] = @
|
37
|
+
used_cache_backend = true
|
38
|
+
mkv[key] = @cache_backend.read(key)
|
39
39
|
end
|
40
40
|
|
41
41
|
else
|
42
|
-
@
|
42
|
+
@cache_backend.read(key)
|
43
43
|
end
|
44
44
|
|
45
45
|
if result
|
46
|
-
IdentityCache.logger.debug { "[IdentityCache] #{
|
46
|
+
IdentityCache.logger.debug { "[IdentityCache] #{ used_cache_backend ? '(cache_backend)' : '(memoized)' } cache hit for #{key}" }
|
47
47
|
else
|
48
48
|
IdentityCache.logger.debug { "[IdentityCache] cache miss for #{key}" }
|
49
49
|
end
|
@@ -53,13 +53,15 @@ module IdentityCache
|
|
53
53
|
|
54
54
|
def delete(key)
|
55
55
|
memoized_key_values.delete(key) if memoizing?
|
56
|
-
@
|
56
|
+
result = @cache_backend.delete(key)
|
57
|
+
IdentityCache.logger.debug { "[IdentityCache] delete #{ result ? 'hit' : 'miss' } for #{key}" }
|
58
|
+
result
|
57
59
|
end
|
58
60
|
|
59
61
|
def read_multi(*keys)
|
60
62
|
|
61
63
|
if IdentityCache.logger.debug?
|
62
|
-
memoized_keys ,
|
64
|
+
memoized_keys , cache_backend_keys = [], []
|
63
65
|
end
|
64
66
|
|
65
67
|
result = if memoizing?
|
@@ -75,7 +77,7 @@ module IdentityCache
|
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
78
|
-
hits = missing_keys.empty? ? {} : @
|
80
|
+
hits = missing_keys.empty? ? {} : @cache_backend.read_multi(*missing_keys)
|
79
81
|
|
80
82
|
missing_keys.each do |key|
|
81
83
|
hit = hits[key]
|
@@ -84,17 +86,17 @@ module IdentityCache
|
|
84
86
|
end
|
85
87
|
hash
|
86
88
|
else
|
87
|
-
@
|
89
|
+
@cache_backend.read_multi(*keys)
|
88
90
|
end
|
89
91
|
|
90
92
|
if IdentityCache.logger.debug?
|
91
93
|
|
92
94
|
result.each do |k, v|
|
93
|
-
|
95
|
+
cache_backend_keys << k if !v.nil? && !memoized_keys.include?(k)
|
94
96
|
end
|
95
97
|
|
96
98
|
memoized_keys.each{ |k| IdentityCache.logger.debug "[IdentityCache] (memoized) cache hit for #{k} (multi)" }
|
97
|
-
|
99
|
+
cache_backend_keys.each{ |k| IdentityCache.logger.debug "[IdentityCache] (cache_backend) cache hit for #{k} (multi)" }
|
98
100
|
end
|
99
101
|
|
100
102
|
result
|
@@ -102,7 +104,7 @@ module IdentityCache
|
|
102
104
|
|
103
105
|
def clear
|
104
106
|
clear_memoization
|
105
|
-
@
|
107
|
+
@cache_backend.clear
|
106
108
|
end
|
107
109
|
|
108
110
|
private
|