Skip to content

Commit f32c013

Browse files
committed
Cache get_all separately
Doing a cache write for hundreds of uncached features doesn't work so well. Wish there was a bulk write. I could use write_multi but only redis cache seems to support that.
1 parent 59c1e50 commit f32c013

File tree

6 files changed

+71
-29
lines changed

6 files changed

+71
-29
lines changed

lib/flipper/adapters/active_support_cache_store.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def enable(feature, gate, thing)
4141
if @write_through
4242
result = @adapter.enable(feature, gate, thing)
4343
cache_write feature_cache_key(feature.key), @adapter.get(feature)
44+
expire_get_all_cache
4445
result
4546
else
4647
super
@@ -51,6 +52,7 @@ def disable(feature, gate, thing)
5152
if @write_through
5253
result = @adapter.disable(feature, gate, thing)
5354
cache_write feature_cache_key(feature.key), @adapter.get(feature)
55+
expire_get_all_cache
5456
result
5557
else
5658
super

lib/flipper/adapters/cache_base.rb

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ class CacheBase
1717
# Public: The cache key where the set of known features is cached.
1818
attr_reader :features_cache_key
1919

20+
# Public: The cache key where the set of all features with gates is cached.
21+
attr_reader :get_all_cache_key
22+
2023
# Public: Alias expires_in to ttl for compatibility.
2124
alias_method :expires_in, :ttl
2225

@@ -29,16 +32,24 @@ def initialize(adapter, cache, ttl = 300, prefix: nil)
2932
@namespace = "flipper/#{@cache_version}"
3033
@namespace = @namespace.prepend(prefix) if prefix
3134
@features_cache_key = "#{@namespace}/features"
35+
@get_all_cache_key = "#{@namespace}/get_all"
36+
end
37+
38+
# Public: Expire the cache for the set of all features with gates.
39+
def expire_get_all_cache
40+
cache_delete @get_all_cache_key
3241
end
3342

3443
# Public: Expire the cache for the set of known feature names.
3544
def expire_features_cache
3645
cache_delete @features_cache_key
46+
expire_get_all_cache
3747
end
3848

3949
# Public: Expire the cache for a given feature.
4050
def expire_feature_cache(key)
4151
cache_delete feature_cache_key(key)
52+
expire_get_all_cache
4253
end
4354

4455
# Public
@@ -80,8 +91,11 @@ def get_multi(features)
8091

8192
# Public
8293
def get_all(**kwargs)
83-
features = read_feature_keys.map { |key| Flipper::Feature.new(key, self) }
84-
read_many_features(features)
94+
cache_fetch(@get_all_cache_key) {
95+
result = read_all_features(**kwargs)
96+
cache_write @features_cache_key, result.keys.to_set
97+
result
98+
}
8599
end
86100

87101
# Public
@@ -107,6 +121,10 @@ def feature_cache_key(key)
107121

108122
private
109123

124+
def read_all_features(**kwargs)
125+
@adapter.get_all(**kwargs)
126+
end
127+
110128
# Private: Returns the Set of known feature keys.
111129
def read_feature_keys
112130
cache_fetch(@features_cache_key) { @adapter.features }

spec/flipper/adapters/active_support_cache_store_spec.rb

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,27 @@
6161
adapter.features
6262
expect(cache.read("flipper/v1/features")).not_to be(nil)
6363

64+
adapter.get_all
65+
expect(cache.read("flipper/v1/get_all")).not_to be(nil)
66+
6467
# expire cache
6568
adapter.expire_features_cache
6669
expect(cache.read("flipper/v1/features")).to be(nil)
70+
expect(cache.read("flipper/v1/get_all")).to be(nil)
6771
end
6872

6973
it "can expire feature cache" do
7074
# cache the features
7175
adapter.get(flipper[:stats])
7276
expect(cache.read("flipper/v1/feature/stats")).not_to be(nil)
7377

78+
adapter.get_all
79+
expect(cache.read("flipper/v1/get_all")).not_to be(nil)
80+
7481
# expire cache
7582
adapter.expire_feature_cache("stats")
7683
expect(cache.read("flipper/v1/feature/stats")).to be(nil)
84+
expect(cache.read("flipper/v1/get_all")).to be(nil)
7785
end
7886

7987
it "can generate feature cache key" do
@@ -88,6 +96,10 @@
8896
expect(adapter.features_cache_key).to eq("foo/flipper/v1/features")
8997
end
9098

99+
it "knows get_all_cache_key" do
100+
expect(adapter.get_all_cache_key).to eq("foo/flipper/v1/get_all")
101+
end
102+
91103
it "can generate feature cache key" do
92104
expect(adapter.feature_cache_key("stats")).to eq("foo/flipper/v1/feature/stats")
93105
end
@@ -109,9 +121,10 @@
109121
adapter.get_all
110122

111123
# verify cached with prefix
124+
get_all_cache_value = cache.read("foo/flipper/v1/get_all")
112125
expect(cache.read("foo/flipper/v1/features")).to eq(Set["stats", "search"])
113-
expect(cache.read("foo/flipper/v1/feature/search")[:percentage_of_actors]).to eq("10")
114-
expect(cache.read("foo/flipper/v1/feature/stats")[:boolean]).to eq("true")
126+
expect(get_all_cache_value["search"][:percentage_of_actors]).to eq("10")
127+
expect(get_all_cache_value["stats"][:boolean]).to eq("true")
115128
end
116129
end
117130

@@ -120,11 +133,13 @@
120133

121134
before do
122135
adapter.get(feature)
136+
adapter.get_all
123137
adapter.remove(feature)
124138
end
125139

126140
it 'expires feature and deletes the cache' do
127141
expect(cache.read("flipper/v1/feature/#{feature.key}")).to be_nil
142+
expect(cache.read("flipper/v1/get_all")).to be(nil)
128143
expect(cache.exist?("flipper/v1/feature/#{feature.key}")).to be(false)
129144
expect(feature).not_to be_enabled
130145
end
@@ -144,10 +159,13 @@
144159
let(:feature) { flipper[:stats] }
145160

146161
before do
162+
adapter.get(feature)
163+
adapter.get_all
147164
adapter.enable(feature, feature.gate(:boolean), Flipper::Types::Boolean.new(true))
148165
end
149166

150167
it 'enables feature and deletes the cache' do
168+
expect(cache.read("flipper/v1/get_all")).to be(nil)
151169
expect(cache.read("flipper/v1/feature/#{feature.key}")).to be_nil
152170
expect(cache.exist?("flipper/v1/feature/#{feature.key}")).to be(false)
153171
expect(feature).to be_enabled
@@ -157,6 +175,7 @@
157175
let(:write_through) { true }
158176

159177
it 'expires feature and writes to the cache' do
178+
expect(cache.read("flipper/v1/get_all")).to be(nil)
160179
expect(cache.exist?("flipper/v1/feature/#{feature.key}")).to be(true)
161180
expect(cache.read("flipper/v1/feature/#{feature.key}")).to include(boolean: 'true')
162181
expect(feature).to be_enabled
@@ -168,10 +187,13 @@
168187
let(:feature) { flipper[:stats] }
169188

170189
before do
190+
adapter.get(feature)
191+
adapter.get_all
171192
adapter.disable(feature, feature.gate(:boolean), Flipper::Types::Boolean.new)
172193
end
173194

174195
it 'disables feature and deletes the cache' do
196+
expect(cache.read("flipper/v1/get_all")).to be(nil)
175197
expect(cache.read("flipper/v1/feature/#{feature.key}")).to be_nil
176198
expect(cache.exist?("flipper/v1/feature/#{feature.key}")).to be(false)
177199
expect(feature).not_to be_enabled
@@ -224,8 +246,10 @@
224246

225247
it 'warms all features' do
226248
adapter.get_all
227-
expect(cache.read("flipper/v1/feature/#{stats.key}")[:boolean]).to eq('true')
228-
expect(cache.read("flipper/v1/feature/#{search.key}")[:boolean]).to be(nil)
249+
get_all_cache_value = cache.read("flipper/v1/get_all")
250+
expect(get_all_cache_value).not_to be(nil)
251+
expect(get_all_cache_value["stats"][:boolean]).to eq('true')
252+
expect(get_all_cache_value["search"][:boolean]).to be(nil)
229253
expect(cache.read("flipper/v1/features")).to eq(Set["stats", "search"])
230254
end
231255

@@ -236,9 +260,8 @@
236260
it 'only invokes two calls to wrapped adapter (for features set and gate data for each feature in set)' do
237261
memory_adapter.reset
238262
5.times { adapter.get_all }
239-
expect(memory_adapter.count(:features)).to eq(1)
240-
expect(memory_adapter.count(:get_multi)).to eq(1)
241-
expect(memory_adapter.count).to eq(2)
263+
expect(memory_adapter.count(:get_all)).to eq(1)
264+
expect(memory_adapter.count).to eq(1)
242265
end
243266
end
244267

spec/flipper/adapters/dalli_spec.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,11 @@
7979
flipper.enable(:stats)
8080

8181
# populate the cache
82-
adapter.get_all
82+
adapter.features
83+
adapter.get_multi([flipper[:stats], flipper[:search]])
8384

8485
# verify cached with prefix
86+
expect(cache.get("foo/flipper/v1/features")).to eq(Set["stats", "search"])
8587
expect(cache.get("foo/flipper/v1/feature/search")[:percentage_of_actors]).to eq("10")
8688
expect(cache.get("foo/flipper/v1/feature/stats")[:boolean]).to eq("true")
8789
end
@@ -132,21 +134,21 @@
132134

133135
it 'warms all features' do
134136
adapter.get_all
135-
expect(cache.get("flipper/v1/feature/#{stats.key}")[:boolean]).to eq('true')
136-
expect(cache.get("flipper/v1/feature/#{search.key}")[:boolean]).to be(nil)
137+
get_all_cache_value = cache.get("flipper/v1/get_all")
138+
expect(get_all_cache_value["stats"][:boolean]).to eq("true")
139+
expect(get_all_cache_value["search"][:boolean]).to be(nil)
137140
expect(cache.get("flipper/v1/features")).to eq(Set["stats", "search"])
138141
end
139142

140143
it 'returns same result when already cached' do
141144
expect(adapter.get_all).to eq(adapter.get_all)
142145
end
143146

144-
it 'only invokes two calls to wrapped adapter (for features set and gate data for each feature in set)' do
147+
it 'only invokes one call to wrapped adapter (for features set and gate data for each feature in set)' do
145148
memory_adapter.reset
146149
5.times { adapter.get_all }
147-
expect(memory_adapter.count(:features)).to eq(1)
148-
expect(memory_adapter.count(:get_multi)).to eq(1)
149-
expect(memory_adapter.count).to eq(2)
150+
expect(memory_adapter.count(:get_all)).to eq(1)
151+
expect(memory_adapter.count).to eq(1)
150152
end
151153
end
152154

spec/flipper/adapters/redis_cache_spec.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@
8080
flipper.enable(:stats)
8181

8282
# populate the cache
83-
adapter.get_all
83+
adapter.features
84+
adapter.get_multi([flipper[:stats], flipper[:search]])
8485

8586
# verify cached with prefix
8687
expect(Marshal.load(client.get("foo/flipper/v1/features"))).to eq(Set["stats", "search"])
@@ -145,21 +146,20 @@
145146

146147
it 'warms all features' do
147148
adapter.get_all
148-
expect(Marshal.load(client.get("flipper/v1/feature/#{stats.key}"))[:boolean]).to eq('true')
149-
expect(Marshal.load(client.get("flipper/v1/feature/#{search.key}"))[:boolean]).to be(nil)
149+
get_all_cache_value = client.get("flipper/v1/get_all")
150+
expect(Marshal.load(get_all_cache_value)["stats"][:boolean]).to eq("true")
150151
expect(Marshal.load(client.get("flipper/v1/features"))).to eq(Set["stats", "search"])
151152
end
152153

153154
it 'returns same result when already cached' do
154155
expect(adapter.get_all).to eq(adapter.get_all)
155156
end
156157

157-
it 'only invokes two calls to wrapped adapter (for features set and gate data for each feature in set)' do
158+
it 'only invokes one calls to wrapped adapter (for features set and gate data for each feature in set)' do
158159
memory_adapter.reset
159160
5.times { adapter.get_all }
160-
expect(memory_adapter.count(:features)).to eq(1)
161-
expect(memory_adapter.count(:get_multi)).to eq(1)
162-
expect(memory_adapter.count).to eq(2)
161+
expect(memory_adapter.count(:get_all)).to eq(1)
162+
expect(memory_adapter.count).to eq(1)
163163
end
164164
end
165165

spec/flipper/middleware/memoizer_spec.rb

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -470,18 +470,15 @@ def get(uri, params = {}, env = {}, &block)
470470

471471
get '/', {}, 'flipper' => flipper
472472
expect(logged_cached.count(:get_all)).to be(1)
473-
expect(logged_memory.count(:features)).to be(1)
474-
expect(logged_memory.count(:get_multi)).to be(1)
473+
expect(logged_memory.count(:get_all)).to be(1)
475474

476475
get '/', {}, 'flipper' => flipper
477476
expect(logged_cached.count(:get_all)).to be(2)
478-
expect(logged_memory.count(:features)).to be(1)
479-
expect(logged_memory.count(:get_multi)).to be(1)
477+
expect(logged_memory.count(:get_all)).to be(1)
480478

481479
get '/', {}, 'flipper' => flipper
482480
expect(logged_cached.count(:get_all)).to be(3)
483-
expect(logged_memory.count(:features)).to be(1)
484-
expect(logged_memory.count(:get_multi)).to be(1)
481+
expect(logged_memory.count(:get_all)).to be(1)
485482
end
486483
end
487484
end

0 commit comments

Comments
 (0)