aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/cache.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support/cache.rb')
-rw-r--r--activesupport/lib/active_support/cache.rb220
1 files changed, 135 insertions, 85 deletions
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 49d8965cb1..d769e2c8ea 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -1,21 +1,22 @@
# frozen_string_literal: true
require "zlib"
-require_relative "core_ext/array/extract_options"
-require_relative "core_ext/array/wrap"
-require_relative "core_ext/module/attribute_accessors"
-require_relative "core_ext/numeric/bytes"
-require_relative "core_ext/numeric/time"
-require_relative "core_ext/object/to_param"
-require_relative "core_ext/string/inflections"
+require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/array/wrap"
+require "active_support/core_ext/module/attribute_accessors"
+require "active_support/core_ext/numeric/bytes"
+require "active_support/core_ext/numeric/time"
+require "active_support/core_ext/object/to_param"
+require "active_support/core_ext/string/inflections"
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
module Cache
- autoload :FileStore, "active_support/cache/file_store"
- autoload :MemoryStore, "active_support/cache/memory_store"
- autoload :MemCacheStore, "active_support/cache/mem_cache_store"
- autoload :NullStore, "active_support/cache/null_store"
+ autoload :FileStore, "active_support/cache/file_store"
+ autoload :MemoryStore, "active_support/cache/memory_store"
+ autoload :MemCacheStore, "active_support/cache/mem_cache_store"
+ autoload :NullStore, "active_support/cache/null_store"
+ autoload :RedisCacheStore, "active_support/cache/redis_cache_store"
# These options mean something to all cache implementations. Individual cache
# implementations may support additional options.
@@ -148,18 +149,34 @@ module ActiveSupport
# cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
#
- # Caches can also store values in a compressed format to save space and
- # reduce time spent sending data. Since there is overhead, values must be
- # large enough to warrant compression. To turn on compression either pass
- # <tt>compress: true</tt> in the initializer or as an option to +fetch+
- # or +write+. To specify the threshold at which to compress values, set the
- # <tt>:compress_threshold</tt> option. The default threshold is 16K.
+ # Cached data larger than 1kB are compressed by default. To turn off
+ # compression, pass <tt>compress: false</tt> to the initializer or to
+ # individual +fetch+ or +write+ method calls. The 1kB compression
+ # threshold is configurable with the <tt>:compress_threshold</tt> option,
+ # specified in bytes.
class Store
cattr_accessor :logger, instance_writer: true
attr_reader :silence, :options
alias :silence? :silence
+ class << self
+ private
+ def retrieve_pool_options(options)
+ {}.tap do |pool_options|
+ pool_options[:size] = options.delete(:pool_size) if options[:pool_size]
+ pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout]
+ end
+ end
+
+ def ensure_connection_pool_added!
+ require "connection_pool"
+ rescue LoadError => e
+ $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install"
+ raise e
+ end
+ end
+
# Creates a new cache. The options will be passed to any write method calls
# except for <tt>:namespace</tt> which can be used to set the global
# namespace for the cache.
@@ -212,8 +229,7 @@ module ActiveSupport
# ask whether you should force a cache write. Otherwise, it's clearer to
# just call <tt>Cache#write</tt>.
#
- # Setting <tt>:compress</tt> will store a large cache entry set by the call
- # in a compressed format.
+ # Setting <tt>compress: false</tt> disables compression of the cache entry.
#
# Setting <tt>:expires_in</tt> will set an expiration time on the cache.
# All caches support auto-expiring content after a specified number of
@@ -317,8 +333,9 @@ module ActiveSupport
# the cache with the given key, then that data is returned. Otherwise,
# +nil+ is returned.
#
- # Note, if data was written with the <tt>:expires_in<tt> or <tt>:version</tt> options,
- # both of these conditions are applied before the data is returned.
+ # Note, if data was written with the <tt>:expires_in</tt> or
+ # <tt>:version</tt> options, both of these conditions are applied before
+ # the data is returned.
#
# Options are passed to the underlying cache implementation.
def read(name, options = nil)
@@ -358,23 +375,11 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- results = {}
- names.each do |name|
- key = normalize_key(name, options)
- version = normalize_version(name, options)
- entry = read_entry(key, options)
-
- if entry
- if entry.expired?
- delete_entry(key, options)
- elsif entry.mismatched?(version)
- # Skip mismatched versions
- else
- results[name] = entry.value
- end
+ instrument :read_multi, names, options do |payload|
+ read_multi_entries(names, options).tap do |results|
+ payload[:hits] = results.keys
end
end
- results
end
# Cache Storage API to write multiple values at once.
@@ -415,14 +420,19 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- read_multi(*names, options).tap do |results|
- writes = {}
+ instrument :read_multi, names, options do |payload|
+ read_multi_entries(names, options).tap do |results|
+ payload[:hits] = results.keys
+ payload[:super_operation] = :fetch_multi
- (names - results.keys).each do |name|
- results[name] = writes[name] = yield(name)
- end
+ writes = {}
+
+ (names - results.keys).each do |name|
+ results[name] = writes[name] = yield(name)
+ end
- write_multi writes, options
+ write_multi writes, options
+ end
end
end
@@ -539,6 +549,28 @@ module ActiveSupport
raise NotImplementedError.new
end
+ # Reads multiple entries from the cache implementation. Subclasses MAY
+ # implement this method.
+ def read_multi_entries(names, options)
+ results = {}
+ names.each do |name|
+ key = normalize_key(name, options)
+ version = normalize_version(name, options)
+ entry = read_entry(key, options)
+
+ if entry
+ if entry.expired?
+ delete_entry(key, options)
+ elsif entry.mismatched?(version)
+ # Skip mismatched versions
+ else
+ results[name] = entry.value
+ end
+ end
+ end
+ results
+ end
+
# Writes multiple entries to the cache implementation. Subclasses MAY
# implement this method.
def write_multi_entries(hash, options)
@@ -562,14 +594,34 @@ module ActiveSupport
end
end
- # Prefixes a key with the namespace. Namespace and key will be delimited
- # with a colon.
- def normalize_key(key, options)
- key = expanded_key(key)
- namespace = options[:namespace] if options
- prefix = namespace.is_a?(Proc) ? namespace.call : namespace
- key = "#{prefix}:#{key}" if prefix
- key
+ # Expands and namespaces the cache key. May be overridden by
+ # cache stores to do additional normalization.
+ def normalize_key(key, options = nil)
+ namespace_key expanded_key(key), options
+ end
+
+ # Prefix the key with a namespace string:
+ #
+ # namespace_key 'foo', namespace: 'cache'
+ # # => 'cache:foo'
+ #
+ # With a namespace block:
+ #
+ # namespace_key 'foo', namespace: -> { 'cache' }
+ # # => 'cache:foo'
+ def namespace_key(key, options = nil)
+ options = merged_options(options)
+ namespace = options[:namespace]
+
+ if namespace.respond_to?(:call)
+ namespace = namespace.call
+ end
+
+ if namespace
+ "#{namespace}:#{key}"
+ else
+ key
+ end
end
# Expands key to be a consistent string value. Invokes +cache_key+ if
@@ -658,22 +710,17 @@ module ActiveSupport
class Entry # :nodoc:
attr_reader :version
- DEFAULT_COMPRESS_LIMIT = 16.kilobytes
+ DEFAULT_COMPRESS_LIMIT = 1.kilobyte
# Creates a new cache entry for the specified value. Options supported are
- # +:compress+, +:compress_threshold+, and +:expires_in+.
- def initialize(value, options = {})
- if should_compress?(value, options)
- @value = compress(value)
- @compressed = true
- else
- @value = value
- end
-
- @version = options[:version]
+ # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
+ def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **)
+ @value = value
+ @version = version
@created_at = Time.now.to_f
- @expires_in = options[:expires_in]
- @expires_in = @expires_in.to_f if @expires_in
+ @expires_in = expires_in && expires_in.to_f
+
+ compress!(compress_threshold) if compress
end
def value
@@ -705,17 +752,13 @@ module ActiveSupport
# Returns the size of the cached value. This could be less than
# <tt>value.size</tt> if the data is compressed.
def size
- if defined?(@s)
- @s
+ case value
+ when NilClass
+ 0
+ when String
+ @value.bytesize
else
- case value
- when NilClass
- 0
- when String
- @value.bytesize
- else
- @s = Marshal.dump(@value).bytesize
- end
+ @s ||= Marshal.dump(@value).bytesize
end
end
@@ -732,23 +775,30 @@ module ActiveSupport
end
private
- def should_compress?(value, options)
- if value && options[:compress]
- compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
- serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize
-
- return true if serialized_value_size >= compress_threshold
+ def compress!(compress_threshold)
+ case @value
+ when nil, true, false, Numeric
+ uncompressed_size = 0
+ when String
+ uncompressed_size = @value.bytesize
+ else
+ serialized = Marshal.dump(@value)
+ uncompressed_size = serialized.bytesize
end
- false
- end
+ if uncompressed_size >= compress_threshold
+ serialized ||= Marshal.dump(@value)
+ compressed = Zlib::Deflate.deflate(serialized)
- def compressed?
- defined?(@compressed) ? @compressed : false
+ if compressed.bytesize < uncompressed_size
+ @value = compressed
+ @compressed = true
+ end
+ end
end
- def compress(value)
- Zlib::Deflate.deflate(Marshal.dump(value))
+ def compressed?
+ defined?(@compressed)
end
def uncompress(value)