diff options
author | Douglas Barbosa Alexandre <dbalexandre@gmail.com> | 2019-07-04 00:38:39 +0000 |
---|---|---|
committer | Douglas Barbosa Alexandre <dbalexandre@gmail.com> | 2019-07-04 00:38:39 +0000 |
commit | c55bbef97534cabecd6427b7685ec0a7cc52ddd6 (patch) | |
tree | c42997f49fefa51445438c3161a5cd0e65ff989b | |
parent | aef24e9f8db8f163fd069fac416b1e0cf3909647 (diff) | |
parent | 2db7c5762b41acbcbd71bc4bd5a8aa3d90e1a383 (diff) | |
download | gitlab-ce-c55bbef97534cabecd6427b7685ec0a7cc52ddd6.tar.gz |
Merge branch 'sh-cache-flipper-checks-in-memory' into 'master'
Cache Flipper feature flags in L1 and L2 caches
See merge request gitlab-org/gitlab-ce!30276
-rw-r--r-- | changelogs/unreleased/sh-cache-flipper-checks-in-memory.yml | 5 | ||||
-rw-r--r-- | lib/feature.rb | 23 | ||||
-rw-r--r-- | spec/lib/feature_spec.rb | 62 |
3 files changed, 87 insertions, 3 deletions
diff --git a/changelogs/unreleased/sh-cache-flipper-checks-in-memory.yml b/changelogs/unreleased/sh-cache-flipper-checks-in-memory.yml new file mode 100644 index 00000000000..125b6244d80 --- /dev/null +++ b/changelogs/unreleased/sh-cache-flipper-checks-in-memory.yml @@ -0,0 +1,5 @@ +--- +title: Cache Flipper feature flags in L1 and L2 caches +merge_request: 30276 +author: +type: performance diff --git a/lib/feature.rb b/lib/feature.rb index 22420e95ea2..e28333aa58e 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -103,10 +103,27 @@ class Feature feature_class: FlipperFeature, gate_class: FlipperGate) + # Redis L2 cache + redis_cache_adapter = + Flipper::Adapters::ActiveSupportCacheStore.new( + active_record_adapter, + l2_cache_backend, + expires_in: 1.hour) + + # Thread-local L1 cache: use a short timeout since we don't have a + # way to expire this cache all at once Flipper::Adapters::ActiveSupportCacheStore.new( - active_record_adapter, - Rails.cache, - expires_in: 1.hour) + redis_cache_adapter, + l1_cache_backend, + expires_in: 1.minute) + end + + def l1_cache_backend + Gitlab::ThreadMemoryCache.cache_backend + end + + def l2_cache_backend + Rails.cache end end diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb index 403e0785d1b..127463a57e8 100644 --- a/spec/lib/feature_spec.rb +++ b/spec/lib/feature_spec.rb @@ -144,6 +144,68 @@ describe Feature do expect(described_class.enabled?(:enabled_feature_flag)).to be_truthy end + it { expect(described_class.l1_cache_backend).to eq(Gitlab::ThreadMemoryCache.cache_backend) } + it { expect(described_class.l2_cache_backend).to eq(Rails.cache) } + + it 'caches the status in L1 and L2 caches', + :request_store, :use_clean_rails_memory_store_caching do + described_class.enable(:enabled_feature_flag) + flipper_key = "flipper/v1/feature/enabled_feature_flag" + + expect(described_class.l2_cache_backend) + .to receive(:fetch) + .once + .with(flipper_key, expires_in: 1.hour) + .and_call_original + + expect(described_class.l1_cache_backend) + .to receive(:fetch) + .once + .with(flipper_key, expires_in: 1.minute) + .and_call_original + + 2.times do + expect(described_class.enabled?(:enabled_feature_flag)).to be_truthy + end + end + + context 'cached feature flag', :request_store do + let(:flag) { :some_feature_flag } + + before do + described_class.flipper.memoize = false + described_class.enabled?(flag) + end + + it 'caches the status in L1 cache for the first minute' do + expect do + expect(described_class.l1_cache_backend).to receive(:fetch).once.and_call_original + expect(described_class.l2_cache_backend).not_to receive(:fetch) + expect(described_class.enabled?(flag)).to be_truthy + end.not_to exceed_query_limit(0) + end + + it 'caches the status in L2 cache after 2 minutes' do + Timecop.travel 2.minutes do + expect do + expect(described_class.l1_cache_backend).to receive(:fetch).once.and_call_original + expect(described_class.l2_cache_backend).to receive(:fetch).once.and_call_original + expect(described_class.enabled?(flag)).to be_truthy + end.not_to exceed_query_limit(0) + end + end + + it 'fetches the status after an hour' do + Timecop.travel 61.minutes do + expect do + expect(described_class.l1_cache_backend).to receive(:fetch).once.and_call_original + expect(described_class.l2_cache_backend).to receive(:fetch).once.and_call_original + expect(described_class.enabled?(flag)).to be_truthy + end.not_to exceed_query_limit(1) + end + end + end + context 'with an individual actor' do CustomActor = Struct.new(:flipper_id) |