123456789_123456789_123456789_123456789_123456789_

Class: ActiveSupport::Cache::MemCacheStore

Relationships & Source Files
Namespace Children
Modules:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, Store
Instance Chain:
Inherits: ActiveSupport::Cache::Store
Defined in: activesupport/lib/active_support/cache/mem_cache_store.rb

Overview

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, then MemCacheStore will ignore it until it comes back up.

MemCacheStore implements the Strategy::LocalCache strategy which implements an in-memory cache inside of a block.

Constant Summary

Store - Inherited

DEFAULT_POOL_OPTIONS

Class Attribute Summary

Store - Inherited

Class Method Summary

Store - Inherited

.new

Creates a new cache.

.retrieve_pool_options

Instance Attribute Summary

Instance Method Summary

DupLocalCache - Included

Strategy::LocalCache - Included

#middleware

Middleware class can be inserted as a ::Rack handler to be local cache for the duration of request.

#with_local_cache

Use a local cache for the duration of block.

#bypass_local_cache, #delete_entry, #local_cache, #local_cache_key, #read_multi_entries, #read_serialized_entry, #use_temporary_local_cache, #write_cache_value, #write_serialized_entry, #cleanup, #clear, #decrement, #delete_matched, #increment

Store - Inherited

#cleanup

Cleans up the cache by removing expired entries.

#clear

Clears the entire cache.

#decrement

Decrements an integer value in the cache.

#delete

Deletes an entry in the cache.

#delete_matched

Deletes all entries with keys matching the pattern.

#delete_multi

Deletes multiple entries in the cache.

#exist?

Returns true if the cache contains an entry for the given key.

#fetch

Fetches data from the cache, using the given key.

#fetch_multi

Fetches data from the cache, using the given keys.

#increment

Increments an integer value in the cache.

#mute

Silences the logger within a block.

#read

Reads data from the cache, using the given key.

#read_multi

Reads multiple values at once from the cache.

#silence,
#silence!

Silences the logger.

#write

Writes the value to the cache with the key.

#write_multi

::ActiveSupport::Cache Storage API to write multiple values at once.

#_instrument, #default_serializer,
#delete_entry

Deletes an entry from the cache implementation.

#delete_multi_entries

Deletes multiples entries in the cache implementation.

#deserialize_entry,
#expanded_key

Expands key to be a consistent string value.

#expanded_version, #get_entry_value, #handle_expired_entry, #handle_invalid_expires_in, #instrument, #instrument_multi,
#key_matcher

Adds the namespace defined in the options to a pattern designed to match keys.

#merged_options

Merges the default options with ones specific to a method call.

#namespace_key

Prefix the key with a namespace string:

#normalize_key

Expands and namespaces the cache key.

#normalize_options

Normalize aliased options to their canonical form.

#normalize_version,
#read_entry

Reads an entry from the cache implementation.

#read_multi_entries

Reads multiple entries from the cache implementation.

#save_block_result_to_cache, #serialize_entry, #validate_options,
#write_entry

Writes an entry to the cache implementation.

#write_multi_entries

Writes multiple entries to the cache implementation.

#new_entry

Constructor Details

.new(*addresses) ⇒ MemCacheStore

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). Passing a Dalli::Client instance is deprecated and will be removed. Please pass an address instead.

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 118

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
  if addresses.first.is_a?(Dalli::Client)
    ActiveSupport.deprecator.warn(<<~MSG)
      Initializing MemCacheStore with a Dalli::Client is deprecated and will be removed in Rails 7.2.
      Use memcached server addresses instead.
    MSG
    @data = addresses.first
  else
    @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
end

Class Attribute Details

.supports_cache_versioning?Boolean (readonly)

Advertise cache versioning support.

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 38

def self.supports_cache_versioning?
  true
end

Class Method Details

.build_mem_cache(*addresses)

This method is for internal use only.

Creates a new Dalli::Client instance with specified addresses and options. If no addresses are provided, we give nil to Dalli::Client, so it uses its fallbacks:

  • ENV (if defined)

  • “127.0.0.1:11211” (otherwise)

    ActiveSupport::Cache::MemCacheStore.build_mem_cache

    # => #<Dalli::Client:0x007f98a47d2028 @servers=["127.0.0.1:11211"], @options={}, @ring=nil>

    ActiveSupport::Cache::MemCacheStore.build_mem_cache(‘localhost:10290’)

    # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 96

def self.build_mem_cache(*addresses) # :nodoc:
  addresses = addresses.flatten
  options = addresses.extract_options!
  addresses = nil if addresses.compact.empty?
  pool_options = retrieve_pool_options(options)

  if pool_options
    ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
  else
    Dalli::Client.new(addresses, options)
  end
end

Instance Method Details

#clear(options = nil)

Clear the entire cache on all memcached servers. This method should be used with care when shared cache is being used.

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 215

def clear(options = nil)
  rescue_error_with(nil) { @data.with { |c| c.flush_all } }
end

#decrement(name, amount = 1, options = nil)

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.

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 204

def decrement(name, amount = 1, options = nil)
  options = merged_options(options)
  instrument(:decrement, name, amount: amount) do
    rescue_error_with nil do
      @data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in], 0) }
    end
  end
end

#default_serializer (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 225

def default_serializer
  if Cache.format_version == 6.1
    ActiveSupport.deprecator.warn <<~EOM
      Support for `config.active_support.cache_format_version = 6.1` has been deprecated and will be removed in Rails 7.2.

      Check the Rails upgrade guide at https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#new-activesupport-cache-serialization-format
      for more information on how to upgrade.
    EOM
    Cache::SerializerWithFallback[:passthrough]
  else
    super
  end
end

#delete_entry(key, **options) (private)

Delete an entry from the cache.

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 296

def delete_entry(key, **options)
  rescue_error_with(false) { @data.with { |c| c.delete(key) } }
end

#deserialize_entry(payload, raw: false) (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 327

def deserialize_entry(payload, raw: false, **)
  if payload && raw
    Entry.new(payload)
  else
    super(payload)
  end
end

#increment(name, amount = 1, options = nil)

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.

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 180

def increment(name, amount = 1, options = nil)
  options = merged_options(options)
  instrument(:increment, name, amount: amount) do
    rescue_error_with nil do
      @data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in], amount) }
    end
  end
end

#inspect

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 144

def inspect
  instance = @data || @mem_cache_options
  "#<#{self.class} options=#{options.inspect} mem_cache=#{instance.inspect}>"
end

#normalize_key(key, options) (private)

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.

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 311

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

#read_entry(key, **options) (private)

Read an entry from the cache.

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 240

def read_entry(key, **options)
  deserialize_entry(read_serialized_entry(key, **options), **options)
end

#read_multi_entries(names, **options) (private)

Reads multiple entries from the cache implementation.

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 270

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

#read_serialized_entry(key, **options) (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 244

def read_serialized_entry(key, **options)
  rescue_error_with(nil) do
    @data.with { |c| c.get(key, options) }
  end
end

#rescue_error_with(fallback) (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 335

def rescue_error_with(fallback)
  yield
rescue Dalli::DalliError => error
  logger.error("DalliError (#{error}): #{error.message}") if logger
  ActiveSupport.error_reporter&.report(
    error,
    severity: :warning,
    source: "mem_cache_store.active_support",
  )
  fallback
end

#serialize_entry(entry, raw: false, **options) (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 300

def serialize_entry(entry, raw: false, **options)
  if raw
    entry.value.to_s
  else
    super(entry, raw: raw, **options)
  end
end

#stats

Get the statistics from the memcached servers.

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 220

def stats
  @data.with { |c| c.stats }
end

#write(name, value, options = nil)

Behaves the same as 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 like increment and decrement only on raw values.

  • unless_exist: true - Prevents overwriting an existing cache entry.

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 150

rdoc_method :method: write

#write_entry(key, entry, **options) (private)

Write an entry to the cache.

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 251

def write_entry(key, entry, **options)
  write_serialized_entry(key, serialize_entry(entry, **options), **options)
end

#write_serialized_entry(key, payload, **options) (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/cache/mem_cache_store.rb', line 255

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 false 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