gitlab-org--gitlab-foss/app/models/concerns/cacheable_attributes.rb
Stan Hu 978647c6cb Add a memory cache local to the thread to reduce Redis load
Loading `ApplicationSetting` from Redis was responsible for at least 50%
of the CPU load of the Redis cluster on GitLab.com. Since these values
generally don't change very much, we can load this from the database and
cache it in memory, skipping Redis altogther. We use
`ActiveSupport::Cache::MemoryStore` as a drop-in replacement for
`RedisCacheStore` even though we probably don't need synchronized access
within `Thread.current`.

Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/63977
2019-07-01 22:23:01 -07:00

81 lines
1.9 KiB
Ruby

# frozen_string_literal: true
module CacheableAttributes
extend ActiveSupport::Concern
included do
after_commit { self.class.expire }
end
class_methods do
def cache_key
"#{name}:#{Gitlab::VERSION}:#{Rails.version}".freeze
end
# Can be overridden
def current_without_cache
last
end
# Can be overridden
def defaults
{}
end
def build_from_defaults(attributes = {})
final_attributes = defaults
.merge(attributes)
.stringify_keys
.slice(*column_names)
new(final_attributes)
end
def cached
Gitlab::SafeRequestStore[:"#{name}_cached_attributes"] ||= retrieve_from_cache
end
def retrieve_from_cache
record = cache_backend.read(cache_key)
ensure_cache_setup if record.present?
record
end
def current
cached_record = cached
return cached_record if cached_record.present?
current_without_cache.tap { |current_record| current_record&.cache! }
rescue => e
if Rails.env.production?
Rails.logger.warn("Cached record for #{name} couldn't be loaded, falling back to uncached record: #{e}")
else
raise e
end
# Fall back to an uncached value if there are any problems (e.g. Redis down)
current_without_cache
end
def expire
cache_backend.delete(cache_key)
rescue
# Gracefully handle when Redis is not available. For example,
# omnibus may fail here during gitlab:assets:compile.
end
def ensure_cache_setup
# This is a workaround for a Rails bug that causes attribute methods not
# to be loaded when read from cache: https://github.com/rails/rails/issues/27348
define_attribute_methods
end
def cache_backend
Rails.cache
end
end
def cache!
self.class.cache_backend.write(self.class.cache_key, self, expires_in: 1.minute)
end
end