Skip to content

Commit 085f877

Browse files
authored
Merge pull request #132 from rails/probabilistic-expiry
Replace expiry counter with random chance
2 parents a5bd61a + b7b14b7 commit 085f877

File tree

2 files changed

+46
-28
lines changed

2 files changed

+46
-28
lines changed

lib/solid_cache/cluster/expiry.rb

+7-27
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,29 @@ module Expiry
99
# This ensures there is downward pressure on the cache size while there is valid data to delete
1010
EXPIRY_MULTIPLIER = 1.25
1111

12-
attr_reader :expiry_batch_size, :expiry_method, :expiry_queue, :expire_every, :max_age, :max_entries
12+
attr_reader :expiry_batch_size, :expiry_method, :expiry_queue, :expiry_chance, :max_age, :max_entries
1313

1414
def initialize(options = {})
1515
super(options)
1616
@expiry_batch_size = options.fetch(:expiry_batch_size, 100)
1717
@expiry_method = options.fetch(:expiry_method, :thread)
1818
@expiry_queue = options.fetch(:expiry_queue, :default)
19-
@expire_every = [ (expiry_batch_size / EXPIRY_MULTIPLIER).floor, 1 ].max
19+
@expiry_chance = (1 / expiry_batch_size.to_f) * EXPIRY_MULTIPLIER
2020
@max_age = options.fetch(:max_age, 2.weeks.to_i)
2121
@max_entries = options.fetch(:max_entries, nil)
2222

2323
raise ArgumentError, "Expiry method must be one of `:thread` or `:job`" unless [ :thread, :job ].include?(expiry_method)
2424
end
2525

2626
def track_writes(count)
27-
expire_later if expiry_counter.count(count)
27+
expire_later if expire?
2828
end
2929

3030
private
31+
def expire?
32+
rand < expiry_chance
33+
end
34+
3135
def expire_later
3236
if expiry_method == :job
3337
ExpiryJob
@@ -37,30 +41,6 @@ def expire_later
3741
async { Entry.expire(expiry_batch_size, max_age: max_age, max_entries: max_entries) }
3842
end
3943
end
40-
41-
def expiry_counter
42-
@expiry_counters ||= connection_names.index_with { |connection_name| Counter.new(expire_every) }
43-
@expiry_counters[Entry.current_shard]
44-
end
45-
46-
class Counter
47-
attr_reader :expire_every, :counter
48-
49-
def initialize(expire_every)
50-
@expire_every = expire_every
51-
@counter = Concurrent::AtomicFixnum.new(rand(expire_every).to_i)
52-
end
53-
54-
def count(count)
55-
value = counter.increment(count)
56-
new_multiple_of_expire_every?(value - count, value)
57-
end
58-
59-
private
60-
def new_multiple_of_expire_every?(first_value, second_value)
61-
first_value / expire_every != second_value / expire_every
62-
end
63-
end
6444
end
6545
end
6646
end

test/unit/expiry_test.rb

+39-1
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ class SolidCache::ExpiryTest < ActiveSupport::TestCase
99

1010
setup do
1111
@namespace = "test-#{SecureRandom.hex}"
12-
SolidCache::Cluster.any_instance.stubs(:rand).returns(0)
1312
end
1413

1514
[ :thread, :job ].each do |expiry_method|
1615
test "expires old records (#{expiry_method})" do
16+
SolidCache::Cluster.any_instance.stubs(:rand).returns(0)
17+
1718
@cache = lookup_store(expiry_batch_size: 3, max_age: 2.weeks, expiry_method: expiry_method)
1819
default_shard_keys = shard_keys(@cache, :default)
1920
@cache.write(default_shard_keys[0], 1)
@@ -36,6 +37,8 @@ class SolidCache::ExpiryTest < ActiveSupport::TestCase
3637
end
3738

3839
test "expires records when the cache is full (#{expiry_method})" do
40+
SolidCache::Cluster.any_instance.stubs(:rand).returns(0)
41+
3942
@cache = lookup_store(expiry_batch_size: 3, max_age: nil, max_entries: 2, expiry_method: expiry_method)
4043
default_shard_keys = shard_keys(@cache, :default)
4144
@cache.write(default_shard_keys[0], 1)
@@ -54,6 +57,8 @@ class SolidCache::ExpiryTest < ActiveSupport::TestCase
5457
end
5558

5659
test "expires records no shards (#{expiry_method})" do
60+
SolidCache::Cluster.any_instance.stubs(:rand).returns(0)
61+
5762
@cache = ActiveSupport::Cache.lookup_store(:solid_cache_store, expiry_batch_size: 3, namespace: @namespace, max_entries: 2, expiry_method: expiry_method)
5863
default_shard_keys = shard_keys(@cache, :default)
5964

@@ -72,8 +77,39 @@ class SolidCache::ExpiryTest < ActiveSupport::TestCase
7277
assert_equal 1, SolidCache.each_shard.sum { SolidCache::Entry.count }
7378
end
7479

80+
test "expires when random number is below threshold (#{expiry_method})" do
81+
SolidCache::Cluster.any_instance.stubs(:rand).returns(0.416)
82+
83+
@cache = ActiveSupport::Cache.lookup_store(:solid_cache_store, expiry_batch_size: 3, namespace: @namespace, max_entries: 1, expiry_method: expiry_method)
84+
default_shard_keys = shard_keys(@cache, :default)
85+
86+
@cache.write(default_shard_keys[0], 1)
87+
@cache.write(default_shard_keys[1], 2)
88+
89+
sleep 0.1
90+
perform_enqueued_jobs
91+
92+
assert_equal 0, SolidCache.each_shard.sum { SolidCache::Entry.count }
93+
end
94+
95+
test "doesn't expire when random number is above threshold (#{expiry_method})" do
96+
SolidCache::Cluster.any_instance.stubs(:rand).returns(0.417)
97+
98+
@cache = ActiveSupport::Cache.lookup_store(:solid_cache_store, expiry_batch_size: 3, namespace: @namespace, max_entries: 1, expiry_method: expiry_method)
99+
default_shard_keys = shard_keys(@cache, :default)
100+
101+
@cache.write(default_shard_keys[0], 1)
102+
@cache.write(default_shard_keys[1], 2)
103+
104+
sleep 0.1
105+
perform_enqueued_jobs
106+
107+
assert_equal 2, SolidCache.each_shard.sum { SolidCache::Entry.count }
108+
end
109+
75110
unless ENV["NO_CONNECTS_TO"]
76111
test "expires old records multiple shards (#{expiry_method})" do
112+
SolidCache::Cluster.any_instance.stubs(:rand).returns(0, 1, 0, 1, 0, 1, 0, 1)
77113
@cache = lookup_store(expiry_batch_size: 2, cluster: { shards: [ :default, :primary_shard_one ] }, expiry_method: expiry_method)
78114
default_shard_keys = shard_keys(@cache, :default)
79115
shard_one_keys = shard_keys(@cache, :primary_shard_one)
@@ -118,6 +154,8 @@ class SolidCache::ExpiryTest < ActiveSupport::TestCase
118154
end
119155

120156
test "expires old records with a custom queue" do
157+
SolidCache::Cluster.any_instance.stubs(:rand).returns(0, 1, 0, 1)
158+
121159
@cache = lookup_store(expiry_batch_size: 3, max_entries: 2, expiry_method: :job, expiry_queue: :cache_expiry)
122160

123161
default_shard_keys = shard_keys(@cache, :default)

0 commit comments

Comments
 (0)