Commit 3a577f32 authored by Stan Hu's avatar Stan Hu

Cache license data in a process-memory cache

Instead of loading the current license once per request, we now cache
the license data for a minute in the process cache.

Relates to https://gitlab.com/gitlab-org/gitlab/-/issues/292900
parent a523866d
---
title: Cache license data in a process-memory cache
merge_request: 50318
author:
type: performance
......@@ -242,6 +242,8 @@ class License < ApplicationRecord
scope :recent, -> { reorder(id: :desc) }
scope :last_hundred, -> { recent.limit(100) }
CACHE_KEY = :current_license
class << self
def features_for_plan(plan)
FEATURES_BY_PLAN.fetch(plan, [])
......@@ -260,11 +262,12 @@ class License < ApplicationRecord
end
def current
if RequestStore.active?
RequestStore.fetch(:current_license) { load_license }
else
load_license
end
cache.fetch(CACHE_KEY, as: License, expires_in: 1.minute) { load_license }
end
def cache
Gitlab::SafeRequestStore[:license_cache] ||=
Gitlab::JsonCache.new(namespace: :ee, backend: ::Gitlab::ProcessMemoryCache.cache_backend)
end
def all_plans
......@@ -274,7 +277,7 @@ class License < ApplicationRecord
delegate :block_changes?, :feature_available?, to: :current, allow_nil: true
def reset_current
RequestStore.delete(:current_license)
cache.expire(CACHE_KEY)
end
def load_license
......
......@@ -415,7 +415,7 @@ RSpec.describe License do
end
end
describe '.current' do
describe '.current', :request_store, :use_clean_rails_memory_store_caching do
context 'when licenses table does not exist' do
it 'returns nil' do
allow(described_class).to receive(:table_exists?).and_return(false)
......@@ -442,12 +442,28 @@ RSpec.describe License do
end
context 'when the license is valid' do
let!(:current_license) { create_list(:license, 2).last }
it 'returns the license' do
current_license = create_list(:license, 2).last
create(:license, data: create(:gitlab_license, starts_at: Date.current + 1.month).export)
expect(described_class.current).to eq(current_license)
end
it 'caches the license' do
described_class.reset_current
expect(described_class).to receive(:load_license).once.and_call_original
2.times do
expect(described_class.current).to eq(current_license)
end
travel_to(61.seconds.from_now) do
expect(described_class).to receive(:load_license).once.and_call_original
expect(described_class.current).to eq(current_license)
end
end
end
end
......
......@@ -69,6 +69,8 @@ RSpec.describe 'Project.cluster_agents' do
end
it 'does not suffer from N+1 performance issues' do
post_graphql(query, current_user: current_user)
expect do
post_graphql(query, current_user: current_user)
end.to issue_same_number_of_queries_as { post_graphql(query, current_user: current_user, variables: [first.with(1)]) }
......
......@@ -850,12 +850,13 @@ RSpec.describe ApplicationSetting do
end
end
describe '#instance_review_permitted?', :request_store do
describe '#instance_review_permitted?', :request_store, :use_clean_rails_memory_store_caching do
subject { setting.instance_review_permitted? }
before do
RequestStore.store[:current_license] = nil
expect(Rails.cache).to receive(:fetch).and_return(
allow(License).to receive(:current).and_return(nil) if Gitlab.ee?
allow(Rails.cache).to receive(:fetch).and_call_original
expect(Rails.cache).to receive(:fetch).with('limited_users_count', anything).and_return(
::ApplicationSetting::INSTANCE_REVIEW_MIN_USERS + users_over_minimum
)
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