aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
authorJeremy Daer <jeremydaer@gmail.com>2017-06-06 16:39:20 -0700
committerGitHub <noreply@github.com>2017-06-06 16:39:20 -0700
commit2b96d5822bfe407be7589e293f3265c0c7a6726c (patch)
treef50c2eef76a67707b10be6ce14e856ab95c19cfc /activesupport
parent0d55b5bb5dfdcf65d1e9a10b567579e3d339c2c9 (diff)
downloadrails-2b96d5822bfe407be7589e293f3265c0c7a6726c.tar.gz
rails-2b96d5822bfe407be7589e293f3265c0c7a6726c.tar.bz2
rails-2b96d5822bfe407be7589e293f3265c0c7a6726c.zip
Cache: write_multi (#29366)
Rails.cache.write_multi foo: 'bar', baz: 'qux' Plus faster `fetch_multi` with stores that implement `write_multi_entries`. Keys that aren't found may be written to the cache store in one shot instead of separate writes. The default implementation simply calls `write_entry` for each entry. Stores may override if they're capable of one-shot bulk writes, like Redis `MSET`.
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md14
-rw-r--r--activesupport/lib/active_support/cache.rb34
-rw-r--r--activesupport/test/caching_test.rb58
3 files changed, 100 insertions, 6 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index fc1e5516f8..d1d61ac8d7 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,17 @@
+* Cache: `write_multi`
+
+ Rails.cache.write_multi foo: 'bar', baz: 'qux'
+
+ Plus faster fetch_multi with stores that implement `write_multi_entries`.
+ Keys that aren't found may be written to the cache store in one shot
+ instead of separate writes.
+
+ The default implementation simply calls `write_entry` for each entry.
+ Stores may override if they're capable of one-shot bulk writes, like
+ Redis `MSET`.
+
+ *Jeremy Daer*
+
* Add default option to module and class attribute accessors.
mattr_accessor :settings, default: {}
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index a1093a2e23..fa487060e2 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -373,6 +373,19 @@ module ActiveSupport
results
end
+ # Cache Storage API to write multiple values at once.
+ def write_multi(hash, options = nil)
+ options = merged_options(options)
+
+ instrument :write_multi, hash, options do |payload|
+ entries = hash.each_with_object({}) do |(name, value), memo|
+ memo[normalize_key(name, options)] = Entry.new(value, options.merge(version: normalize_version(name, options)))
+ end
+
+ write_multi_entries entries, options
+ end
+ end
+
# Fetches data from the cache, using the given keys. If there is data in
# the cache with the given keys, then that data is returned. Otherwise,
# the supplied block is called for each key for which there was no data,
@@ -397,14 +410,15 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- results = read_multi(*names, options)
- names.each_with_object({}) do |name, memo|
- memo[name] = results.fetch(name) do
- value = yield name
- write(name, value, options)
- value
+ read_multi(*names, options).tap do |results|
+ writes = {}
+
+ (names - results.keys).each do |name|
+ results[name] = writes[name] = yield(name)
end
+
+ write_multi writes, options
end
end
@@ -521,6 +535,14 @@ module ActiveSupport
raise NotImplementedError.new
end
+ # Writes multiple entries to the cache implementation. Subclasses MAY
+ # implement this method.
+ def write_multi_entries(hash, options)
+ hash.each do |key, entry|
+ write_entry key, entry, options
+ end
+ end
+
# Deletes an entry from the cache implementation. Subclasses must
# implement this method.
def delete_entry(key, options)
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index f53b98c73e..f2f8c58111 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -1299,3 +1299,61 @@ class CacheEntryTest < ActiveSupport::TestCase
assert_equal value.bytesize, entry.size
end
end
+
+class CacheStoreWriteMultiEntriesStoreProviderInterfaceTest < ActiveSupport::TestCase
+ setup do
+ @cache = ActiveSupport::Cache.lookup_store(:null_store)
+ end
+
+ test "fetch_multi uses write_multi_entries store provider interface" do
+ assert_called_with(@cache, :write_multi_entries) do
+ @cache.fetch_multi "a", "b", "c" do |key|
+ key * 2
+ end
+ end
+ end
+end
+
+class CacheStoreWriteMultiInstrumentationTest < ActiveSupport::TestCase
+ setup do
+ @cache = ActiveSupport::Cache.lookup_store(:null_store)
+ end
+
+ test "instrumentation" do
+ writes = { "a" => "aa", "b" => "bb" }
+
+ events = with_instrumentation "write_multi" do
+ @cache.write_multi(writes)
+ end
+
+ assert_equal %w[ cache_write_multi.active_support ], events.map(&:name)
+ assert_nil events[0].payload[:super_operation]
+ assert_equal({ "a" => "aa", "b" => "bb" }, events[0].payload[:key])
+ end
+
+ test "instrumentation with fetch_multi as super operation" do
+ skip "fetch_multi isn't instrumented yet"
+
+ events = with_instrumentation "write_multi" do
+ @cache.fetch_multi("a", "b") { |key| key * 2 }
+ end
+
+ assert_equal %w[ cache_write_multi.active_support ], events.map(&:name)
+ assert_nil events[0].payload[:super_operation]
+ assert !events[0].payload[:hit]
+ end
+
+ private
+ def with_instrumentation(method)
+ event_name = "cache_#{method}.active_support"
+
+ [].tap do |events|
+ ActiveSupport::Notifications.subscribe event_name do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+ yield
+ end
+ ensure
+ ActiveSupport::Notifications.unsubscribe event_name
+ end
+end