Commit 561bd16d authored by Krasimir Angelov's avatar Krasimir Angelov

Update Gitlab::Database::Count to work with decomposed database

Split models that need to be counted per database, count, and combine
the results.

https://gitlab.com/gitlab-org/gitlab/-/issues/345279
parent 0daa0e44
...@@ -32,12 +32,12 @@ module Gitlab ...@@ -32,12 +32,12 @@ module Gitlab
# Models using single-type inheritance (STI) don't work with # Models using single-type inheritance (STI) don't work with
# reltuple count estimates. We just have to ignore them and # reltuple count estimates. We just have to ignore them and
# use another strategy to compute them. # use another strategy to compute them.
def non_sti_models def non_sti_models(models)
models.reject { |model| sti_model?(model) } models.reject { |model| sti_model?(model) }
end end
def non_sti_table_names def non_sti_table_names(models)
non_sti_models.map(&:table_name) non_sti_models(models).map(&:table_name)
end end
def sti_model?(model) def sti_model?(model)
...@@ -45,21 +45,34 @@ module Gitlab ...@@ -45,21 +45,34 @@ module Gitlab
model.base_class != model model.base_class != model
end end
def table_names def table_to_model_mapping
models.map(&:table_name) @table_to_model_mapping ||= models.each_with_object({}) { |model, h| h[model.table_name] = model }
end
def table_to_model(table_name)
table_to_model_mapping[table_name]
end end
def size_estimates(check_statistics: true) def size_estimates(check_statistics: true)
table_to_model = models.each_with_object({}) { |model, h| h[model.table_name] = model } results = {}
# Querying tuple stats only works on the primary. Due to load balancing, the models.group_by { |model| model.connection_db_config.name }.map do |db_name, models_for_db|
# easiest way to do this is to start a transaction. base_model = Gitlab::Database.database_base_models[db_name]
ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases tables = non_sti_table_names(models_for_db)
get_statistics(non_sti_table_names, check_statistics: check_statistics).each_with_object({}) do |row, data|
model = table_to_model[row.table_name] # Querying tuple stats only works on the primary. Due to load balancing, the
data[model] = row.estimate # easiest way to do this is to start a transaction.
base_model.transaction do
Gitlab::Database::SharedModel.using_connection(base_model.connection) do
get_statistics(tables, check_statistics: check_statistics).each do |row|
model = table_to_model(row.table_name)
results[model] = row.estimate
end
end
end end
end end
results
end end
# Generates the PostgreSQL query to return the tuples for tables # Generates the PostgreSQL query to return the tuples for tables
......
...@@ -61,7 +61,7 @@ module Gitlab ...@@ -61,7 +61,7 @@ module Gitlab
#{where_clause(model)} #{where_clause(model)}
SQL SQL
rows = ActiveRecord::Base.connection.select_all(query) # rubocop: disable Database/MultipleDatabases rows = model.connection.select_all(query)
Integer(rows.first['count']) Integer(rows.first['count'])
end end
......
...@@ -258,7 +258,9 @@ module Gitlab ...@@ -258,7 +258,9 @@ module Gitlab
# We keep track of the estimated number of tuples to reason later # We keep track of the estimated number of tuples to reason later
# about the overall progress of a migration. # about the overall progress of a migration.
migration.total_tuple_count = Gitlab::Database::PgClass.for_table(batch_table_name)&.cardinality_estimate migration.total_tuple_count = Gitlab::Database::SharedModel.using_connection(connection) do
Gitlab::Database::PgClass.for_table(batch_table_name)&.cardinality_estimate
end
migration.save! migration.save!
migration migration
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module Gitlab module Gitlab
module Database module Database
class PgClass < ActiveRecord::Base class PgClass < SharedModel
self.table_name = 'pg_class' self.table_name = 'pg_class'
def self.for_table(relname) def self.for_table(relname)
......
...@@ -5,24 +5,24 @@ require 'spec_helper' ...@@ -5,24 +5,24 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::Count::ReltuplesCountStrategy do RSpec.describe Gitlab::Database::Count::ReltuplesCountStrategy do
before do before do
create_list(:project, 3) create_list(:project, 3)
create(:identity) create_list(:ci_instance_variable, 2)
end end
subject { described_class.new(models).count } subject { described_class.new(models).count }
describe '#count' do describe '#count' do
let(:models) { [Project, Identity] } let(:models) { [Project, Ci::InstanceVariable] }
context 'when reltuples is up to date' do context 'when reltuples is up to date' do
before do before do
ActiveRecord::Base.connection.execute('ANALYZE projects') Project.connection.execute('ANALYZE projects')
ActiveRecord::Base.connection.execute('ANALYZE identities') Ci::InstanceVariable.connection.execute('ANALYZE ci_instance_variables')
end end
it 'uses statistics to do the count' do it 'uses statistics to do the count' do
models.each { |model| expect(model).not_to receive(:count) } models.each { |model| expect(model).not_to receive(:count) }
expect(subject).to eq({ Project => 3, Identity => 1 }) expect(subject).to eq({ Project => 3, Ci::InstanceVariable => 2 })
end end
end end
...@@ -31,7 +31,7 @@ RSpec.describe Gitlab::Database::Count::ReltuplesCountStrategy do ...@@ -31,7 +31,7 @@ RSpec.describe Gitlab::Database::Count::ReltuplesCountStrategy do
before do before do
models.each do |model| models.each do |model|
ActiveRecord::Base.connection.execute("ANALYZE #{model.table_name}") model.connection.execute("ANALYZE #{model.table_name}")
end end
end end
...@@ -45,7 +45,9 @@ RSpec.describe Gitlab::Database::Count::ReltuplesCountStrategy do ...@@ -45,7 +45,9 @@ RSpec.describe Gitlab::Database::Count::ReltuplesCountStrategy do
context 'insufficient permissions' do context 'insufficient permissions' do
it 'returns an empty hash' do it 'returns an empty hash' do
allow(ActiveRecord::Base).to receive(:transaction).and_raise(PG::InsufficientPrivilege) Gitlab::Database.database_base_models.each_value do |base_model|
allow(base_model).to receive(:transaction).and_raise(PG::InsufficientPrivilege)
end
expect(subject).to eq({}) expect(subject).to eq({})
end end
......
...@@ -5,11 +5,12 @@ require 'spec_helper' ...@@ -5,11 +5,12 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::Count::TablesampleCountStrategy do RSpec.describe Gitlab::Database::Count::TablesampleCountStrategy do
before do before do
create_list(:project, 3) create_list(:project, 3)
create_list(:ci_instance_variable, 2)
create(:identity) create(:identity)
create(:group) create(:group)
end end
let(:models) { [Project, Identity, Group, Namespace] } let(:models) { [Project, Ci::InstanceVariable, Identity, Group, Namespace] }
let(:strategy) { described_class.new(models) } let(:strategy) { described_class.new(models) }
subject { strategy.count } subject { strategy.count }
...@@ -20,7 +21,8 @@ RSpec.describe Gitlab::Database::Count::TablesampleCountStrategy do ...@@ -20,7 +21,8 @@ RSpec.describe Gitlab::Database::Count::TablesampleCountStrategy do
Project => threshold + 1, Project => threshold + 1,
Identity => threshold - 1, Identity => threshold - 1,
Group => threshold + 1, Group => threshold + 1,
Namespace => threshold + 1 Namespace => threshold + 1,
Ci::InstanceVariable => threshold + 1
} }
end end
...@@ -43,12 +45,14 @@ RSpec.describe Gitlab::Database::Count::TablesampleCountStrategy do ...@@ -43,12 +45,14 @@ RSpec.describe Gitlab::Database::Count::TablesampleCountStrategy do
expect(Project).not_to receive(:count) expect(Project).not_to receive(:count)
expect(Group).not_to receive(:count) expect(Group).not_to receive(:count)
expect(Namespace).not_to receive(:count) expect(Namespace).not_to receive(:count)
expect(Ci::InstanceVariable).not_to receive(:count)
result = subject result = subject
expect(result[Project]).to eq(3) expect(result[Project]).to eq(3)
expect(result[Group]).to eq(1) expect(result[Group]).to eq(1)
# 1-Group, 3 namespaces for each project and 3 project namespaces for each project # 1-Group, 3 namespaces for each project and 3 project namespaces for each project
expect(result[Namespace]).to eq(7) expect(result[Namespace]).to eq(7)
expect(result[Ci::InstanceVariable]).to eq(2)
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