identity_cache 1.2.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +3 -3
- data/CHANGELOG.md +16 -0
- data/README.md +20 -5
- data/dev.yml +1 -1
- data/lib/identity_cache/cache_key_loader.rb +1 -1
- data/lib/identity_cache/cached/attribute.rb +6 -2
- data/lib/identity_cache/parent_model_expiration.rb +3 -2
- data/lib/identity_cache/query_api.rb +12 -6
- data/lib/identity_cache/version.rb +1 -1
- data/lib/identity_cache/with_primary_index.rb +12 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3abed29487ae94532060d1a35e2bc86f8b4187cbc9a3074c8c304ab4f8426b4b
|
4
|
+
data.tar.gz: ddcaf494c9bc48055ee4e1ddc83a5caf61e047e4d053eb2b4e87a086a73f97ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e0c8e9d0996d6f7f3f3e760fbf1ec2f7b8fa819bbeeaa3dc8b1286125265da45c6b174a77d3b13f763e6db104d613a55562f0a0ff13eb6d0c800e455ae30020c
|
7
|
+
data.tar.gz: 03a5cffffa5a0971f0d12a4d449c518a471d579410c2f7b8dfe32ad82234fb08115fa44cc99e726ed28352ead0287c5e966f47fb0b607758ddb29b1baa4667a4
|
data/.github/workflows/ci.yml
CHANGED
@@ -16,14 +16,14 @@ jobs:
|
|
16
16
|
matrix:
|
17
17
|
entry:
|
18
18
|
- name: 'Minimum supported'
|
19
|
-
ruby: '2.
|
19
|
+
ruby: '2.7'
|
20
20
|
gemfile: "Gemfile.min-supported"
|
21
21
|
- name: 'Latest released & run rubocop'
|
22
|
-
ruby: '3.
|
22
|
+
ruby: '3.2'
|
23
23
|
gemfile: "Gemfile.latest-release"
|
24
24
|
rubocop: true
|
25
25
|
- name: 'Rails edge'
|
26
|
-
ruby: '3.
|
26
|
+
ruby: '3.2'
|
27
27
|
gemfile: "Gemfile.rails-edge"
|
28
28
|
edge: true
|
29
29
|
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
# Identity Cache Changelog
|
2
2
|
|
3
|
+
## Unreleased
|
4
|
+
|
5
|
+
## 1.3.1
|
6
|
+
|
7
|
+
### Fixes
|
8
|
+
- Remove N+1 queries from embedded associations when using `fetch` while `should_use_cache` is false. (#531)
|
9
|
+
|
10
|
+
## 1.3.0
|
11
|
+
|
12
|
+
### Features
|
13
|
+
- Return meaningful value from `expire_cache` indicating whenever it succeeded or failed in the process. (#523)
|
14
|
+
|
15
|
+
### Fixes
|
16
|
+
- Expire parents cache when when calling `expire_cache`. (#523)
|
17
|
+
- Avoid creating too many shapes on Ruby 3.2+. (#526)
|
18
|
+
|
3
19
|
## 1.2.0
|
4
20
|
|
5
21
|
### Fixes
|
data/README.md
CHANGED
@@ -254,11 +254,26 @@ Cache keys include a version number by default, specified in `IdentityCache::CAC
|
|
254
254
|
|
255
255
|
## Caveats
|
256
256
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
257
|
+
IdentityCache is never going to be 100% consistent, since cache invalidations can be lost. As such, it was intentionally designed to be _opt-in_, so it is only used where cache inconsistency is tolerated. 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 means that you need to think carefully about when you use `fetch` and when you use `find`.
|
258
|
+
|
259
|
+
Expected sources of lost cache invalidations include:
|
260
|
+
* Database write performed that doesn't trigger an after_commit callback
|
261
|
+
* Process/system getting killed or crashing between the database commit and cache invalidation
|
262
|
+
* Network unavailability, including transient failures, preventing the delivery of the cache invalidation
|
263
|
+
* Memcached unavailability or failure preventing the processing of the cache invalidation request
|
264
|
+
* Memcached flush / restart could remove a cache invalidation that would normally interrupt a cache fill that started when the cache key was absent. E.g.
|
265
|
+
1. cache key absent (not just invalidated)
|
266
|
+
2. process 1 reads cache key
|
267
|
+
3. process 1 starts reading from the database
|
268
|
+
4. process 2 writes to the database
|
269
|
+
5. process 2 writes a cache invalidation marker to cache key
|
270
|
+
6. memcached flush
|
271
|
+
7. process 1 uses an `ADD` operation, which succeeds in filling the cache with the now stale data
|
272
|
+
* Rollout of cache namespace changes (e.g. from upgrading IdentityCache, adding columns, cached associations or from application changes to IdentityCache.cache_namespace) can result in cache fills to the new namespace that aren't invalidated by cache invalidations from a process still using the old namespace
|
273
|
+
|
274
|
+
Cache expiration is meant to be used to help the system recover, but it only works if the application avoids using the cache data as a transaction to write data. IdentityCache avoids loading cached data from its methods during an open transaction, but can't prevent cache data that was loaded before the transaction was opened from being used in a transaction. IdentityCache won't help with scaling write traffic, it was intended for scaling database queries from read-only requests.
|
275
|
+
|
276
|
+
IdentityCache also caches the absence of database values (e.g. to avoid performance problems when it is destroyed), so lost cache invalidations can also result in that value continuing to remain absent. As such, avoid sending the id of an uncommitted database record to another process (e.g. queuing it to a background job), since that could result in an attempt to read the record by its id before it has been created. A cache invalidation will still be attempted when the record is created, but that could be lost.
|
262
277
|
|
263
278
|
## Notes
|
264
279
|
|
data/dev.yml
CHANGED
@@ -44,7 +44,7 @@ module IdentityCache
|
|
44
44
|
# @param db_key [Array] Reference to what to load from the database.
|
45
45
|
# @return [Hash] A hash mapping each database key to its corresponding value
|
46
46
|
def load_multi(cache_fetcher, db_keys)
|
47
|
-
load_batch(cache_fetcher => db_keys).fetch(cache_fetcher)
|
47
|
+
load_batch({ cache_fetcher => db_keys }).fetch(cache_fetcher)
|
48
48
|
end
|
49
49
|
|
50
50
|
# Load multiple keys for multiple cache fetchers
|
@@ -35,16 +35,20 @@ module IdentityCache
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def expire(record)
|
38
|
+
all_deleted = true
|
39
|
+
|
38
40
|
unless record.send(:was_new_record?)
|
39
41
|
old_key = old_cache_key(record)
|
40
|
-
IdentityCache.cache.delete(old_key)
|
42
|
+
all_deleted = IdentityCache.cache.delete(old_key)
|
41
43
|
end
|
42
44
|
unless record.destroyed?
|
43
45
|
new_key = new_cache_key(record)
|
44
46
|
if new_key != old_key
|
45
|
-
IdentityCache.cache.delete(new_key)
|
47
|
+
all_deleted = IdentityCache.cache.delete(new_key) && all_deleted
|
46
48
|
end
|
47
49
|
end
|
50
|
+
|
51
|
+
all_deleted
|
48
52
|
end
|
49
53
|
|
50
54
|
def cache_key(index_key)
|
@@ -45,8 +45,9 @@ module IdentityCache
|
|
45
45
|
def expire_parent_caches
|
46
46
|
parents_to_expire = Set.new
|
47
47
|
add_parents_to_cache_expiry_set(parents_to_expire)
|
48
|
-
parents_to_expire.
|
49
|
-
|
48
|
+
parents_to_expire.select! { |parent| parent.class.primary_cache_index_enabled }
|
49
|
+
parents_to_expire.reduce(true) do |all_expired, parent|
|
50
|
+
parent.expire_primary_index && all_expired
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
@@ -70,6 +70,8 @@ module IdentityCache
|
|
70
70
|
readonly: IdentityCache.fetch_read_only_records && should_use_cache?)
|
71
71
|
return if records.empty?
|
72
72
|
|
73
|
+
return unless should_use_cache?
|
74
|
+
|
73
75
|
records.each(&:readonly!) if readonly
|
74
76
|
each_id_embedded_association do |cached_association|
|
75
77
|
preload_id_embedded_association(records, cached_association)
|
@@ -166,15 +168,17 @@ module IdentityCache
|
|
166
168
|
def _run_commit_callbacks
|
167
169
|
if destroyed? || transaction_changed_attributes.present?
|
168
170
|
expire_cache
|
169
|
-
expire_parent_caches
|
170
171
|
end
|
171
172
|
super
|
172
173
|
end
|
173
174
|
|
174
|
-
# Invalidate the cache data associated with the record.
|
175
|
+
# Invalidate the cache data associated with the record. Returns `true` on success,
|
176
|
+
# `false` otherwise.
|
175
177
|
def expire_cache
|
176
|
-
|
177
|
-
|
178
|
+
expired_parent_caches = expire_parent_caches
|
179
|
+
expired_attribute_indexes = expire_attribute_indexes
|
180
|
+
|
181
|
+
expired_parent_caches && expired_attribute_indexes
|
178
182
|
end
|
179
183
|
|
180
184
|
# @api private
|
@@ -185,9 +189,11 @@ module IdentityCache
|
|
185
189
|
|
186
190
|
private
|
187
191
|
|
192
|
+
# Even if we have problems with some attributes, carry over the results and expire
|
193
|
+
# all possible attributes without array allocation.
|
188
194
|
def expire_attribute_indexes # :nodoc:
|
189
|
-
cache_indexes.
|
190
|
-
cached_attribute.expire(self)
|
195
|
+
cache_indexes.reduce(true) do |all_expired, cached_attribute|
|
196
|
+
cached_attribute.expire(self) && all_expired
|
191
197
|
end
|
192
198
|
end
|
193
199
|
end
|
@@ -7,8 +7,9 @@ module IdentityCache
|
|
7
7
|
include WithoutPrimaryIndex
|
8
8
|
|
9
9
|
def expire_cache
|
10
|
-
expire_primary_index
|
11
|
-
|
10
|
+
expired_primary_index = expire_primary_index
|
11
|
+
|
12
|
+
super && expired_primary_index
|
12
13
|
end
|
13
14
|
|
14
15
|
# @api private
|
@@ -158,6 +159,15 @@ module IdentityCache
|
|
158
159
|
def expire_primary_key_cache_index(id)
|
159
160
|
cached_primary_index.expire(id)
|
160
161
|
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
def inherited(subclass)
|
166
|
+
super
|
167
|
+
subclass.class_eval do
|
168
|
+
@cached_primary_index = nil
|
169
|
+
end
|
170
|
+
end
|
161
171
|
end
|
162
172
|
end
|
163
173
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: identity_cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Camilo Lopez
|
@@ -14,7 +14,7 @@ authors:
|
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
|
-
date:
|
17
|
+
date: 2023-03-23 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: activerecord
|
@@ -190,7 +190,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
190
190
|
- !ruby/object:Gem::Version
|
191
191
|
version: '0'
|
192
192
|
requirements: []
|
193
|
-
rubygems_version: 3.
|
193
|
+
rubygems_version: 3.4.9
|
194
194
|
signing_key:
|
195
195
|
specification_version: 4
|
196
196
|
summary: IdentityCache lets you specify how you want to cache your model objects,
|