Skip to content

Commit

Permalink
Handle max_age: nil
Browse files Browse the repository at this point in the history
  • Loading branch information
brian-kephart committed Jan 4, 2024
1 parent 9631687 commit d8fe0a2
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 12 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Solid Cache supports these options in addition to the standard `ActiveSupport::C
- `error_handler` - a Proc to call to handle any `ActiveRecord::ActiveRecordError`s that are raises (default: log errors as warnings)
- `expiry_batch_size` - the batch size to use when deleting old records (default: `100`)
- `expiry_method` - what expiry method to use `thread` or `job` (default: `thread`)
- `max_age` - the maximum age of entries in the cache (default: `2.weeks.to_i`)
- `max_age` - the maximum age of entries in the cache (default: `2.weeks.to_i`). Can be set to `nil`, but this is not recommended unless using `max_entries` to limit the size of the cache.
- `max_entries` - the maximum number of entries allowed in the cache (default: `nil`, meaning no limit)
- `cluster` - a Hash of options for the cache database cluster, e.g `{ shards: [:database1, :database2, :database3] }`
- `clusters` - and Array of Hashes for multiple cache clusters (ignored if `:cluster` is set)
Expand Down
32 changes: 22 additions & 10 deletions app/models/solid_cache/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Entry < Record
# 2. We avoid the overhead of building queries and active record objects
class << self
def write(key, value)
upsert_all_no_query_cache([ { key: key, value: value } ])
upsert_all_no_query_cache([ { key:, value: } ])
end

def write_multi(payloads)
Expand All @@ -29,7 +29,7 @@ def delete_by_key(key)

def delete_matched(matcher, batch_size:)
like_matcher = arel_table[:key].matches(matcher, nil, true)
where(like_matcher).select(:id).find_in_batches(batch_size: batch_size) do |entries|
where(like_matcher).select(:id).find_in_batches(batch_size:) do |entries|
delete_no_query_cache(:id, entries.map(&:id))
end
end
Expand All @@ -45,7 +45,7 @@ def clear_delete
def increment(key, amount)
transaction do
uncached do
amount += lock.where(key: key).pick(:value).to_i
amount += lock.where(key:).pick(:value).to_i
write(key, amount)
amount
end
Expand All @@ -63,7 +63,7 @@ def id_range
end

def expire(count, max_age:, max_entries:)
if (ids = expiry_candidate_ids(count, max_age: max_age, max_entries: max_entries)).any?
if (ids = expiry_candidate_ids(count, max_age:, max_entries:)).any?
delete(ids)
end
end
Expand Down Expand Up @@ -142,14 +142,26 @@ def to_binary(key)

def expiry_candidate_ids(count, max_age:, max_entries:)
cache_full = max_entries && max_entries < id_range
min_created_at = max_age.seconds.ago
return [] unless cache_full || max_age

# In the case of multiple concurrent expiry operations, it is desirable to
# reduce the overlap of entries being addressed by each. For that reason,
# retrieve more ids than are being expired, and use random
# sampling to reduce that number to the actual intended count.
retrieve_count = count * 3

uncached do
order(:id)
.limit(count * 3)
.pluck(:id, :created_at)
.filter_map { |id, created_at| id if cache_full || created_at < min_created_at }
.sample(count)
candidates = order(:id).limit(retrieve_count)

candidate_ids = if cache_full
candidates.pluck(:id)
else
min_created_at = max_age.seconds.ago
candidates.pluck(:id, :created_at)
.filter_map { |id, created_at| id if created_at < min_created_at }
end

candidate_ids.sample(count)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion test/unit/expiry_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class SolidCache::ExpiryTest < ActiveSupport::TestCase
end

test "expires records when the cache is full (#{expiry_method})" do
@cache = lookup_store(expiry_batch_size: 3, max_age: 2.weeks, max_entries: 2, expiry_method: expiry_method)
@cache = lookup_store(expiry_batch_size: 3, max_age: nil, max_entries: 2, expiry_method: expiry_method)
default_shard_keys = shard_keys(@cache, :default)
@cache.write(default_shard_keys[0], 1)
@cache.write(default_shard_keys[1], 2)
Expand Down

0 comments on commit d8fe0a2

Please sign in to comment.