Commit 80f46041 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'move-load-balancing-proxy-to-activerecord' into 'master'

Store the load balancer proxy in ActiveRecord

See merge request gitlab-org/gitlab!65805
parents a1c8ea30 51ba2d76
# frozen_string_literal: true # frozen_string_literal: true
ActiveRecord::Base.singleton_class.attr_accessor :load_balancing_proxy
if Gitlab::Database::LoadBalancing.enable? if Gitlab::Database::LoadBalancing.enable?
Gitlab::Database.main.disable_prepared_statements Gitlab::Database.main.disable_prepared_statements
...@@ -7,6 +9,11 @@ if Gitlab::Database::LoadBalancing.enable? ...@@ -7,6 +9,11 @@ if Gitlab::Database::LoadBalancing.enable?
config.middleware.use(Gitlab::Database::LoadBalancing::RackMiddleware) config.middleware.use(Gitlab::Database::LoadBalancing::RackMiddleware)
end end
# This hijacks the "connection" method to ensure both
# `ActiveRecord::Base.connection` and all models use the same load
# balancing proxy.
ActiveRecord::Base.singleton_class.prepend(Gitlab::Database::LoadBalancing::ActiveRecordProxy)
Gitlab::Database::LoadBalancing.configure_proxy Gitlab::Database::LoadBalancing.configure_proxy
# This needs to be executed after fork of clustered processes # This needs to be executed after fork of clustered processes
......
...@@ -23,7 +23,7 @@ module Gitlab ...@@ -23,7 +23,7 @@ module Gitlab
# The connection proxy to use for load balancing (if enabled). # The connection proxy to use for load balancing (if enabled).
def self.proxy def self.proxy
unless @proxy unless load_balancing_proxy = ActiveRecord::Base.load_balancing_proxy
Gitlab::ErrorTracking.track_exception( Gitlab::ErrorTracking.track_exception(
ProxyNotConfiguredError.new( ProxyNotConfiguredError.new(
"Attempting to access the database load balancing proxy, but it wasn't configured.\n" \ "Attempting to access the database load balancing proxy, but it wasn't configured.\n" \
...@@ -31,7 +31,7 @@ module Gitlab ...@@ -31,7 +31,7 @@ module Gitlab
)) ))
end end
@proxy load_balancing_proxy
end end
# Returns a Hash containing the load balancing configuration. # Returns a Hash containing the load balancing configuration.
...@@ -107,12 +107,7 @@ module Gitlab ...@@ -107,12 +107,7 @@ module Gitlab
# Configures proxying of requests. # Configures proxying of requests.
def self.configure_proxy(proxy = ConnectionProxy.new(hosts)) def self.configure_proxy(proxy = ConnectionProxy.new(hosts))
@proxy = proxy ActiveRecord::Base.load_balancing_proxy = proxy
# This hijacks the "connection" method to ensure both
# `ActiveRecord::Base.connection` and all models use the same load
# balancing proxy.
ActiveRecord::Base.singleton_class.prepend(ActiveRecordProxy)
end end
def self.active_record_models def self.active_record_models
...@@ -132,7 +127,7 @@ module Gitlab ...@@ -132,7 +127,7 @@ module Gitlab
# recognize the connection, this method returns the primary role # recognize the connection, this method returns the primary role
# directly. In future, we may need to check for other sources. # directly. In future, we may need to check for other sources.
def self.db_role_for_connection(connection) def self.db_role_for_connection(connection)
return ROLE_PRIMARY if !enable? || @proxy.blank? return ROLE_PRIMARY if !enable? || proxy.blank?
proxy.load_balancer.db_role_for_connection(connection) proxy.load_balancer.db_role_for_connection(connection)
end end
......
...@@ -7,7 +7,7 @@ module Gitlab ...@@ -7,7 +7,7 @@ module Gitlab
# "connection" method. # "connection" method.
module ActiveRecordProxy module ActiveRecordProxy
def connection def connection
LoadBalancing.proxy ::Gitlab::Database::LoadBalancing.proxy
end end
end end
end end
......
...@@ -120,7 +120,6 @@ RSpec.describe 'lograge', type: :request do ...@@ -120,7 +120,6 @@ RSpec.describe 'lograge', type: :request do
context 'with a log subscriber' do context 'with a log subscriber' do
include_context 'parsed logs' include_context 'parsed logs'
include_context 'clear DB Load Balancing configuration'
let(:subscriber) { Lograge::LogSubscribers::ActionController.new } let(:subscriber) { Lograge::LogSubscribers::ActionController.new }
......
...@@ -49,12 +49,11 @@ RSpec.describe Gitlab::Checks::MatchingMergeRequest do ...@@ -49,12 +49,11 @@ RSpec.describe Gitlab::Checks::MatchingMergeRequest do
end end
end end
context 'with load balancing enabled', :request_store, :redis do context 'with load balancing enabled', :db_load_balancing do
let(:session) { ::Gitlab::Database::LoadBalancing::Session.current } let(:session) { ::Gitlab::Database::LoadBalancing::Session.current }
let(:all_caught_up) { true } let(:all_caught_up) { true }
before do before do
expect(::Gitlab::Database::LoadBalancing).to receive(:enable?).at_least(:once).and_return(true)
allow(::Gitlab::Database::LoadBalancing::Sticking).to receive(:all_caught_up?).and_return(all_caught_up) allow(::Gitlab::Database::LoadBalancing::Sticking).to receive(:all_caught_up?).and_return(all_caught_up)
expect(::Gitlab::Database::LoadBalancing::Sticking).to receive(:select_valid_host).with(:project, project.id).and_call_original expect(::Gitlab::Database::LoadBalancing::Sticking).to receive(:select_valid_host).with(:project, project.id).and_call_original
......
...@@ -3,25 +3,32 @@ ...@@ -3,25 +3,32 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Database::LoadBalancing do RSpec.describe Gitlab::Database::LoadBalancing do
include_context 'clear DB Load Balancing configuration'
before do before do
stub_env('ENABLE_LOAD_BALANCING_FOR_FOSS', 'true') stub_env('ENABLE_LOAD_BALANCING_FOR_FOSS', 'true')
end end
describe '.proxy' do describe '.proxy' do
before do
@previous_proxy = ActiveRecord::Base.load_balancing_proxy
ActiveRecord::Base.load_balancing_proxy = connection_proxy
end
after do
ActiveRecord::Base.load_balancing_proxy = @previous_proxy
end
context 'when configured' do context 'when configured' do
before do let(:connection_proxy) { double(:connection_proxy) }
allow(ActiveRecord::Base.singleton_class).to receive(:prepend)
subject.configure_proxy
end
it 'returns the connection proxy' do it 'returns the connection proxy' do
expect(subject.proxy).to be_an_instance_of(subject::ConnectionProxy) expect(subject.proxy).to eq(connection_proxy)
end end
end end
context 'when not configured' do context 'when not configured' do
let(:connection_proxy) { nil }
it 'returns nil' do it 'returns nil' do
expect(subject.proxy).to be_nil expect(subject.proxy).to be_nil
end end
...@@ -132,7 +139,6 @@ RSpec.describe Gitlab::Database::LoadBalancing do ...@@ -132,7 +139,6 @@ RSpec.describe Gitlab::Database::LoadBalancing do
describe '.enable?' do describe '.enable?' do
before do before do
clear_load_balancing_configuration
allow(described_class).to receive(:hosts).and_return(%w(foo)) allow(described_class).to receive(:hosts).and_return(%w(foo))
end end
...@@ -173,10 +179,6 @@ RSpec.describe Gitlab::Database::LoadBalancing do ...@@ -173,10 +179,6 @@ RSpec.describe Gitlab::Database::LoadBalancing do
end end
describe '.configured?' do describe '.configured?' do
before do
clear_load_balancing_configuration
end
it 'returns true when Sidekiq is being used' do it 'returns true when Sidekiq is being used' do
allow(described_class).to receive(:hosts).and_return(%w(foo)) allow(described_class).to receive(:hosts).and_return(%w(foo))
allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true) allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true)
...@@ -207,12 +209,12 @@ RSpec.describe Gitlab::Database::LoadBalancing do ...@@ -207,12 +209,12 @@ RSpec.describe Gitlab::Database::LoadBalancing do
describe '.configure_proxy' do describe '.configure_proxy' do
it 'configures the connection proxy' do it 'configures the connection proxy' do
allow(ActiveRecord::Base.singleton_class).to receive(:prepend) allow(ActiveRecord::Base).to receive(:load_balancing_proxy=)
described_class.configure_proxy described_class.configure_proxy
expect(ActiveRecord::Base.singleton_class).to have_received(:prepend) expect(ActiveRecord::Base).to have_received(:load_balancing_proxy=)
.with(Gitlab::Database::LoadBalancing::ActiveRecordProxy) .with(Gitlab::Database::LoadBalancing::ConnectionProxy)
end end
end end
...@@ -315,13 +317,9 @@ RSpec.describe Gitlab::Database::LoadBalancing do ...@@ -315,13 +317,9 @@ RSpec.describe Gitlab::Database::LoadBalancing do
let(:load_balancer) { described_class::LoadBalancer.new(%w(foo)) } let(:load_balancer) { described_class::LoadBalancer.new(%w(foo)) }
before do before do
allow(ActiveRecord::Base.singleton_class).to receive(:prepend)
allow(described_class).to receive(:enable?).and_return(true) allow(described_class).to receive(:enable?).and_return(true)
allow(described_class).to receive(:proxy).and_return(proxy) allow(described_class).to receive(:proxy).and_return(proxy)
allow(proxy).to receive(:load_balancer).and_return(load_balancer) allow(proxy).to receive(:load_balancer).and_return(load_balancer)
subject.configure_proxy(proxy)
end end
context 'when the load balancer returns :replica' do context 'when the load balancer returns :replica' do
...@@ -366,7 +364,7 @@ RSpec.describe Gitlab::Database::LoadBalancing do ...@@ -366,7 +364,7 @@ RSpec.describe Gitlab::Database::LoadBalancing do
# - In each test, we listen to the SQL queries (via sql.active_record # - In each test, we listen to the SQL queries (via sql.active_record
# instrumentation) while triggering real queries from the defined model. # instrumentation) while triggering real queries from the defined model.
# - We assert the desinations (replica/primary) of the queries in order. # - We assert the desinations (replica/primary) of the queries in order.
describe 'LoadBalancing integration tests', :delete do describe 'LoadBalancing integration tests', :db_load_balancing, :delete do
before(:all) do before(:all) do
ActiveRecord::Schema.define do ActiveRecord::Schema.define do
create_table :load_balancing_test, force: true do |t| create_table :load_balancing_test, force: true do |t|
...@@ -381,30 +379,14 @@ RSpec.describe Gitlab::Database::LoadBalancing do ...@@ -381,30 +379,14 @@ RSpec.describe Gitlab::Database::LoadBalancing do
end end
end end
shared_context 'LoadBalancing setup' do let(:model) do
let(:development_db_config) { ActiveRecord::Base.configurations.configs_for(env_name: 'development').first.configuration_hash } Class.new(ApplicationRecord) do
let(:hosts) { [development_db_config[:host]] } self.table_name = "load_balancing_test"
let(:model) do
Class.new(ApplicationRecord) do
self.table_name = "load_balancing_test"
end
end end
end
before do before do
# Preloading testing class model.singleton_class.prepend ::Gitlab::Database::LoadBalancing::ActiveRecordProxy
model.singleton_class.prepend ::Gitlab::Database::LoadBalancing::ActiveRecordProxy
# Setup load balancing
clear_load_balancing_configuration
allow(ActiveRecord::Base.singleton_class).to receive(:prepend)
subject.configure_proxy(::Gitlab::Database::LoadBalancing::ConnectionProxy.new(hosts))
original_db_config = Gitlab::Database.main.config
modified_db_config = original_db_config.merge(load_balancing: { hosts: hosts })
allow(Gitlab::Database.main).to receive(:config).and_return(modified_db_config)
::Gitlab::Database::LoadBalancing::Session.clear_session
end
end end
where(:queries, :include_transaction, :expected_results) do where(:queries, :include_transaction, :expected_results) do
...@@ -715,8 +697,6 @@ RSpec.describe Gitlab::Database::LoadBalancing do ...@@ -715,8 +697,6 @@ RSpec.describe Gitlab::Database::LoadBalancing do
end end
with_them do with_them do
include_context 'LoadBalancing setup'
it 'redirects queries to the right roles' do it 'redirects queries to the right roles' do
roles = [] roles = []
...@@ -785,8 +765,6 @@ RSpec.describe Gitlab::Database::LoadBalancing do ...@@ -785,8 +765,6 @@ RSpec.describe Gitlab::Database::LoadBalancing do
end end
with_them do with_them do
include_context 'LoadBalancing setup'
it 'redirects queries to the right roles' do it 'redirects queries to the right roles' do
roles = [] roles = []
...@@ -805,8 +783,6 @@ RSpec.describe Gitlab::Database::LoadBalancing do ...@@ -805,8 +783,6 @@ RSpec.describe Gitlab::Database::LoadBalancing do
end end
context 'a write inside a transaction inside fallback_to_replicas_for_ambiguous_queries block' do context 'a write inside a transaction inside fallback_to_replicas_for_ambiguous_queries block' do
include_context 'LoadBalancing setup'
it 'raises an exception' do it 'raises an exception' do
expect do expect do
::Gitlab::Database::LoadBalancing::Session.current.fallback_to_replicas_for_ambiguous_queries do ::Gitlab::Database::LoadBalancing::Session.current.fallback_to_replicas_for_ambiguous_queries do
......
...@@ -102,8 +102,6 @@ RSpec.describe Gitlab::InstrumentationHelper do ...@@ -102,8 +102,6 @@ RSpec.describe Gitlab::InstrumentationHelper do
end end
context 'when load balancing is enabled' do context 'when load balancing is enabled' do
include_context 'clear DB Load Balancing configuration'
before do before do
allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true) allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true)
end end
......
...@@ -228,8 +228,6 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do ...@@ -228,8 +228,6 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
end end
context 'when the job performs database queries' do context 'when the job performs database queries' do
include_context 'clear DB Load Balancing configuration'
before do before do
allow(Time).to receive(:now).and_return(timestamp) allow(Time).to receive(:now).and_return(timestamp)
allow(Process).to receive(:clock_gettime).and_call_original allow(Process).to receive(:clock_gettime).and_call_original
...@@ -293,11 +291,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do ...@@ -293,11 +291,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
include_examples 'performs database queries' include_examples 'performs database queries'
end end
context 'when load balancing is enabled' do context 'when load balancing is enabled', :db_load_balancing do
before do
allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true)
end
let(:db_config_name) { ::Gitlab::Database.db_config_name(ApplicationRecord.connection) } let(:db_config_name) { ::Gitlab::Database.db_config_name(ApplicationRecord.connection) }
let(:expected_end_payload_with_db) do let(:expected_end_payload_with_db) do
......
...@@ -236,7 +236,6 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do ...@@ -236,7 +236,6 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
include_context 'server metrics with mocked prometheus' include_context 'server metrics with mocked prometheus'
include_context 'server metrics call' include_context 'server metrics call'
include_context 'clear DB Load Balancing configuration'
shared_context 'worker declaring data consistency' do shared_context 'worker declaring data consistency' do
let(:worker_class) { LBTestWorker } let(:worker_class) { LBTestWorker }
......
...@@ -344,12 +344,9 @@ RSpec.describe Ci::Build do ...@@ -344,12 +344,9 @@ RSpec.describe Ci::Build do
end end
describe '#stick_build_if_status_changed' do describe '#stick_build_if_status_changed' do
it 'sticks the build if the status changed' do it 'sticks the build if the status changed', :db_load_balancing do
job = create(:ci_build, :pending) job = create(:ci_build, :pending)
allow(Gitlab::Database::LoadBalancing).to receive(:enable?)
.and_return(true)
expect(Gitlab::Database::LoadBalancing::Sticking).to receive(:stick) expect(Gitlab::Database::LoadBalancing::Sticking).to receive(:stick)
.with(:build, job.id) .with(:build, job.id)
......
...@@ -128,19 +128,15 @@ RSpec.describe ProjectFeatureUsage, type: :model do ...@@ -128,19 +128,15 @@ RSpec.describe ProjectFeatureUsage, type: :model do
end end
context 'ProjectFeatureUsage with DB Load Balancing', :request_store do context 'ProjectFeatureUsage with DB Load Balancing', :request_store do
include_context 'clear DB Load Balancing configuration'
describe '#log_jira_dvcs_integration_usage' do describe '#log_jira_dvcs_integration_usage' do
let!(:project) { create(:project) } let!(:project) { create(:project) }
subject { project.feature_usage } subject { project.feature_usage }
context 'database load balancing is configured' do context 'database load balancing is configured', :db_load_balancing do
before do before do
# Do not pollute AR for other tests, but rather simulate effect of configure_proxy.
allow(ActiveRecord::Base.singleton_class).to receive(:prepend)
::Gitlab::Database::LoadBalancing.configure_proxy
allow(ActiveRecord::Base).to receive(:connection).and_return(::Gitlab::Database::LoadBalancing.proxy) allow(ActiveRecord::Base).to receive(:connection).and_return(::Gitlab::Database::LoadBalancing.proxy)
::Gitlab::Database::LoadBalancing::Session.clear_session ::Gitlab::Database::LoadBalancing::Session.clear_session
end end
......
...@@ -14,7 +14,7 @@ module Ci ...@@ -14,7 +14,7 @@ module Ci
let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) } let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) }
describe '#execute' do describe '#execute' do
context 'checks database loadbalancing stickiness' do context 'checks database loadbalancing stickiness', :db_load_balancing do
subject { described_class.new(shared_runner).execute } subject { described_class.new(shared_runner).execute }
before do before do
...@@ -22,9 +22,6 @@ module Ci ...@@ -22,9 +22,6 @@ module Ci
end end
it 'result is valid if replica did caught-up' do it 'result is valid if replica did caught-up' do
allow(Gitlab::Database::LoadBalancing).to receive(:enable?)
.and_return(true)
expect(Gitlab::Database::LoadBalancing::Sticking).to receive(:all_caught_up?) expect(Gitlab::Database::LoadBalancing::Sticking).to receive(:all_caught_up?)
.with(:runner, shared_runner.id) { true } .with(:runner, shared_runner.id) { true }
...@@ -32,9 +29,6 @@ module Ci ...@@ -32,9 +29,6 @@ module Ci
end end
it 'result is invalid if replica did not caught-up' do it 'result is invalid if replica did not caught-up' do
allow(Gitlab::Database::LoadBalancing).to receive(:enable?)
.and_return(true)
expect(Gitlab::Database::LoadBalancing::Sticking).to receive(:all_caught_up?) expect(Gitlab::Database::LoadBalancing::Sticking).to receive(:all_caught_up?)
.with(:runner, shared_runner.id) { false } .with(:runner, shared_runner.id) { false }
......
...@@ -85,19 +85,14 @@ RSpec.describe Users::ActivityService do ...@@ -85,19 +85,14 @@ RSpec.describe Users::ActivityService do
end end
end end
context 'with DB Load Balancing', :request_store, :redis, :clean_gitlab_redis_shared_state do context 'with DB Load Balancing' do
include_context 'clear DB Load Balancing configuration'
let(:user) { create(:user, last_activity_on: last_activity_on) } let(:user) { create(:user, last_activity_on: last_activity_on) }
context 'when last activity is in the past' do context 'when last activity is in the past' do
let(:user) { create(:user, last_activity_on: Date.today - 1.week) } let(:user) { create(:user, last_activity_on: Date.today - 1.week) }
context 'database load balancing is configured' do context 'database load balancing is configured', :db_load_balancing do
before do before do
# Do not pollute AR for other tests, but rather simulate effect of configure_proxy.
allow(ActiveRecord::Base.singleton_class).to receive(:prepend)
::Gitlab::Database::LoadBalancing.configure_proxy
allow(ActiveRecord::Base).to receive(:connection).and_return(::Gitlab::Database::LoadBalancing.proxy) allow(ActiveRecord::Base).to receive(:connection).and_return(::Gitlab::Database::LoadBalancing.proxy)
end end
......
# frozen_string_literal: true
RSpec.configure do |config|
config.before(:each, :db_load_balancing) do
allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true)
proxy = ::Gitlab::Database::LoadBalancing::ConnectionProxy.new([Gitlab::Database.main.config['host']])
allow(ActiveRecord::Base).to receive(:load_balancing_proxy).and_return(proxy)
::Gitlab::Database::LoadBalancing::Session.clear_session
redis_shared_state_cleanup!
end
config.after(:each, :db_load_balancing) do
::Gitlab::Database::LoadBalancing::Session.clear_session
redis_shared_state_cleanup!
end
end
# frozen_string_literal: true
RSpec.shared_context 'clear DB Load Balancing configuration' do
def clear_load_balancing_configuration
proxy = ::Gitlab::Database::LoadBalancing.instance_variable_get(:@proxy)
proxy.load_balancer.release_host if proxy
::Gitlab::Database::LoadBalancing.instance_variable_set(:@proxy, nil)
::Gitlab::Database::LoadBalancing::Session.clear_session
end
around do |example|
clear_load_balancing_configuration
example.run
clear_load_balancing_configuration
end
end
...@@ -44,11 +44,7 @@ RSpec.describe AuthorizedProjectUpdate::UserRefreshFromReplicaWorker do ...@@ -44,11 +44,7 @@ RSpec.describe AuthorizedProjectUpdate::UserRefreshFromReplicaWorker do
end end
end end
context 'with load balancing enabled' do context 'with load balancing enabled', :db_load_balancing do
before do
allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true)
end
it 'reads from the replica database' do it 'reads from the replica database' do
expect(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_replicas_for_read_queries).and_call_original expect(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_replicas_for_read_queries).and_call_original
......
...@@ -156,11 +156,7 @@ RSpec.describe ContainerExpirationPolicyWorker do ...@@ -156,11 +156,7 @@ RSpec.describe ContainerExpirationPolicyWorker do
subject subject
end end
context 'with load balancing enabled' do context 'with load balancing enabled', :db_load_balancing do
before do
allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true)
end
it 'reads the counts from the replica' do it 'reads the counts from the replica' do
expect(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_replicas_for_read_queries).and_call_original expect(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_replicas_for_read_queries).and_call_original
......
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