debian-mirror-gitlab/app/models/concerns/reactive_caching.rb

160 lines
4.7 KiB
Ruby
Raw Normal View History

2018-11-20 20:47:30 +05:30
# frozen_string_literal: true
2020-03-13 15:44:24 +05:30
# The usage of the ReactiveCaching module is documented here:
# https://docs.gitlab.com/ee/development/utilities.html#reactivecaching
2017-08-17 22:00:37 +05:30
module ReactiveCaching
extend ActiveSupport::Concern
2018-11-18 11:00:15 +05:30
InvalidateReactiveCache = Class.new(StandardError)
2020-03-13 15:44:24 +05:30
ExceededReactiveCacheLimit = Class.new(StandardError)
2018-11-18 11:00:15 +05:30
2017-08-17 22:00:37 +05:30
included do
2020-03-13 15:44:24 +05:30
extend ActiveModel::Naming
2017-08-17 22:00:37 +05:30
class_attribute :reactive_cache_key
2020-03-13 15:44:24 +05:30
class_attribute :reactive_cache_lease_timeout
2017-08-17 22:00:37 +05:30
class_attribute :reactive_cache_refresh_interval
2020-03-13 15:44:24 +05:30
class_attribute :reactive_cache_lifetime
class_attribute :reactive_cache_hard_limit
2019-07-07 11:18:12 +05:30
class_attribute :reactive_cache_worker_finder
2017-08-17 22:00:37 +05:30
# defaults
2019-09-04 21:01:54 +05:30
self.reactive_cache_key = -> (record) { [model_name.singular, record.id] }
2017-08-17 22:00:37 +05:30
self.reactive_cache_lease_timeout = 2.minutes
self.reactive_cache_refresh_interval = 1.minute
self.reactive_cache_lifetime = 10.minutes
2020-03-13 15:44:24 +05:30
self.reactive_cache_hard_limit = 1.megabyte
2019-07-07 11:18:12 +05:30
self.reactive_cache_worker_finder = ->(id, *_args) do
find_by(primary_key => id)
end
2017-08-17 22:00:37 +05:30
def calculate_reactive_cache(*args)
raise NotImplementedError
end
2018-11-18 11:00:15 +05:30
def reactive_cache_updated(*args)
end
2017-08-17 22:00:37 +05:30
def with_reactive_cache(*args, &blk)
2018-11-18 11:00:15 +05:30
unless within_reactive_cache_lifetime?(*args)
refresh_reactive_cache!(*args)
2019-07-07 11:18:12 +05:30
return
2018-11-18 11:00:15 +05:30
end
2018-11-08 19:23:39 +05:30
2018-11-18 11:00:15 +05:30
keep_alive_reactive_cache!(*args)
begin
2017-08-17 22:00:37 +05:30
data = Rails.cache.read(full_reactive_cache_key(*args))
2019-07-07 11:18:12 +05:30
yield data unless data.nil?
2018-11-18 11:00:15 +05:30
rescue InvalidateReactiveCache
refresh_reactive_cache!(*args)
nil
2017-08-17 22:00:37 +05:30
end
end
2020-04-08 14:13:33 +05:30
def with_reactive_cache_set(resource, opts, &blk)
data = with_reactive_cache(resource, opts, &blk)
save_keys_in_set(resource, opts) if data
data
end
2020-03-13 15:44:24 +05:30
# This method is used for debugging purposes and should not be used otherwise.
def without_reactive_cache(*args, &blk)
return with_reactive_cache(*args, &blk) unless Rails.env.development?
data = self.class.reactive_cache_worker_finder.call(id, *args).calculate_reactive_cache(*args)
yield data
end
2017-08-17 22:00:37 +05:30
def clear_reactive_cache!(*args)
Rails.cache.delete(full_reactive_cache_key(*args))
2018-11-08 19:23:39 +05:30
Rails.cache.delete(alive_reactive_cache_key(*args))
2017-08-17 22:00:37 +05:30
end
2020-04-08 14:13:33 +05:30
def clear_reactive_cache_set!(*args)
cache_key = full_reactive_cache_key(args)
reactive_set_cache.clear_cache!(cache_key)
end
2017-08-17 22:00:37 +05:30
def exclusively_update_reactive_cache!(*args)
locking_reactive_cache(*args) do
2020-01-01 13:55:28 +05:30
key = full_reactive_cache_key(*args)
2018-11-08 19:23:39 +05:30
if within_reactive_cache_lifetime?(*args)
2017-08-17 22:00:37 +05:30
enqueuing_update(*args) do
2018-11-18 11:00:15 +05:30
new_value = calculate_reactive_cache(*args)
2020-03-13 15:44:24 +05:30
check_exceeded_reactive_cache_limit!(new_value)
2018-11-18 11:00:15 +05:30
old_value = Rails.cache.read(key)
Rails.cache.write(key, new_value)
reactive_cache_updated(*args) if new_value != old_value
2017-08-17 22:00:37 +05:30
end
2020-01-01 13:55:28 +05:30
else
Rails.cache.delete(key)
2017-08-17 22:00:37 +05:30
end
end
end
private
2020-04-08 14:13:33 +05:30
def save_keys_in_set(resource, opts)
cache_key = full_reactive_cache_key(resource)
reactive_set_cache.write(cache_key, "#{cache_key}:#{opts}")
end
def reactive_set_cache
Gitlab::ReactiveCacheSetCache.new(expires_in: reactive_cache_lifetime)
end
2018-11-18 11:00:15 +05:30
def refresh_reactive_cache!(*args)
clear_reactive_cache!(*args)
keep_alive_reactive_cache!(*args)
ReactiveCachingWorker.perform_async(self.class, id, *args)
end
def keep_alive_reactive_cache!(*args)
Rails.cache.write(alive_reactive_cache_key(*args), true, expires_in: self.class.reactive_cache_lifetime)
end
2017-08-17 22:00:37 +05:30
def full_reactive_cache_key(*qualifiers)
prefix = self.class.reactive_cache_key
prefix = prefix.call(self) if prefix.respond_to?(:call)
([prefix].flatten + qualifiers).join(':')
end
def alive_reactive_cache_key(*qualifiers)
full_reactive_cache_key(*(qualifiers + ['alive']))
end
def locking_reactive_cache(*args)
lease = Gitlab::ExclusiveLease.new(full_reactive_cache_key(*args), timeout: reactive_cache_lease_timeout)
uuid = lease.try_obtain
yield if uuid
ensure
Gitlab::ExclusiveLease.cancel(full_reactive_cache_key(*args), uuid)
end
2018-11-08 19:23:39 +05:30
def within_reactive_cache_lifetime?(*args)
2019-12-04 20:38:33 +05:30
Rails.cache.exist?(alive_reactive_cache_key(*args))
2017-08-17 22:00:37 +05:30
end
def enqueuing_update(*args)
yield
2019-09-30 21:07:59 +05:30
2017-08-17 22:00:37 +05:30
ReactiveCachingWorker.perform_in(self.class.reactive_cache_refresh_interval, self.class, id, *args)
end
2020-03-13 15:44:24 +05:30
def check_exceeded_reactive_cache_limit!(data)
return unless Feature.enabled?(:reactive_cache_limit)
data_deep_size = Gitlab::Utils::DeepSize.new(data, max_size: self.class.reactive_cache_hard_limit)
raise ExceededReactiveCacheLimit.new unless data_deep_size.valid?
end
2017-08-17 22:00:37 +05:30
end
end