Commit 5cd29114 authored by Eugie Limpin's avatar Eugie Limpin Committed by Bob Van Landuyt

Add Gitlab.revision to Gitlab::JsonCache cache_key by default

Update Gitlab::JsonCache to add Gitlab.revision to the cache_key by
default instead of using Gitlab::VERSION and Rails.version. This change
will result to broadcast messages (and anything that uses JsonCache
going forward) cache invalidation on every deploy which should prevent
the stale data issue encountered in
https://gitlab.com/gitlab-com/gl-infra/production/-/issues/6372.

For backwards compatibility, Gitlab::JsonCache accepts
{ cache_key_strategy: :version } (adds Gitlab::VERSION and
Rails.version to the cache_key) which will invalidate the cache on every
bump of Gitlab::VERSION (every release, once a month) and Rails.version.

Changelog: changed
parent ab1491be
...@@ -53,7 +53,7 @@ class BroadcastMessage < ApplicationRecord ...@@ -53,7 +53,7 @@ class BroadcastMessage < ApplicationRecord
def cache def cache
::Gitlab::SafeRequestStore.fetch(:broadcast_message_json_cache) do ::Gitlab::SafeRequestStore.fetch(:broadcast_message_json_cache) do
Gitlab::JsonCache.new(cache_key_with_version: false) Gitlab::JsonCache.new
end end
end end
......
...@@ -315,7 +315,7 @@ class License < ApplicationRecord ...@@ -315,7 +315,7 @@ class License < ApplicationRecord
def cache def cache
Gitlab::SafeRequestStore[:license_cache] ||= Gitlab::SafeRequestStore[:license_cache] ||=
Gitlab::JsonCache.new(namespace: :ee, backend: ::Gitlab::ProcessMemoryCache.cache_backend) Gitlab::JsonCache.new(namespace: :ee, backend: ::Gitlab::ProcessMemoryCache.cache_backend, cache_key_strategy: :version)
end end
def all_plans def all_plans
......
...@@ -115,11 +115,11 @@ module Gitlab ...@@ -115,11 +115,11 @@ module Gitlab
def self.l1_cache def self.l1_cache
SafeRequestStore[:geo_l1_cache] ||= SafeRequestStore[:geo_l1_cache] ||=
Gitlab::JsonCache.new(namespace: :geo, backend: ::Gitlab::ProcessMemoryCache.cache_backend) Gitlab::JsonCache.new(namespace: :geo, backend: ::Gitlab::ProcessMemoryCache.cache_backend, cache_key_strategy: :version)
end end
def self.l2_cache def self.l2_cache
SafeRequestStore[:geo_l2_cache] ||= Gitlab::JsonCache.new(namespace: :geo) SafeRequestStore[:geo_l2_cache] ||= Gitlab::JsonCache.new(namespace: :geo, cache_key_strategy: :version)
end end
def self.cache_value(raw_key, as: nil, &block) def self.cache_value(raw_key, as: nil, &block)
......
...@@ -2,12 +2,17 @@ ...@@ -2,12 +2,17 @@
module Gitlab module Gitlab
class JsonCache class JsonCache
attr_reader :backend, :cache_key_with_version, :namespace attr_reader :backend, :namespace
STRATEGY_KEY_COMPONENTS = {
revision: Gitlab.revision,
version: [Gitlab::VERSION, Rails.version]
}.freeze
def initialize(options = {}) def initialize(options = {})
@backend = options.fetch(:backend, Rails.cache) @backend = options.fetch(:backend, Rails.cache)
@namespace = options.fetch(:namespace, nil) @namespace = options.fetch(:namespace, nil)
@cache_key_with_version = options.fetch(:cache_key_with_version, true) @cache_key_strategy = options.fetch(:cache_key_strategy, :revision)
end end
def active? def active?
...@@ -19,13 +24,12 @@ module Gitlab ...@@ -19,13 +24,12 @@ module Gitlab
end end
def cache_key(key) def cache_key(key)
expanded_cache_key = [namespace, key].compact expanded_cache_key = [namespace, key, *strategy_key_component].compact
expanded_cache_key.join(':').freeze
if cache_key_with_version end
expanded_cache_key << [Gitlab::VERSION, Rails.version]
end
expanded_cache_key.flatten.join(':').freeze def strategy_key_component
STRATEGY_KEY_COMPONENTS.fetch(@cache_key_strategy)
end end
def expire(key) def expire(key)
......
...@@ -8,7 +8,7 @@ RSpec.describe Gitlab::JsonCache do ...@@ -8,7 +8,7 @@ RSpec.describe Gitlab::JsonCache do
let(:backend) { double('backend').as_null_object } let(:backend) { double('backend').as_null_object }
let(:namespace) { 'geo' } let(:namespace) { 'geo' }
let(:key) { 'foo' } let(:key) { 'foo' }
let(:expanded_key) { "#{namespace}:#{key}:#{Gitlab::VERSION}:#{Rails.version}" } let(:expanded_key) { "#{namespace}:#{key}:#{Gitlab.revision}" }
subject(:cache) { described_class.new(namespace: namespace, backend: backend) } subject(:cache) { described_class.new(namespace: namespace, backend: backend) }
...@@ -35,69 +35,63 @@ RSpec.describe Gitlab::JsonCache do ...@@ -35,69 +35,63 @@ RSpec.describe Gitlab::JsonCache do
end end
describe '#cache_key' do describe '#cache_key' do
context 'when namespace is not defined' do using RSpec::Parameterized::TableSyntax
context 'when cache_key_with_version is true' do
it 'expands out the key with GitLab, and Rails versions' do
cache = described_class.new(cache_key_with_version: true)
cache_key = cache.cache_key(key) where(:namespace, :cache_key_strategy, :expanded_key) do
nil | :revision | "#{key}:#{Gitlab.revision}"
expect(cache_key).to eq("#{key}:#{Gitlab::VERSION}:#{Rails.version}") nil | :version | "#{key}:#{Gitlab::VERSION}:#{Rails.version}"
end namespace | :revision | "#{namespace}:#{key}:#{Gitlab.revision}"
end namespace | :version | "#{namespace}:#{key}:#{Gitlab::VERSION}:#{Rails.version}"
end
context 'when cache_key_with_version is false' do with_them do
it 'returns the key' do let(:cache) { described_class.new(namespace: namespace, cache_key_strategy: cache_key_strategy) }
cache = described_class.new(namespace: nil, cache_key_with_version: false)
cache_key = cache.cache_key(key) subject { cache.cache_key(key) }
expect(cache_key).to eq(key) it { is_expected.to eq expanded_key }
end
end
end end
context 'when namespace is nil' do context 'when cache_key_strategy is unknown' do
context 'when cache_key_with_version is true' do let(:cache) { described_class.new(namespace: namespace, cache_key_strategy: 'unknown') }
it 'expands out the key with GitLab, and Rails versions' do
cache = described_class.new(cache_key_with_version: true)
cache_key = cache.cache_key(key)
expect(cache_key).to eq("#{key}:#{Gitlab::VERSION}:#{Rails.version}") it 'raises KeyError' do
end expect { cache.cache_key('key') }.to raise_error(KeyError)
end end
end
end
context 'when cache_key_with_version is false' do describe '#namespace' do
it 'returns the key' do it 'defaults to nil' do
cache = described_class.new(namespace: nil, cache_key_with_version: false) cache = described_class.new
expect(cache.namespace).to be_nil
end
end
cache_key = cache.cache_key(key) describe '#strategy_key_component' do
subject { cache.strategy_key_component }
expect(cache_key).to eq(key) it 'defaults to Gitlab.revision' do
end expect(described_class.new.strategy_key_component).to eq Gitlab.revision
end
end end
context 'when namespace is set' do context 'when cache_key_strategy is :revision' do
context 'when cache_key_with_version is true' do let(:cache) { described_class.new(cache_key_strategy: :revision) }
it 'expands out the key with namespace and Rails version' do
cache = described_class.new(namespace: namespace, cache_key_with_version: true)
cache_key = cache.cache_key(key) it { is_expected.to eq Gitlab.revision }
end
expect(cache_key).to eq("#{namespace}:#{key}:#{Gitlab::VERSION}:#{Rails.version}") context 'when cache_key_strategy is :version' do
end let(:cache) { described_class.new(cache_key_strategy: :version) }
end
context 'when cache_key_with_version is false' do it { is_expected.to eq [Gitlab::VERSION, Rails.version] }
it 'expands out the key with namespace' do end
cache = described_class.new(namespace: namespace, cache_key_with_version: false)
cache_key = cache.cache_key(key) context 'when cache_key_strategy is invalid' do
let(:cache) { described_class.new(cache_key_strategy: 'unknown') }
expect(cache_key).to eq("#{namespace}:#{key}") it 'raises KeyError' do
end expect { subject }.to raise_error(KeyError)
end end
end end
end end
......
...@@ -286,9 +286,9 @@ RSpec.describe BroadcastMessage do ...@@ -286,9 +286,9 @@ RSpec.describe BroadcastMessage do
it 'flushes the Redis cache' do it 'flushes the Redis cache' do
message = create(:broadcast_message) message = create(:broadcast_message)
expect(Rails.cache).to receive(:delete).with(described_class::CACHE_KEY) expect(Rails.cache).to receive(:delete).with("#{described_class::CACHE_KEY}:#{Gitlab.revision}")
expect(Rails.cache).to receive(:delete).with(described_class::BANNER_CACHE_KEY) expect(Rails.cache).to receive(:delete).with("#{described_class::BANNER_CACHE_KEY}:#{Gitlab.revision}")
expect(Rails.cache).to receive(:delete).with(described_class::NOTIFICATION_CACHE_KEY) expect(Rails.cache).to receive(:delete).with("#{described_class::NOTIFICATION_CACHE_KEY}:#{Gitlab.revision}")
message.flush_redis_cache message.flush_redis_cache
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment