incrdecr_cached_counts 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|