Commit 9599f866 authored by Yorick Peterse's avatar Yorick Peterse

Don't run WAL queries when not using replicas

This changes the load balancer to not run redundant WAL related queries
when no replicas are in use. These queries are not needed as a single
database is always in sync with itself. It should also ensure the new
load balancer setup works on Aurora, at least for the time being.

Changelog: fixed
parent e64d4b68
...@@ -11,6 +11,12 @@ module Gitlab ...@@ -11,6 +11,12 @@ module Gitlab
# balancing is enabled, but no replicas have been configured (= the # balancing is enabled, but no replicas have been configured (= the
# default case). # default case).
class PrimaryHost class PrimaryHost
WAL_ERROR_MESSAGE = <<~MSG.strip
Obtaining WAL information when not using any replicas results in
redundant queries, and may break installations that don't support
streaming replication (e.g. AWS' Aurora database).
MSG
def initialize(load_balancer) def initialize(load_balancer)
@load_balancer = load_balancer @load_balancer = load_balancer
end end
...@@ -51,30 +57,16 @@ module Gitlab ...@@ -51,30 +57,16 @@ module Gitlab
end end
def primary_write_location def primary_write_location
@load_balancer.primary_write_location raise NotImplementedError, WAL_ERROR_MESSAGE
end end
def database_replica_location def database_replica_location
row = query_and_release(<<-SQL.squish) raise NotImplementedError, WAL_ERROR_MESSAGE
SELECT pg_last_wal_replay_lsn()::text AS location
SQL
row['location'] if row.any?
rescue *Host::CONNECTION_ERRORS
nil
end end
def caught_up?(_location) def caught_up?(_location)
true true
end end
def query_and_release(sql)
connection.select_all(sql).first || {}
rescue StandardError
{}
ensure
release_connection
end
end end
end end
end end
......
...@@ -42,6 +42,9 @@ module Gitlab ...@@ -42,6 +42,9 @@ module Gitlab
end end
def wal_location_for(load_balancer) def wal_location_for(load_balancer)
# When only using the primary there's no need for any WAL queries.
return if load_balancer.primary_only?
if ::Gitlab::Database::LoadBalancing::Session.current.use_primary? if ::Gitlab::Database::LoadBalancing::Session.current.use_primary?
load_balancer.primary_write_location load_balancer.primary_write_location
else else
......
...@@ -104,6 +104,10 @@ module Gitlab ...@@ -104,6 +104,10 @@ module Gitlab
end end
def with_primary_write_location def with_primary_write_location
# When only using the primary, there's no point in getting write
# locations, as the primary is always in sync with itself.
return if @load_balancer.primary_only?
location = @load_balancer.primary_write_location location = @load_balancer.primary_write_location
return if location.blank? return if location.blank?
......
...@@ -63,9 +63,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::PrimaryHost do ...@@ -63,9 +63,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::PrimaryHost do
end end
describe '#primary_write_location' do describe '#primary_write_location' do
it 'returns the write location of the primary' do it 'raises NotImplementedError' do
expect(host.primary_write_location).to be_an_instance_of(String) expect { host.primary_write_location }.to raise_error(NotImplementedError)
expect(host.primary_write_location).not_to be_empty
end end
end end
...@@ -76,51 +75,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::PrimaryHost do ...@@ -76,51 +75,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::PrimaryHost do
end end
describe '#database_replica_location' do describe '#database_replica_location' do
let(:connection) { double(:connection) } it 'raises NotImplementedError' do
expect { host.database_replica_location }.to raise_error(NotImplementedError)
it 'returns the write ahead location of the replica', :aggregate_failures do
expect(host)
.to receive(:query_and_release)
.and_return({ 'location' => '0/D525E3A8' })
expect(host.database_replica_location).to be_an_instance_of(String)
end
it 'returns nil when the database query returned no rows' do
expect(host).to receive(:query_and_release).and_return({})
expect(host.database_replica_location).to be_nil
end
it 'returns nil when the database connection fails' do
allow(host).to receive(:connection).and_raise(PG::Error)
expect(host.database_replica_location).to be_nil
end
end
describe '#query_and_release' do
it 'executes a SQL query' do
results = host.query_and_release('SELECT 10 AS number')
expect(results).to be_an_instance_of(Hash)
expect(results['number'].to_i).to eq(10)
end
it 'releases the connection after running the query' do
expect(host)
.to receive(:release_connection)
.once
host.query_and_release('SELECT 10 AS number')
end
it 'returns an empty Hash in the event of an error' do
expect(host.connection)
.to receive(:select_all)
.and_raise(RuntimeError, 'kittens')
expect(host.query_and_release('SELECT 10 AS number')).to eq({})
end end
end end
end end
...@@ -21,7 +21,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do ...@@ -21,7 +21,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
middleware.call(worker_class, job, nil, nil) {} middleware.call(worker_class, job, nil, nil) {}
end end
describe '#call' do describe '#call', :database_replica do
shared_context 'data consistency worker class' do |data_consistency, feature_flag| shared_context 'data consistency worker class' do |data_consistency, feature_flag|
let(:expected_consistency) { data_consistency } let(:expected_consistency) { data_consistency }
let(:worker_class) do let(:worker_class) do
......
...@@ -188,6 +188,10 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do ...@@ -188,6 +188,10 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do
end end
it 'sticks an entity to the primary', :aggregate_failures do it 'sticks an entity to the primary', :aggregate_failures do
allow(ActiveRecord::Base.connection.load_balancer)
.to receive(:primary_only?)
.and_return(false)
ids.each do |id| ids.each do |id|
expect(sticking) expect(sticking)
.to receive(:set_write_location_for) .to receive(:set_write_location_for)
...@@ -199,6 +203,12 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do ...@@ -199,6 +203,12 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do
subject subject
end end
it 'does not update the write location when no replicas are used' do
expect(sticking).not_to receive(:set_write_location_for)
subject
end
end end
describe '#stick' do describe '#stick' do
...@@ -221,12 +231,22 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do ...@@ -221,12 +231,22 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do
.to receive(:primary_write_location) .to receive(:primary_write_location)
.and_return('foo') .and_return('foo')
allow(ActiveRecord::Base.connection.load_balancer)
.to receive(:primary_only?)
.and_return(false)
expect(sticking) expect(sticking)
.to receive(:set_write_location_for) .to receive(:set_write_location_for)
.with(:user, 42, 'foo') .with(:user, 42, 'foo')
sticking.mark_primary_write_location(:user, 42) sticking.mark_primary_write_location(:user, 42)
end end
it 'does nothing when no replicas are used' do
expect(sticking).not_to receive(:set_write_location_for)
sticking.mark_primary_write_location(:user, 42)
end
end end
describe '#unstick' do describe '#unstick' do
......
...@@ -2791,17 +2791,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do ...@@ -2791,17 +2791,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
extra_update_queries = 4 # transition ... => :canceled, queue pop extra_update_queries = 4 # transition ... => :canceled, queue pop
extra_generic_commit_status_validation_queries = 2 # name_uniqueness_across_types extra_generic_commit_status_validation_queries = 2 # name_uniqueness_across_types
# The number of extra load balancing queries depends on whether or not expect(control2.count).to eq(control1.count + extra_update_queries + extra_generic_commit_status_validation_queries)
# we use a load balancer for CI. That in turn depends on the contents of
# database.yml, so here we support both cases.
extra_load_balancer_queries =
if Gitlab::Database.has_config?(:ci)
6
else
3
end
expect(control2.count).to eq(control1.count + extra_update_queries + extra_generic_commit_status_validation_queries + extra_load_balancer_queries)
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