Skip to content

Instantly share code, notes, and snippets.

@djmb
Created January 4, 2024 12:00
Show Gist options
  • Save djmb/bd90a92d0b6cf4a6e44e94049c7352ec to your computer and use it in GitHub Desktop.
Save djmb/bd90a92d0b6cf4a6e44e94049c7352ec to your computer and use it in GitHub Desktop.
module ActiveSupport
module Cache
# A cache that splits calls between multiple caches based on the cache key
#
# It takes a map of cache names to cache arguments and a splitter lambda
# that chooses the cache to use based on the cache key.
#
# Configure with something like:
# config.cache_store = [
# :splitter_cache_store,
# caches: { redis: :redis_cache_store, solid_cache: :solid_cache_store },
# splitter: ->(key) ::Digest::MD5.digest(key).unpack1("L>") % 100 < 50 ? :solid_cache : :redis
# ]
class SplitterCacheStore < Store
KEY_METHODS = [ :fetch, :read, :write, :delete, :exist?, :increment, :decrement ]
NO_KEY_METHODS = [ :silence!, :mute, :delete_matched, :cleanup, :clear ]
def self.supports_cache_versioning?
true
end
attr_reader :caches, :splitter
def initialize(options)
@caches = options.delete(:caches).to_h do |name, *cache_args|
[ name, ActiveSupport::Cache.lookup_store(*cache_args) ]
end
@splitter = options.delete(:splitter)
super(options)
end
KEY_METHODS.each do |method|
define_method method do |*args, **kwargs, &block|
choose_cache(args.first).public_send(method, *args, **kwargs, &block)
end
end
NO_KEY_METHODS.each do |method|
define_method method do |*args, **kwargs, &block|
caches.values.each do |cache|
cache.public_send(method, *args, **kwargs, &block)
end
end
end
def read_multi(*names)
options = names.extract_options!
cache_to_names(names).each_with_object({}) do |(cache, names), results|
results.merge!(cache.read_multi(*names, **options))
end
end
def write_multi(hash, options = nil)
per_cache_writes = {}
hash.each do |name, value|
cache = choose_cache(name)
per_cache_writes[cache] ||= {}
per_cache_writes[cache][name] = value
end
per_cache_writes.each do |cache, hash|
cache.write_multi(hash, options)
end
end
def fetch_multi(*names, &block)
raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block
options = names.extract_options!
cache_to_names(names).each_with_object({}) do |(cache, names), results|
results.merge!(cache.fetch_multi(*names, **options, &block))
end
end
def delete_multi(names, options = nil)
cache_to_names(names).each_with_object(0) do |(cache, names), results|
results += cache.delete_multi(names, options)
end
end
private
def choose_cache(key)
caches[splitter.call(normalize_key(key))]
end
def cache_to_names(names)
names.each_with_object({}) do |name, results|
(results[choose_cache(name)] ||= []) << name
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment