Commit 7b562c97 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'sh-fix-schema-migrations-seq-scans' into 'master'

Avoid sequential scans loading schema_migrations table when loading application settings

See merge request gitlab-org/gitlab-ce!19541
parents 5024c4aa 6afe6fa6
...@@ -24,7 +24,22 @@ module Gitlab ...@@ -24,7 +24,22 @@ module Gitlab
private private
def ensure_application_settings! def ensure_application_settings!
cached_application_settings || uncached_application_settings
end
def cached_application_settings
return in_memory_application_settings if ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true' return in_memory_application_settings if ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true'
begin
::ApplicationSetting.cached
rescue
# In case Redis isn't running
# or the Redis UNIX socket file is not available
# or the DB is not running (we use migrations in the cache key)
end
end
def uncached_application_settings
return fake_application_settings unless connect_to_db? return fake_application_settings unless connect_to_db?
current_settings = ::ApplicationSetting.current current_settings = ::ApplicationSetting.current
......
...@@ -5,6 +5,13 @@ describe Gitlab::CurrentSettings do ...@@ -5,6 +5,13 @@ describe Gitlab::CurrentSettings do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end end
shared_context 'with settings in cache' do
before do
create(:application_setting)
described_class.current_application_settings # warm the cache
end
end
describe '#current_application_settings', :use_clean_rails_memory_store_caching do describe '#current_application_settings', :use_clean_rails_memory_store_caching do
it 'allows keys to be called directly' do it 'allows keys to be called directly' do
db_settings = create(:application_setting, db_settings = create(:application_setting,
...@@ -31,16 +38,29 @@ describe Gitlab::CurrentSettings do ...@@ -31,16 +38,29 @@ describe Gitlab::CurrentSettings do
end end
context 'with DB unavailable' do context 'with DB unavailable' do
before do context 'and settings in cache' do
# For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues include_context 'with settings in cache'
# during the initialization phase of the test suite, so instead let's mock the internals of it
allow(ActiveRecord::Base.connection).to receive(:active?).and_return(false) it 'fetches the settings from cache without issuing any query' do
expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
end
end end
it 'returns an in-memory ApplicationSetting object' do context 'and no settings in cache' do
expect(ApplicationSetting).not_to receive(:current) before do
# For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues
# during the initialization phase of the test suite, so instead let's mock the internals of it
allow(ActiveRecord::Base.connection).to receive(:active?).and_return(false)
expect(ApplicationSetting).not_to receive(:current)
end
expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) it 'returns an in-memory ApplicationSetting object' do
expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
end
it 'does not issue any query' do
expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
end
end end
end end
...@@ -52,73 +72,86 @@ describe Gitlab::CurrentSettings do ...@@ -52,73 +72,86 @@ describe Gitlab::CurrentSettings do
ar_wrapped_defaults.slice(*::ApplicationSetting.defaults.keys) ar_wrapped_defaults.slice(*::ApplicationSetting.defaults.keys)
end end
before do context 'and settings in cache' do
# For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(true)` causes issues include_context 'with settings in cache'
# during the initialization phase of the test suite, so instead let's mock the internals of it
allow(ActiveRecord::Base.connection).to receive(:active?).and_return(true)
allow(ActiveRecord::Base.connection).to receive(:cached_table_exists?).with('application_settings').and_return(true)
end
it 'creates default ApplicationSettings if none are present' do it 'fetches the settings from cache' do
settings = described_class.current_application_settings # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(true)` causes issues
# during the initialization phase of the test suite, so instead let's mock the internals of it
expect(settings).to be_a(ApplicationSetting) expect(ActiveRecord::Base.connection).not_to receive(:active?)
expect(settings).to be_persisted expect(ActiveRecord::Base.connection).not_to receive(:cached_table_exists?)
expect(settings).to have_attributes(settings_from_defaults) expect(ActiveRecord::Migrator).not_to receive(:needs_migration?)
expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
end
end end
context 'with migrations pending' do context 'and no settings in cache' do
before do before do
expect(ActiveRecord::Migrator).to receive(:needs_migration?).and_return(true) allow(ActiveRecord::Base.connection).to receive(:active?).and_return(true)
allow(ActiveRecord::Base.connection).to receive(:cached_table_exists?).with('application_settings').and_return(true)
end end
it 'returns an in-memory ApplicationSetting object' do it 'creates default ApplicationSettings if none are present' do
settings = described_class.current_application_settings settings = described_class.current_application_settings
expect(settings).to be_a(Gitlab::FakeApplicationSettings) expect(settings).to be_a(ApplicationSetting)
expect(settings.sign_in_enabled?).to eq(settings.sign_in_enabled) expect(settings).to be_persisted
expect(settings.sign_up_enabled?).to eq(settings.sign_up_enabled) expect(settings).to have_attributes(settings_from_defaults)
end end
it 'uses the existing database settings and falls back to defaults' do context 'with migrations pending' do
db_settings = create(:application_setting, before do
home_page_url: 'http://mydomain.com', expect(ActiveRecord::Migrator).to receive(:needs_migration?).and_return(true)
signup_enabled: false) end
settings = described_class.current_application_settings
app_defaults = ApplicationSetting.last it 'returns an in-memory ApplicationSetting object' do
settings = described_class.current_application_settings
expect(settings).to be_a(Gitlab::FakeApplicationSettings)
expect(settings.home_page_url).to eq(db_settings.home_page_url) expect(settings).to be_a(Gitlab::FakeApplicationSettings)
expect(settings.signup_enabled?).to be_falsey expect(settings.sign_in_enabled?).to eq(settings.sign_in_enabled)
expect(settings.signup_enabled).to be_falsey expect(settings.sign_up_enabled?).to eq(settings.sign_up_enabled)
end
# Check that unspecified values use the defaults
settings.reject! { |key, _| [:home_page_url, :signup_enabled].include? key } it 'uses the existing database settings and falls back to defaults' do
settings.each { |key, _| expect(settings[key]).to eq(app_defaults[key]) } db_settings = create(:application_setting,
home_page_url: 'http://mydomain.com',
signup_enabled: false)
settings = described_class.current_application_settings
app_defaults = ApplicationSetting.last
expect(settings).to be_a(Gitlab::FakeApplicationSettings)
expect(settings.home_page_url).to eq(db_settings.home_page_url)
expect(settings.signup_enabled?).to be_falsey
expect(settings.signup_enabled).to be_falsey
# Check that unspecified values use the defaults
settings.reject! { |key, _| [:home_page_url, :signup_enabled].include? key }
settings.each { |key, _| expect(settings[key]).to eq(app_defaults[key]) }
end
end end
end
context 'when ApplicationSettings.current is present' do context 'when ApplicationSettings.current is present' do
it 'returns the existing application settings' do it 'returns the existing application settings' do
expect(ApplicationSetting).to receive(:current).and_return(:current_settings) expect(ApplicationSetting).to receive(:current).and_return(:current_settings)
expect(described_class.current_application_settings).to eq(:current_settings) expect(described_class.current_application_settings).to eq(:current_settings)
end
end end
end
context 'when the application_settings table does not exists' do context 'when the application_settings table does not exists' do
it 'returns an in-memory ApplicationSetting object' do it 'returns an in-memory ApplicationSetting object' do
expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::StatementInvalid) expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::StatementInvalid)
expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
end
end end
end
context 'when the application_settings table is not fully migrated' do context 'when the application_settings table is not fully migrated' do
it 'returns an in-memory ApplicationSetting object' do it 'returns an in-memory ApplicationSetting object' do
expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::UnknownAttributeError) expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::UnknownAttributeError)
expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
end
end end
end end
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