class ActiveSupport::Cache::MemCacheStore
Memcached Cache Store¶ ↑
A cache store implementation which stores data in Memcached: memcached.org
This is currently the most popular cache store for production websites.
Special features:
-
Clustering and load balancing. One can specify multiple memcached servers, and
MemCacheStore
will load balance between all available servers. If a server goes down, thenMemCacheStore
will ignore it until it comes back up.
MemCacheStore
implements the Strategy::LocalCache
strategy which implements an in-memory cache inside of a block.
Constants
- ESCAPE_KEY_CHARS
- KEY_MAX_SIZE
- OVERRIDDEN_OPTIONS
-
These options represent behavior overridden by this implementation and should not be allowed to get down to the Dalli client
Public Class Methods
Source
# File lib/active_support/cache/mem_cache_store.rb, line 77 def initialize(*addresses) addresses = addresses.flatten options = addresses.extract_options! if options.key?(:cache_nils) options[:skip_nil] = !options.delete(:cache_nils) end super(options) unless [String, Dalli::Client, NilClass].include?(addresses.first.class) raise ArgumentError, "First argument must be an empty array, address, or array of addresses." end @mem_cache_options = options.dup # The value "compress: false" prevents duplicate compression within Dalli. @mem_cache_options[:compress] = false (OVERRIDDEN_OPTIONS - %i(compress)).each { |name| @mem_cache_options.delete(name) } @data = self.class.build_mem_cache(*(addresses + [@mem_cache_options])) end
Creates a new MemCacheStore
object, with the given memcached server addresses. Each address is either a host name, or a host-with-port string in the form of “host_name:port”. For example:
ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
If no addresses are provided, but ENV['MEMCACHE_SERVERS']
is defined, it will be used instead. Otherwise, MemCacheStore
will connect to localhost:11211 (the default memcached port).
ActiveSupport::Cache::Store::new
Source
# File lib/active_support/cache/mem_cache_store.rb, line 38 def self.supports_cache_versioning? true end
Advertise cache versioning support.
Public Instance Methods
Source
# File lib/active_support/cache/mem_cache_store.rb, line 171 def clear(options = nil) rescue_error_with(nil) { @data.with { |c| c.flush_all } } end
Clear the entire cache on all memcached servers. This method should be used with care when shared cache is being used.
Source
# File lib/active_support/cache/mem_cache_store.rb, line 158 def decrement(name, amount = 1, options = nil) options = merged_options(options) key = normalize_key(name, options) instrument(:decrement, key, amount: amount) do rescue_error_with nil do @data.with { |c| c.decr(key, amount, options[:expires_in], 0) } end end end
Decrement a cached integer value using the memcached decr atomic operator. Returns the updated value.
If the key is unset or has expired, it will be set to 0. Memcached does not support negative counters.
cache.decrement("foo") # => 0
To set a specific value, call write
passing raw: true
:
cache.write("baz", 5, raw: true) cache.decrement("baz") # => 4
Decrementing a non-numeric value, or a value written without raw: true
, will fail and return nil
.
Source
# File lib/active_support/cache/mem_cache_store.rb, line 132 def increment(name, amount = 1, options = nil) options = merged_options(options) key = normalize_key(name, options) instrument(:increment, key, amount: amount) do rescue_error_with nil do @data.with { |c| c.incr(key, amount, options[:expires_in], amount) } end end end
Increment a cached integer value using the memcached incr atomic operator. Returns the updated value.
If the key is unset or has expired, it will be set to amount
:
cache.increment("foo") # => 1 cache.increment("bar", 100) # => 100
To set a specific value, call write
passing raw: true
:
cache.write("baz", 5, raw: true) cache.increment("baz") # => 6
Incrementing a non-numeric value, or a value written without raw: true
, will fail and return nil
.
Source
# File lib/active_support/cache/mem_cache_store.rb, line 96 def inspect instance = @data || @mem_cache_options "#<#{self.class} options=#{options.inspect} mem_cache=#{instance.inspect}>" end
Source
# File lib/active_support/cache/mem_cache_store.rb, line 176 def stats @data.with { |c| c.stats } end
Get the statistics from the memcached servers.
Source
# File lib/active_support/cache/mem_cache_store.rb, line 102
Behaves the same as ActiveSupport::Cache::Store#write
, but supports additional options specific to memcached.
Additional Options¶ ↑
-
raw: true
- Sends the value directly to the server as raw bytes. The value must be a string or number. You can use memcached direct operations likeincrement
anddecrement
only on raw values. -
unless_exist: true
- Prevents overwriting an existing cache entry.
Private Instance Methods
Source
# File lib/active_support/cache/mem_cache_store.rb, line 238 def delete_entry(key, **options) rescue_error_with(false) { @data.with { |c| c.delete(key) } } end
Delete an entry from the cache.
Source
# File lib/active_support/cache/mem_cache_store.rb, line 269 def deserialize_entry(payload, raw: false, **) if payload && raw Entry.new(payload) else super(payload) end end
ActiveSupport::Cache::Store#deserialize_entry
Source
# File lib/active_support/cache/mem_cache_store.rb, line 253 def normalize_key(key, options) key = super if key key = key.dup.force_encoding(Encoding::ASCII_8BIT) key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" } if key.size > KEY_MAX_SIZE key_separator = ":hash:" key_hash = ActiveSupport::Digest.hexdigest(key) key_trim_size = KEY_MAX_SIZE - key_separator.size - key_hash.size key = "#{key[0, key_trim_size]}#{key_separator}#{key_hash}" end end key end
Memcache keys are binaries. So we need to force their encoding to binary before applying the regular expression to ensure we are escaping all characters properly.
ActiveSupport::Cache::Store#normalize_key
Source
# File lib/active_support/cache/mem_cache_store.rb, line 182 def read_entry(key, **options) deserialize_entry(read_serialized_entry(key, **options), **options) end
Read an entry from the cache.
Source
# File lib/active_support/cache/mem_cache_store.rb, line 212 def read_multi_entries(names, **options) keys_to_names = names.index_by { |name| normalize_key(name, options) } raw_values = begin @data.with { |c| c.get_multi(keys_to_names.keys) } rescue Dalli::UnmarshalError {} end values = {} raw_values.each do |key, value| entry = deserialize_entry(value, raw: options[:raw]) unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options)) begin values[keys_to_names[key]] = entry.value rescue DeserializationError end end end values end
Reads multiple entries from the cache implementation.
Source
# File lib/active_support/cache/mem_cache_store.rb, line 186 def read_serialized_entry(key, **options) rescue_error_with(nil) do @data.with { |c| c.get(key, options) } end end
Source
# File lib/active_support/cache/mem_cache_store.rb, line 277 def rescue_error_with(fallback) yield rescue Dalli::DalliError, ConnectionPool::Error, ConnectionPool::TimeoutError => error logger.error("DalliError (#{error}): #{error.message}") if logger ActiveSupport.error_reporter&.report( error, severity: :warning, source: "mem_cache_store.active_support", ) fallback end
Source
# File lib/active_support/cache/mem_cache_store.rb, line 242 def serialize_entry(entry, raw: false, **options) if raw entry.value.to_s else super(entry, raw: raw, **options) end end
ActiveSupport::Cache::Store#serialize_entry
Source
# File lib/active_support/cache/mem_cache_store.rb, line 193 def write_entry(key, entry, **options) write_serialized_entry(key, serialize_entry(entry, **options), **options) end
Write an entry to the cache.
Source
# File lib/active_support/cache/mem_cache_store.rb, line 197 def write_serialized_entry(key, payload, **options) method = options[:unless_exist] ? :add : :set expires_in = options[:expires_in].to_i if options[:race_condition_ttl] && expires_in > 0 && !options[:raw] # Set the memcache expire a few minutes in the future to support race condition ttls on read expires_in += 5.minutes end rescue_error_with nil do # Don't pass compress option to Dalli since we are already dealing with compression. options.delete(:compress) @data.with { |c| !!c.send(method, key, payload, expires_in, **options) } end end