incrdecr_cached_counts 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a018b5815304845fe6bd35505944da125418b3e5
4
- data.tar.gz: c1799c3ddaa64683faf943ea9e0d352177c896f0
3
+ metadata.gz: 933d0e94e08544064f20c36691f4c82fc581a843
4
+ data.tar.gz: d60e51a4e1f2f3be7329f41999199ee04c471675
5
5
  SHA512:
6
- metadata.gz: d4ae781d8e7394523beb4f91cbb622f018bc9ee30a04a92cbd4e00e7bdecbeb87f93c9f87dacc6b27a41872c1b1b64f97349a4ecfb1a89043345922a8f57b699
7
- data.tar.gz: d52e2154bc2bd201b8297b9ca32cd4b142f1a265e2f7f89b8df3b86aa4c0b8ccd79ecf76e847da13c0f86389ca017ef465ae501d330a22653fb13521e73e1862
6
+ metadata.gz: 0e83facc3fb470a922d4dbbca8fbefdfe525a8a03016a49a9e3f244b97f8ca46e4c77a1d46143fd9ad84046c98d5bc1d9c46ab50fbd861f58c15d618947e9c7b
7
+ data.tar.gz: c7fb4d8e58784e72ac21884abb76412050a18c6e55520d470b65fc336c49336728ff2677bf2f6b5760c2c6a5bba2ed8683ff44280ebd1967e4982b1125b80352
@@ -1,3 +1,3 @@
1
1
  module CachedCounts
2
- VERSION = "0.0.6"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/cached_counts.rb CHANGED
@@ -39,13 +39,11 @@ 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 [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.
42
+ # @option options [Lambda] :default_value_lambda
43
+ # Uses this value when the cache is empty, while it is being populated
44
+ #
45
+ # @option options [Lambda] :value_updater_lambda
46
+ # Override logic for updating a cache value - e.g. to perform background updates
49
47
  #
50
48
  def caches_count_where(attribute_name, options = {})
51
49
  # Delay actual run to work around circular dependencies
@@ -136,14 +134,6 @@ module CachedCounts
136
134
  version = options.fetch :version, 1
137
135
  key = scope_count_key(attribute_name, version)
138
136
 
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
-
147
137
  [attribute_name, *Array(options[:alias])].each do |attr_name|
148
138
  add_count_attribute_methods(
149
139
  attr_name,
@@ -156,23 +146,6 @@ module CachedCounts
156
146
  end
157
147
  end
158
148
 
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
-
176
149
  def define_association_count_attribute(attribute_name, association, options)
177
150
  options = options.dup
178
151
 
@@ -225,7 +198,12 @@ module CachedCounts
225
198
 
226
199
  def add_count_attribute_methods(attribute_name, key_getter, relation_getter, define_with, counted_class, options)
227
200
  expires_in = options.fetch :expires_in, 1.week
228
- race_condition_fallback = options.fetch :race_condition_fallback, nil
201
+ default_value_lambda = options.fetch :default_value_lambda, -> {0}
202
+ value_updater_lambda = options.fetch :value_updater_lambda, default_value_updater_lambda
203
+ # TODO: need a good strategy to figure this value out
204
+ # As a fallback value is used, we immediately fire the calculation for the real value
205
+ # Is it reasonable to wait as long as it takes to calcualte it? Is there an alternative?
206
+ fallback_expiry_seconds = expires_in
229
207
 
230
208
  key_method = "#{attribute_name}_count_key"
231
209
 
@@ -235,34 +213,23 @@ module CachedCounts
235
213
  val = Rails.cache.fetch(
236
214
  send(key_method),
237
215
  expires_in: expires_in,
238
- race_condition_ttl: 30.seconds,
216
+ race_condition_ttl: fallback_expiry_seconds,
239
217
  raw: true # Necessary for incrementing to work correctly
240
218
  ) do
241
- if race_condition_fallback
242
- # Ensure that other reads find something in the cache, but
243
- # continue calculating here because the default is likely inaccurate.
244
- fallback_value = instance_exec &race_condition_fallback
245
- CachedCounts.logger.warn "Setting #{fallback_value} as race_condition_fallback for #{send(key_method)}"
246
- Rails.cache.write(
247
- send(key_method),
248
- fallback_value.to_i,
249
- expires_in: 30.seconds,
250
- raw: true
251
- )
252
- end
253
-
219
+ # Write down the default value first so subsequent reads don't repeat calculations
220
+ # TODO: multiple requests can potentially get to this point and start multiple update operations? Is that a problem?
221
+ default_value = instance_exec &default_value_lambda
222
+ Rails.cache.write(
223
+ send(key_method),
224
+ default_value,
225
+ expires_in: fallback_expiry_seconds,
226
+ raw: true
227
+ )
254
228
  relation = instance_exec(&relation_getter)
255
229
  relation = relation.reorder('')
256
230
  relation.select_values = ['count(*)']
257
-
258
- conn = CachedCounts.connection_for(counted_class)
259
- if Rails.version < '4.2'.freeze
260
- conn.select_value(relation.to_sql, nil, relation.values[:bind] || []).to_i
261
- else
262
- conn.select_value(relation.to_sql).to_i
263
- end
231
+ value_updater_lambda.call(counted_class, relation.to_sql, send(key_method), expires_in) || default_value
264
232
  end
265
-
266
233
  if val.is_a?(ActiveSupport::Cache::Entry)
267
234
  val.value.to_i
268
235
  else
@@ -284,6 +251,20 @@ module CachedCounts
284
251
  end
285
252
  end
286
253
 
254
+ def default_value_updater_lambda()
255
+ lambda { |counted_class, relation_sql, cache_key, expires_in|
256
+ conn = CachedCounts.connection_for(counted_class)
257
+ value = conn.select_value(relation_sql).to_i
258
+ Rails.cache.write(
259
+ cache_key,
260
+ value,
261
+ expires_in: expires_in,
262
+ raw: true
263
+ )
264
+ return value
265
+ }
266
+ end
267
+
287
268
  def add_counting_hooks(attribute_name, key_getter, counted_class, options)
288
269
  increment_hook = "increment_#{attribute_name}_count"
289
270
  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.0.6
4
+ version: 0.1.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: 2016-06-23 00:00:00.000000000 Z
11
+ date: 2016-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -175,18 +175,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
175
175
  version: '0'
176
176
  requirements: []
177
177
  rubyforge_project:
178
- rubygems_version: 2.4.5.1
178
+ rubygems_version: 2.5.1
179
179
  signing_key:
180
180
  specification_version: 4
181
181
  summary: A replacement for Rails' counter caches using memcached (via Dalli)
182
- test_files:
183
- - spec/caches_count_of_spec.rb
184
- - spec/caches_count_where_spec.rb
185
- - spec/database.yml
186
- - spec/fixtures.rb
187
- - spec/fixtures/department.rb
188
- - spec/fixtures/following.rb
189
- - spec/fixtures/university.rb
190
- - spec/fixtures/user.rb
191
- - spec/spec_helper.rb
182
+ test_files: []
192
183
  has_rdoc: