class ActiveSupport::Cache::LibmemcachedStore
Store using memcached gem as client
Global options can be passed to be applied to each method by default. Supported options are
-
:compress
: if set to true, data will be compress before stored -
:compress_threshold
: specify the threshold at which to compress
value, default is 4K
-
:namespace
: prepend each key with this value for simple namespacing -
:expires_in
: default TTL in seconds for each. Default value is 0, i.e. forever
Specific value can be passed per key with write and fetch command.
Options can also be passed direclty to the memcache client, via the :client
option. For example, if you want to use pipelining, you can use :client => { :no_block => true }
Constants
- DEFAULT_CLIENT_OPTIONS
- DEFAULT_COMPRESS_THRESHOLD
- ESCAPE_KEY_CHARS
- FLAG_COMPRESSED
Attributes
Public Class Methods
# File lib/active_support/cache/libmemcached_store.rb, line 67 def initialize(*addresses) addresses.flatten! options = addresses.extract_options! client_options = options.delete(:client) || {} if options[:namespace] client_options[:prefix_key] = options.delete(:namespace) client_options[:prefix_delimiter] = ':' end @namespace_length = "#{client_options[:prefix_key]}#{client_options[:prefix_delimiter]}".force_encoding("BINARY").size client_options[:default_ttl] = options.delete(:expires_in).to_i if options[:expires_in] @options = {compress_threshold: DEFAULT_COMPRESS_THRESHOLD}.merge(options) @addresses = addresses @cache = ::LibmemcachedStore::MemcachedWithFlags.new(@addresses, DEFAULT_CLIENT_OPTIONS.merge(client_options)) end
Public Instance Methods
# File lib/active_support/cache/libmemcached_store.rb, line 200 def clear(options = nil) instrument(:clear, "*") do @cache.flush end end
# File lib/active_support/cache/libmemcached_store.rb, line 166 def decrement(key, amount = 1, options = nil) key = normalize_key(key) instrument(:decrement, key, amount: amount) do @cache.decr(key, amount) end rescue Memcached::NotFound nil rescue Memcached::Error => e log_error(e) nil end
# File lib/active_support/cache/libmemcached_store.rb, line 133 def delete(key, options = nil) key = normalize_key(key) instrument(:delete, key) do |payload| delete_entry(key, options) end end
# File lib/active_support/cache/libmemcached_store.rb, line 140 def exist?(key, options = nil) key = normalize_key(key) instrument(:exist?, key) do |payload| if @cache.respond_to?(:exist) @cache.exist(key) true else read_entry(key, options) != nil end end rescue Memcached::NotFound false end
# File lib/active_support/cache/libmemcached_store.rb, line 84 def fetch(key, options = nil, &block) if block_given? if options && options[:race_condition_ttl] && options[:expires_in] fetch_with_race_condition_ttl(key, options, &block) else key = normalize_key(key) unless options && options[:force] entry = instrument(:read, key, options) do |payload| read_entry(key, options).tap do |result| if payload payload[:super_operation] = :fetch payload[:hit] = !!result end end end end if entry.nil? result = instrument(:generate, key, options) do |payload| yield end write_entry(key, result, options) result else instrument(:fetch_hit, key, options) { |payload| } entry end end else read(key, options) end end
# File lib/active_support/cache/libmemcached_store.rb, line 154 def increment(key, amount = 1, options = nil) key = normalize_key(key) instrument(:increment, key, amount: amount) do @cache.incr(key, amount) end rescue Memcached::NotFound nil rescue Memcached::Error => e log_error(e) nil end
Silence the logger within a block.
# File lib/active_support/cache/libmemcached_store.rb, line 60 def mute previous_silence, @silence = defined?(@silence) && @silence, true yield ensure @silence = previous_silence end
# File lib/active_support/cache/libmemcached_store.rb, line 117 def read(key, options = nil) key = normalize_key(key) instrument(:read, key, options) do |payload| entry = read_entry(key, options) payload[:hit] = !!entry if payload entry end end
# File lib/active_support/cache/libmemcached_store.rb, line 178 def read_multi(*names) names.flatten! options = names.extract_options! return {} if names.empty? mapping = Hash[names.map {|name| [normalize_key(name), name] }] keys = mapping.keys raw_values, flags = instrument(:read_multi, keys, options) do @cache.get(keys, false, true) end values = {} raw_values.each do |key, value| values[mapping[key]] = convert_race_condition_entry(deserialize(value, options[:raw], flags[key])) end values rescue Memcached::Error => e log_error(e) {} end
Silence the logger.
# File lib/active_support/cache/libmemcached_store.rb, line 54 def silence! @silence = true self end
# File lib/active_support/cache/libmemcached_store.rb, line 206 def stats @cache.stats end
# File lib/active_support/cache/libmemcached_store.rb, line 126 def write(key, value, options = nil) key = normalize_key(key) instrument(:write, key, options) do |payload| write_entry(key, value, options) end end
Protected Instance Methods
# File lib/active_support/cache/libmemcached_store.rb, line 242 def delete_entry(key, options = nil) @cache.delete(key) true rescue Memcached::NotFound false rescue Memcached::Error => e log_error(e) false end
# File lib/active_support/cache/libmemcached_store.rb, line 212 def read_entry(key, options = nil) options ||= {} raw_value, flags = @cache.get(key, false, true) value = deserialize(raw_value, options[:raw], flags) convert_race_condition_entry(value, options) rescue Memcached::NotFound nil rescue Memcached::Error => e log_error(e) nil end
# File lib/active_support/cache/libmemcached_store.rb, line 224 def write_entry(key, entry, options = nil) options = options ? @options.merge(options) : @options method = options[:unless_exist] ? :add : :set entry = options[:raw] ? entry.to_s : Marshal.dump(entry) flags = 0 if options[:compress] && entry.bytesize >= options[:compress_threshold] entry = Zlib::Deflate.deflate(entry) flags |= FLAG_COMPRESSED end @cache.send(method, key, entry, options[:expires_in].to_i, false, flags) true rescue Memcached::Error => e log_error(e) false end
Private Instance Methods
# File lib/active_support/cache/libmemcached_store.rb, line 279 def convert_race_condition_entry(value, options={}) if !options[:preserve_race_condition_entry] && value.is_a?(FetchWithRaceConditionTTLEntry) value.value else value end end
# File lib/active_support/cache/libmemcached_store.rb, line 287 def deserialize(value, raw = false, flags = 0) value = Zlib::Inflate.inflate(value) if (flags & FLAG_COMPRESSED) != 0 raw ? value : Marshal.load(value) rescue TypeError, ArgumentError value end
# File lib/active_support/cache/libmemcached_store.rb, line 299 def escape_and_normalize(key) key = key.to_s.dup.force_encoding("BINARY").gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" } key_length = key.length return key if @namespace_length + key_length <= 250 max_key_length = 213 - @namespace_length "#{key[0, max_key_length]}:md5:#{Digest::MD5.hexdigest(key)}" end
# File lib/active_support/cache/libmemcached_store.rb, line 254 def fetch_with_race_condition_ttl(key, options={}, &block) options = options.dup race_ttl = options.delete(:race_condition_ttl) || raise("Use :race_condition_ttl option or normal fetch") expires_in = options.fetch(:expires_in) options[:expires_in] = expires_in + race_ttl options[:preserve_race_condition_entry] = true value = fetch(key, options) { FetchWithRaceConditionTTLEntry.new(yield, expires_in) } return value unless value.is_a?(FetchWithRaceConditionTTLEntry) if value.expired? && !value.extended # we take care of refreshing the cache, all others should keep reading value.extended = true write(key, value, options.merge(:expires_in => value.expires_in + race_ttl)) # calculate new value and store it value = FetchWithRaceConditionTTLEntry.new(yield, expires_in) write(key, value, options) end value.value end
# File lib/active_support/cache/libmemcached_store.rb, line 326 def instrument(operation, key, options=nil) log(operation, key, options) if instrument? payload = { :key => key } payload.merge!(options) if options.is_a?(Hash) ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) } else yield(nil) end end
# File lib/active_support/cache/libmemcached_store.rb, line 338 def instrument? ActiveSupport::VERSION::MAJOR == 3 ? ActiveSupport::Cache::Store.instrument : true end
# File lib/active_support/cache/libmemcached_store.rb, line 342 def log(operation, key, options=nil) return unless !silence? && logger && logger.debug? logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}") end
# File lib/active_support/cache/libmemcached_store.rb, line 347 def log_error(exception) return unless !silence? && logger && logger.error? logger.error "MemcachedError (#{exception.inspect}): #{exception.message}" end
# File lib/active_support/cache/libmemcached_store.rb, line 352 def logger Rails.logger end
TODO: support namespace or other things passed as option
# File lib/active_support/cache/libmemcached_store.rb, line 295 def normalize_key(key, _options = nil) escape_and_normalize(expanded_key(key)) end