incrdecr_cached_counts 0.0.6 → 0.1.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 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: