Commit ef4d93f8 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'mo-update-shared-runners-query' into 'master'

Fetch pending builds with denormalized shared runners

See merge request gitlab-org/gitlab!66082
parents c52f5671 48f66af8
...@@ -10,6 +10,7 @@ module Ci ...@@ -10,6 +10,7 @@ module Ci
scope :ref_protected, -> { where(protected: true) } scope :ref_protected, -> { where(protected: true) }
scope :queued_before, ->(time) { where(arel_table[:created_at].lt(time)) } scope :queued_before, ->(time) { where(arel_table[:created_at].lt(time)) }
scope :with_instance_runners, -> { where(instance_runners_enabled: true) }
def self.upsert_from_build!(build) def self.upsert_from_build!(build)
entry = self.new(args_from_build(build)) entry = self.new(args_from_build(build))
......
...@@ -53,6 +53,10 @@ module Ci ...@@ -53,6 +53,10 @@ module Ci
relation.pluck(:id) relation.pluck(:id)
end end
def use_denormalized_shared_runners_data?
false
end
private private
def running_builds_for_shared_runners def running_builds_for_shared_runners
......
...@@ -11,25 +11,9 @@ module Ci ...@@ -11,25 +11,9 @@ module Ci
# rubocop:disable CodeReuse/ActiveRecord # rubocop:disable CodeReuse/ActiveRecord
def builds_for_shared_runner def builds_for_shared_runner
relation = new_builds shared_builds = builds_available_for_shared_runners
# don't run projects which have not enabled shared runners and builds
.joins('INNER JOIN projects ON ci_pending_builds.project_id = projects.id')
.where(projects: { shared_runners_enabled: true, pending_delete: false })
.joins('LEFT JOIN project_features ON ci_pending_builds.project_id = project_features.project_id')
.where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0')
if Feature.enabled?(:ci_queueing_disaster_recovery_disable_fair_scheduling, runner, type: :ops, default_enabled: :yaml) builds_ordered_for_shared_runners(shared_builds)
# if disaster recovery is enabled, we fallback to FIFO scheduling
relation.order('ci_pending_builds.build_id ASC')
else
# Implement fair scheduling
# this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all
relation
.with(running_builds_for_shared_runners_cte.to_arel)
.joins("LEFT JOIN project_builds ON ci_pending_builds.project_id = project_builds.project_id")
.order(Arel.sql('COALESCE(project_builds.running_builds, 0) ASC'), 'ci_pending_builds.build_id ASC')
end
end end
def builds_matching_tag_ids(relation, ids) def builds_matching_tag_ids(relation, ids)
...@@ -52,8 +36,40 @@ module Ci ...@@ -52,8 +36,40 @@ module Ci
relation.pluck(:build_id) relation.pluck(:build_id)
end end
def use_denormalized_shared_runners_data?
::Feature.enabled?(:ci_queueing_denormalize_shared_runners_information, runner, type: :development, default_enabled: :yaml)
end
private private
def builds_available_for_shared_runners
if use_denormalized_shared_runners_data?
new_builds.with_instance_runners
else
new_builds
# don't run projects which have not enabled shared runners and builds
.joins('INNER JOIN projects ON ci_pending_builds.project_id = projects.id')
.where(projects: { shared_runners_enabled: true, pending_delete: false })
.joins('LEFT JOIN project_features ON ci_pending_builds.project_id = project_features.project_id')
.where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0')
end
end
def builds_ordered_for_shared_runners(relation)
if Feature.enabled?(:ci_queueing_disaster_recovery_disable_fair_scheduling, runner, type: :ops, default_enabled: :yaml)
# if disaster recovery is enabled, we fallback to FIFO scheduling
relation.order('ci_pending_builds.build_id ASC')
else
# Implement fair scheduling
# this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all
relation
.with(running_builds_for_shared_runners_cte.to_arel)
.joins("LEFT JOIN project_builds ON ci_pending_builds.project_id = project_builds.project_id")
.order(Arel.sql('COALESCE(project_builds.running_builds, 0) ASC'), 'ci_pending_builds.build_id ASC')
end
end
def running_builds_for_shared_runners_cte def running_builds_for_shared_runners_cte
running_builds = ::Ci::RunningBuild running_builds = ::Ci::RunningBuild
.instance_type .instance_type
......
---
name: ci_queueing_denormalize_shared_runners_information
introduced_by_url:
rollout_issue_url:
milestone: '14.2'
type: development
group: group::pipeline execution
default_enabled: false
...@@ -19,6 +19,13 @@ module EE ...@@ -19,6 +19,13 @@ module EE
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def enforce_minutes_based_on_cost_factors(relation) def enforce_minutes_based_on_cost_factors(relation)
if strategy.use_denormalized_shared_runners_data?
# If shared runners information is denormalized then the query does not include the join
# with `projects` anymore, so we need to add it until we use denormalized ci minutes
relation = relation.joins('INNER JOIN projects ON ci_pending_builds.project_id = projects.id')
end
visibility_relation = ::CommitStatus.where( visibility_relation = ::CommitStatus.where(
projects: { visibility_level: runner.visibility_levels_without_minutes_quota }) projects: { visibility_level: runner.visibility_levels_without_minutes_quota })
......
...@@ -281,6 +281,20 @@ RSpec.describe Ci::RegisterJobService, '#execute' do ...@@ -281,6 +281,20 @@ RSpec.describe Ci::RegisterJobService, '#execute' do
stub_feature_flags(ci_pending_builds_queue_source: true) stub_feature_flags(ci_pending_builds_queue_source: true)
end end
include_examples 'namespace minutes quota' context 'with ci_queueing_denormalize_shared_runners_information enabled' do
before do
stub_feature_flags(ci_queueing_denormalize_shared_runners_information: true)
end
include_examples 'namespace minutes quota'
end
context 'with ci_queueing_denormalize_shared_runners_information disabled' do
before do
stub_feature_flags(ci_queueing_denormalize_shared_runners_information: false)
end
include_examples 'namespace minutes quota'
end
end end
end end
...@@ -14,6 +14,28 @@ RSpec.describe Ci::PendingBuild do ...@@ -14,6 +14,28 @@ RSpec.describe Ci::PendingBuild do
it { is_expected.to belong_to :namespace } it { is_expected.to belong_to :namespace }
end end
describe 'scopes' do
describe '.with_instance_runners' do
subject(:pending_builds) { described_class.with_instance_runners }
let!(:pending_build_1) { create(:ci_pending_build, instance_runners_enabled: false) }
context 'when pending builds cannot be picked up by runner' do
it 'returns an empty collection of pending builds' do
expect(pending_builds).to be_empty
end
end
context 'when pending builds can be picked up by runner' do
let!(:pending_build_2) { create(:ci_pending_build) }
it 'returns matching pending builds' do
expect(pending_builds).to contain_exactly(pending_build_2)
end
end
end
end
describe '.upsert_from_build!' do describe '.upsert_from_build!' do
context 'another pending entry does not exist' do context 'another pending entry does not exist' do
it 'creates a new pending entry' do it 'creates a new pending entry' do
......
...@@ -90,6 +90,9 @@ module Ci ...@@ -90,6 +90,9 @@ module Ci
context 'allow shared runners' do context 'allow shared runners' do
before do before do
project.update!(shared_runners_enabled: true) project.update!(shared_runners_enabled: true)
pipeline.reload
pending_job.reload
pending_job.create_queuing_entry!
end end
context 'for multiple builds' do context 'for multiple builds' do
...@@ -703,7 +706,21 @@ module Ci ...@@ -703,7 +706,21 @@ module Ci
stub_feature_flags(ci_pending_builds_queue_source: true) stub_feature_flags(ci_pending_builds_queue_source: true)
end end
include_examples 'handles runner assignment' context 'with ci_queueing_denormalize_shared_runners_information enabled' do
before do
stub_feature_flags(ci_queueing_denormalize_shared_runners_information: true)
end
include_examples 'handles runner assignment'
end
context 'with ci_queueing_denormalize_shared_runners_information disabled' do
before do
stub_feature_flags(ci_queueing_denormalize_shared_runners_information: false)
end
include_examples 'handles runner assignment'
end
end end
context 'when not using pending builds table' do context 'when not using pending builds table' do
...@@ -777,6 +794,11 @@ module Ci ...@@ -777,6 +794,11 @@ module Ci
end end
context 'when shared runner is used' do context 'when shared runner is used' do
before do
pending_job.reload
pending_job.create_queuing_entry!
end
let(:runner) { create(:ci_runner, :instance, tag_list: %w(tag1 tag2)) } let(:runner) { create(:ci_runner, :instance, tag_list: %w(tag1 tag2)) }
let(:expected_shared_runner) { true } let(:expected_shared_runner) { true }
let(:expected_shard) { ::Gitlab::Ci::Queue::Metrics::DEFAULT_METRICS_SHARD } let(:expected_shard) { ::Gitlab::Ci::Queue::Metrics::DEFAULT_METRICS_SHARD }
......
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