1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
|
# frozen_string_literal: true
# This cache strategy is an implementation on top of the redis hash data type, that also adheres to the
# ActiveSupport::Cache::Store interface. It's a good example of how to build a custom caching strategy for
# Gitlab::Experiment, and is intended to be a long lived cache -- until the experiment is cleaned up.
#
# The data structure:
# key: experiment.name
# fields: context key => variant name
#
# Example configuration usage:
#
# config.cache = Gitlab::Experiment::Cache::RedisHashStore.new(
# pool: ->(&block) { block.call(Redis.current) }
# )
#
module Gitlab
class Experiment
module Cache
class RedisHashStore < ActiveSupport::Cache::Store
# Clears the entire cache for a given experiment. Be careful with this since it would reset all resolved
# variants for the entire experiment.
def clear(key:)
key = hkey(key)[0] # extract only the first part of the key
pool do |redis|
case redis.type(key)
when 'hash', 'none'
redis.del(key) # delete the primary experiment key
redis.del("#{key}_attrs") # delete the experiment attributes key
else raise ArgumentError, 'invalid call to clear a non-hash cache key'
end
end
end
def increment(key, amount = 1)
pool { |redis| redis.hincrby(*hkey(key), amount) }
end
private
def pool(&block)
raise ArgumentError, 'missing block' unless block.present?
@options[:pool].call(&block)
end
def hkey(key)
key.to_s.split(':') # this assumes the default strategy in gitlab-experiment
end
def read_entry(key, **_options)
value = pool { |redis| redis.hget(*hkey(key)) }
value.nil? ? nil : ActiveSupport::Cache::Entry.new(value)
end
def write_entry(key, entry, **_options)
return false if entry.value.blank? # don't cache any empty values
pool { |redis| redis.hset(*hkey(key), entry.value) }
end
def delete_entry(key, **_options)
pool { |redis| redis.hdel(*hkey(key)) }
end
end
end
end
end
|