incrdecr_cached_counts 0.2.1 → 0.3.0
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/cached_counts.rb +55 -36
- data/lib/cached_counts/version.rb +1 -1
- metadata +12 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42bbe0c4b1d6ddc82d92bc3d1475dcf3e7553ef9f8299d0e3d75aa5d7c8519c6
|
4
|
+
data.tar.gz: 652f35c8b0c667c3ee9517494c2a23b04ea3f90a7591d74922538c84906c754f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6610220977371d6bde5f48e6aa6dfbe879e4213d04fd0ae7bc05a00e4766751ee427b5e2f3e42ecb88fb8e665885a786f38a125a81fc042b258078e3a3debfa1
|
7
|
+
data.tar.gz: 6d8c16611f42f680f4350b196935cf0caffef9253e6b016e1731b142f5d719c3ce43f2293869efb6393cb52f61e05e4819dfdac6357fccf4f228ff05111b3306
|
data/lib/cached_counts.rb
CHANGED
@@ -39,11 +39,13 @@ module CachedCounts
|
|
39
39
|
# @option options [Integer, #to_s] :version
|
40
40
|
# Cache version - bump if you change the definition of a count.
|
41
41
|
#
|
42
|
-
# @option options [
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
42
|
+
# @option options [Proc] :race_condition_fallback
|
43
|
+
# Fallback to the result of this proc if the cache is empty, while
|
44
|
+
# loading the actual value from the db. Works similarly to
|
45
|
+
# +race_condition_ttl+ but for empty caches rather than expired values.
|
46
|
+
# Meant to prevent a thundering-herd scenario, if for example a
|
47
|
+
# memcached instance goes away. Can be nil; defaults to using a value
|
48
|
+
# grabbed from the cache or DB at startup.
|
47
49
|
#
|
48
50
|
def caches_count_where(attribute_name, options = {})
|
49
51
|
# Delay actual run to work around circular dependencies
|
@@ -134,6 +136,14 @@ module CachedCounts
|
|
134
136
|
version = options.fetch :version, 1
|
135
137
|
key = scope_count_key(attribute_name, version)
|
136
138
|
|
139
|
+
unless options.has_key?(:race_condition_fallback)
|
140
|
+
options[:race_condition_fallback] = default_race_condition_fallback_proc(
|
141
|
+
key,
|
142
|
+
relation,
|
143
|
+
options
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
137
147
|
[attribute_name, *Array(options[:alias])].each do |attr_name|
|
138
148
|
add_count_attribute_methods(
|
139
149
|
attr_name,
|
@@ -146,6 +156,23 @@ module CachedCounts
|
|
146
156
|
end
|
147
157
|
end
|
148
158
|
|
159
|
+
def default_race_condition_fallback_proc(key, relation, options)
|
160
|
+
fallback = Rails.cache.read(key)
|
161
|
+
fallback = fallback.value if fallback.is_a?(ActiveSupport::Cache::Entry)
|
162
|
+
|
163
|
+
if fallback.nil?
|
164
|
+
begin
|
165
|
+
fallback = relation.count
|
166
|
+
rescue ActiveRecord::StatementInvalid => e
|
167
|
+
fallback = 0
|
168
|
+
end
|
169
|
+
|
170
|
+
Rails.cache.write key, fallback, expires_in: options.fetch(:expires_in, 1.week), raw: true
|
171
|
+
end
|
172
|
+
|
173
|
+
-> { fallback }
|
174
|
+
end
|
175
|
+
|
149
176
|
def define_association_count_attribute(attribute_name, association, options)
|
150
177
|
options = options.dup
|
151
178
|
|
@@ -210,12 +237,7 @@ module CachedCounts
|
|
210
237
|
|
211
238
|
def add_count_attribute_methods(attribute_name, key_getter, relation_getter, define_with, counted_class, options)
|
212
239
|
expires_in = options.fetch :expires_in, 1.week
|
213
|
-
|
214
|
-
value_updater_lambda = options.fetch :value_updater_lambda, default_value_updater_lambda
|
215
|
-
# TODO: need a good strategy to figure this value out
|
216
|
-
# As a fallback value is used, we immediately fire the calculation for the real value
|
217
|
-
# Is it reasonable to wait as long as it takes to calcualte it? Is there an alternative?
|
218
|
-
fallback_expiry_seconds = expires_in
|
240
|
+
race_condition_fallback = options.fetch :race_condition_fallback, nil
|
219
241
|
|
220
242
|
key_method = "#{attribute_name}_count_key"
|
221
243
|
|
@@ -225,23 +247,34 @@ module CachedCounts
|
|
225
247
|
val = Rails.cache.fetch(
|
226
248
|
send(key_method),
|
227
249
|
expires_in: expires_in,
|
228
|
-
race_condition_ttl:
|
250
|
+
race_condition_ttl: 30.seconds,
|
229
251
|
raw: true # Necessary for incrementing to work correctly
|
230
252
|
) do
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
send(key_method)
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
253
|
+
if race_condition_fallback
|
254
|
+
# Ensure that other reads find something in the cache, but
|
255
|
+
# continue calculating here because the default is likely inaccurate.
|
256
|
+
fallback_value = instance_exec &race_condition_fallback
|
257
|
+
CachedCounts.logger.warn "Setting #{fallback_value} as race_condition_fallback for #{send(key_method)}"
|
258
|
+
Rails.cache.write(
|
259
|
+
send(key_method),
|
260
|
+
fallback_value.to_i,
|
261
|
+
expires_in: 30.seconds,
|
262
|
+
raw: true
|
263
|
+
)
|
264
|
+
end
|
265
|
+
|
240
266
|
relation = instance_exec(&relation_getter)
|
241
267
|
relation = relation.reorder('')
|
242
268
|
relation.select_values = ['count(*)']
|
243
|
-
|
269
|
+
|
270
|
+
conn = CachedCounts.connection_for(counted_class)
|
271
|
+
if Rails.version < '4.2'.freeze
|
272
|
+
conn.select_value(relation.to_sql, nil, relation.values[:bind] || []).to_i
|
273
|
+
else
|
274
|
+
conn.select_value(relation.to_sql).to_i
|
275
|
+
end
|
244
276
|
end
|
277
|
+
|
245
278
|
if val.is_a?(ActiveSupport::Cache::Entry)
|
246
279
|
val.value.to_i
|
247
280
|
else
|
@@ -263,20 +296,6 @@ module CachedCounts
|
|
263
296
|
end
|
264
297
|
end
|
265
298
|
|
266
|
-
def default_value_updater_lambda()
|
267
|
-
lambda { |counted_class, relation_sql, cache_key, expires_in|
|
268
|
-
conn = CachedCounts.connection_for(counted_class)
|
269
|
-
value = conn.select_value(relation_sql).to_i
|
270
|
-
Rails.cache.write(
|
271
|
-
cache_key,
|
272
|
-
value,
|
273
|
-
expires_in: expires_in,
|
274
|
-
raw: true
|
275
|
-
)
|
276
|
-
return value
|
277
|
-
}
|
278
|
-
end
|
279
|
-
|
280
299
|
def add_counting_hooks(attribute_name, key_getter, counted_class, options)
|
281
300
|
increment_hook = "increment_#{attribute_name}_count".to_sym
|
282
301
|
counted_class.send :define_method, increment_hook do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: incrdecr_cached_counts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Judd
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-07-
|
11
|
+
date: 2019-07-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -177,4 +177,13 @@ rubygems_version: 3.0.3
|
|
177
177
|
signing_key:
|
178
178
|
specification_version: 4
|
179
179
|
summary: A replacement for Rails' counter caches using memcached (via Dalli)
|
180
|
-
test_files:
|
180
|
+
test_files:
|
181
|
+
- spec/caches_count_of_spec.rb
|
182
|
+
- spec/caches_count_where_spec.rb
|
183
|
+
- spec/database.yml
|
184
|
+
- spec/fixtures.rb
|
185
|
+
- spec/fixtures/department.rb
|
186
|
+
- spec/fixtures/following.rb
|
187
|
+
- spec/fixtures/university.rb
|
188
|
+
- spec/fixtures/user.rb
|
189
|
+
- spec/spec_helper.rb
|