-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This will improve the performance since we are going to hit the backend just once to read all keys, as long the cache adapter implements fetch multi support like dalli. For example: builder.cache! :x do |cache| cache.x true end builder.cache! :y do |cache| cache.y true end builder.cache! :z do |cache| cache.z true end This example was hitting the memcached 6 times on cache miss: 1. read x 2. write x 3. read y 4. write y 5. read z 6. write z And 3 times on cache hit: 1. read x 2. read y 3. read z After this change, 4 times on cache miss: 1. read multi x,y,z 2. write x 3. write y 4. write z And 1 time on cache hit: 1. read multi x,y,z Note that in the case of different options, one read multi will be made per each options, i.e.: builder.cache! :x do |cache| cache.x true end builder.cache! :y do |cache| cache.y true end builder.cache! :z, expires_in: 10.minutes do |cache| cache.z true end builder.cache! :w, expires_in: 10.minutes do |cache| cache.w true end In the case of cache miss: 1. read multi x,y 2. write x 3. write y 4. read multi z,w 5. write z 5. write w In the case of cache hit: 1. read multi x,y 2. read multi z,w That's because Rails.cache.fetch_multi signature is limited to use the same options for all given keys. And for last, nested cache calls are allowed and will follow recursively to accomplish the same behavior, i.e.: builder.cache! :x do |cache_x| cache_x.x true cache_x.cache! :y do |cache_y| cache_y.y true end cache_x.cache! :z do |cache_z| cache_z.z true end end builder.cache! :w do |cache_w| cache_w.w true end In the case of cache miss: 1. read multi x,w 2. read multi y,z 3. write y 4. write z 5. write x 6. write w In the case of cache hit: 1. read multi x,w The same rule of options will be applied, if you have different options, one hit per options. Pretty much the same of rails/jbuilder#421.
- Loading branch information
Showing
6 changed files
with
240 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
class AbstractBuilder | ||
class LazyCache | ||
def initialize(driver) | ||
@cache = Hash.new { |h, k| h[k] = {} } | ||
@driver = driver | ||
end | ||
|
||
def add(key, options, &block) | ||
cache[options][key] = block | ||
end | ||
|
||
def resolve | ||
resolved = [] | ||
|
||
# Fail-fast if there is no items to be computed. | ||
return resolved if cache.empty? | ||
|
||
# We can't add new items during interation, so iterate through a clone | ||
# that will allow us to add new items. | ||
previous = cache.clone | ||
cache.clear | ||
|
||
# Keys are grouped by options and because of that, fetch_multi will use | ||
# the same options for the same group of keys. | ||
previous.each do |options, group| | ||
result = driver.fetch_multi(*group.keys, options) do |group_key| | ||
[group[group_key].call, *resolve] | ||
end | ||
|
||
# Since the fetch_multi returns { cache_key => value }, we need to | ||
# discard the cache key and merge only the values. | ||
resolved.concat result.values.flatten(1) | ||
end | ||
|
||
resolved | ||
end | ||
|
||
private | ||
|
||
attr_reader :cache, :driver | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
RSpec.describe AbstractBuilder::LazyCache do | ||
it "resolves all entries at once using fetch multi" do | ||
driver = NaiveCache.new | ||
|
||
expect(driver).to receive(:fetch_multi).with("x", "y", "z", {}).and_call_original.exactly(3).times | ||
|
||
3.times do | ||
lazy_cache = described_class.new(driver) | ||
lazy_cache.add("x", {}) { { x: true } } | ||
lazy_cache.add("y", {}) { { y: true } } | ||
lazy_cache.add("z", {}) { { z: true } } | ||
|
||
expect(lazy_cache.resolve).to eq [ | ||
{ x: true }, | ||
{ y: true }, | ||
{ z: true } | ||
] | ||
end | ||
end | ||
|
||
it "resolves all entries at once per options using fetch multi" do | ||
driver = NaiveCache.new | ||
|
||
expect(driver).to receive(:fetch_multi).with("x", { option: false }).and_call_original.exactly(3).times | ||
expect(driver).to receive(:fetch_multi).with("y", "z", { option: true }).and_call_original.exactly(3).times | ||
|
||
3.times do | ||
lazy_cache = described_class.new(driver) | ||
lazy_cache.add("x", { option: false }) { { x: true } } | ||
lazy_cache.add("y", { option: true }) { { y: true } } | ||
lazy_cache.add("z", { option: true }) { { z: true } } | ||
|
||
expect(lazy_cache.resolve).to eq [ | ||
{ x: true }, | ||
{ y: true }, | ||
{ z: true } | ||
] | ||
end | ||
end | ||
|
||
it 'resolves nested entries at once using fetch multi' do | ||
driver = NaiveCache.new | ||
|
||
expect(driver).to receive(:fetch_multi).with("x", {}).and_call_original.exactly(3).times | ||
expect(driver).to receive(:fetch_multi).with("y", "z", { option: false }).and_call_original.exactly(1).times | ||
expect(driver).to receive(:fetch_multi).with("w", { option: true }).and_call_original.exactly(1).times | ||
|
||
3.times do | ||
lazy_cache = described_class.new(driver) | ||
|
||
lazy_cache.add("x", {}) do | ||
lazy_cache.add("y", { option: false }) do | ||
{ y: true } | ||
end | ||
|
||
lazy_cache.add("z", { option: false }) do | ||
{ z: true } | ||
end | ||
|
||
lazy_cache.add("w", { option: true }) do | ||
{ w: true } | ||
end | ||
|
||
{ x: true } | ||
end | ||
|
||
expect(lazy_cache.resolve).to eq [ | ||
{ x: true }, | ||
{ y: true }, | ||
{ z: true }, | ||
{ w: true } | ||
] | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.