Commit 004ace1c authored by Marius Bobin's avatar Marius Bobin Committed by Grzegorz Bizon

Refine bulk insert tags

parent 07d68c69
...@@ -194,7 +194,7 @@ end ...@@ -194,7 +194,7 @@ end
# State machine # State machine
gem 'state_machines-activerecord', '~> 0.8.0' gem 'state_machines-activerecord', '~> 0.8.0'
# Issue tags # CI domain tags
gem 'acts-as-taggable-on', '~> 9.0' gem 'acts-as-taggable-on', '~> 9.0'
# Background jobs # Background jobs
......
...@@ -420,6 +420,10 @@ module Ci ...@@ -420,6 +420,10 @@ module Ci
true true
end end
def save_tags
super unless Thread.current['ci_bulk_insert_tags']
end
def archived? def archived?
return true if degenerated? return true if degenerated?
......
...@@ -221,8 +221,8 @@ class CommitStatus < Ci::ApplicationRecord ...@@ -221,8 +221,8 @@ class CommitStatus < Ci::ApplicationRecord
false false
end end
def self.bulk_insert_tags!(statuses, tag_list_by_build) def self.bulk_insert_tags!(statuses)
Gitlab::Ci::Tags::BulkInsert.new(statuses, tag_list_by_build).insert! Gitlab::Ci::Tags::BulkInsert.new(statuses).insert!
end end
def locking_enabled? def locking_enabled?
......
...@@ -11,11 +11,11 @@ module Gitlab ...@@ -11,11 +11,11 @@ module Gitlab
def perform! def perform!
logger.instrument_with_sql(:pipeline_save) do logger.instrument_with_sql(:pipeline_save) do
BulkInsertableAssociations.with_bulk_insert do BulkInsertableAssociations.with_bulk_insert do
tags = extract_tag_list_by_status with_bulk_insert_tags do
pipeline.transaction do
pipeline.transaction do pipeline.save!
pipeline.save! CommitStatus.bulk_insert_tags!(statuses) if bulk_insert_tags?
CommitStatus.bulk_insert_tags!(statuses, tags) if bulk_insert_tags? end
end end
end end
end end
...@@ -29,32 +29,26 @@ module Gitlab ...@@ -29,32 +29,26 @@ module Gitlab
private private
def statuses def bulk_insert_tags?
strong_memoize(:statuses) do strong_memoize(:bulk_insert_tags) do
pipeline.stages.flat_map(&:statuses) ::Feature.enabled?(:ci_bulk_insert_tags, project, default_enabled: :yaml)
end end
end end
# We call `job.tag_list=` to assign tags to the jobs from the def with_bulk_insert_tags
# Chain::Seed step which uses the `@tag_list` instance variable to previous = Thread.current['ci_bulk_insert_tags']
# store them on the record. We remove them here because we want to Thread.current['ci_bulk_insert_tags'] = bulk_insert_tags?
# bulk insert them, otherwise they would be inserted and assigned one yield
# by one with callbacks. We must use `remove_instance_variable` ensure
# because having the instance variable defined would still run the callbacks Thread.current['ci_bulk_insert_tags'] = previous
def extract_tag_list_by_status
return {} unless bulk_insert_tags?
statuses.each.with_object({}) do |job, acc|
tag_list = job.clear_memoization(:tag_list)
next unless tag_list
acc[job.name] = tag_list
end
end end
def bulk_insert_tags? def statuses
strong_memoize(:bulk_insert_tags) do strong_memoize(:statuses) do
::Feature.enabled?(:ci_bulk_insert_tags, project, default_enabled: :yaml) pipeline
.stages
.flat_map(&:statuses)
.select { |status| status.respond_to?(:tag_list) }
end end
end end
end end
......
...@@ -4,12 +4,13 @@ module Gitlab ...@@ -4,12 +4,13 @@ module Gitlab
module Ci module Ci
module Tags module Tags
class BulkInsert class BulkInsert
include Gitlab::Utils::StrongMemoize
TAGGINGS_BATCH_SIZE = 1000 TAGGINGS_BATCH_SIZE = 1000
TAGS_BATCH_SIZE = 500 TAGS_BATCH_SIZE = 500
def initialize(statuses, tag_list_by_status) def initialize(statuses)
@statuses = statuses @statuses = statuses
@tag_list_by_status = tag_list_by_status
end end
def insert! def insert!
...@@ -20,7 +21,18 @@ module Gitlab ...@@ -20,7 +21,18 @@ module Gitlab
private private
attr_reader :statuses, :tag_list_by_status attr_reader :statuses
def tag_list_by_status
strong_memoize(:tag_list_by_status) do
statuses.each.with_object({}) do |status, acc|
tag_list = status.tag_list
next unless tag_list
acc[status] = tag_list
end
end
end
def persist_build_tags! def persist_build_tags!
all_tags = tag_list_by_status.values.flatten.uniq.reject(&:blank?) all_tags = tag_list_by_status.values.flatten.uniq.reject(&:blank?)
...@@ -54,7 +66,7 @@ module Gitlab ...@@ -54,7 +66,7 @@ module Gitlab
def build_taggings_attributes(tag_records_by_name) def build_taggings_attributes(tag_records_by_name)
taggings = statuses.flat_map do |status| taggings = statuses.flat_map do |status|
tag_list = tag_list_by_status[status.name] tag_list = tag_list_by_status[status]
next unless tag_list next unless tag_list
tags = tag_records_by_name.values_at(*tag_list) tags = tag_records_by_name.values_at(*tag_list)
......
...@@ -79,12 +79,11 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do ...@@ -79,12 +79,11 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do
it 'extracts an empty tag list' do it 'extracts an empty tag list' do
expect(CommitStatus) expect(CommitStatus)
.to receive(:bulk_insert_tags!) .to receive(:bulk_insert_tags!)
.with(stage.statuses, {}) .with([job])
.and_call_original .and_call_original
step.perform! step.perform!
expect(job.instance_variable_defined?(:@tag_list)).to be_falsey
expect(job).to be_persisted expect(job).to be_persisted
expect(job.tag_list).to eq([]) expect(job.tag_list).to eq([])
end end
...@@ -98,14 +97,13 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do ...@@ -98,14 +97,13 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do
it 'bulk inserts tags' do it 'bulk inserts tags' do
expect(CommitStatus) expect(CommitStatus)
.to receive(:bulk_insert_tags!) .to receive(:bulk_insert_tags!)
.with(stage.statuses, { job.name => %w[tag1 tag2] }) .with([job])
.and_call_original .and_call_original
step.perform! step.perform!
expect(job.instance_variable_defined?(:@tag_list)).to be_falsey
expect(job).to be_persisted expect(job).to be_persisted
expect(job.tag_list).to match_array(%w[tag1 tag2]) expect(job.reload.tag_list).to match_array(%w[tag1 tag2])
end end
end end
...@@ -120,7 +118,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do ...@@ -120,7 +118,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do
step.perform! step.perform!
expect(job.instance_variable_defined?(:@tag_list)).to be_truthy
expect(job).to be_persisted expect(job).to be_persisted
expect(job.reload.tag_list).to match_array(%w[tag1 tag2]) expect(job.reload.tag_list).to match_array(%w[tag1 tag2])
end end
......
...@@ -5,27 +5,37 @@ require 'spec_helper' ...@@ -5,27 +5,37 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Tags::BulkInsert do RSpec.describe Gitlab::Ci::Tags::BulkInsert do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be_with_refind(:job) { create(:ci_build, :unique_name, pipeline: pipeline, project: project) } let_it_be_with_refind(:job) { create(:ci_build, :unique_name, pipeline: pipeline) }
let_it_be_with_refind(:other_job) { create(:ci_build, :unique_name, pipeline: pipeline, project: project) } let_it_be_with_refind(:other_job) { create(:ci_build, :unique_name, pipeline: pipeline) }
let_it_be_with_refind(:bridge) { create(:ci_bridge, pipeline: pipeline, project: project) }
let(:statuses) { [job, bridge, other_job] } let(:statuses) { [job, other_job] }
subject(:service) { described_class.new(statuses, tags_list) } subject(:service) { described_class.new(statuses) }
describe 'gem version' do
let(:acceptable_version) { '9.0.0' }
let(:error_message) do
<<~MESSAGE
A mechanism depending on internals of 'act-as-taggable-on` has been designed
to bulk insert tags for Ci::Build records.
Please review the code carefully before updating the gem version
https://gitlab.com/gitlab-org/gitlab/-/issues/350053
MESSAGE
end
it { expect(ActsAsTaggableOn::VERSION).to eq(acceptable_version), error_message }
end
describe '#insert!' do describe '#insert!' do
context 'without tags' do context 'without tags' do
let(:tags_list) { {} }
it { expect(service.insert!).to be_falsey } it { expect(service.insert!).to be_falsey }
end end
context 'with tags' do context 'with tags' do
let(:tags_list) do before do
{ job.tag_list = %w[tag1 tag2]
job.name => %w[tag1 tag2], other_job.tag_list = %w[tag2 tag3 tag4]
other_job.name => %w[tag2 tag3 tag4]
}
end end
it 'persists tags' do it 'persists tags' do
...@@ -35,5 +45,18 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do ...@@ -35,5 +45,18 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do
expect(other_job.reload.tag_list).to match_array(%w[tag2 tag3 tag4]) expect(other_job.reload.tag_list).to match_array(%w[tag2 tag3 tag4])
end end
end end
context 'with tags for only one job' do
before do
job.tag_list = %w[tag1 tag2]
end
it 'persists tags' do
expect(service.insert!).to be_truthy
expect(job.reload.tag_list).to match_array(%w[tag1 tag2])
expect(other_job.reload.tag_list).to be_empty
end
end
end end
end end
...@@ -961,18 +961,17 @@ RSpec.describe CommitStatus do ...@@ -961,18 +961,17 @@ RSpec.describe CommitStatus do
describe '.bulk_insert_tags!' do describe '.bulk_insert_tags!' do
let(:statuses) { double('statuses') } let(:statuses) { double('statuses') }
let(:tag_list_by_build) { double('tag list') }
let(:inserter) { double('inserter') } let(:inserter) { double('inserter') }
it 'delegates to bulk insert class' do it 'delegates to bulk insert class' do
expect(Gitlab::Ci::Tags::BulkInsert) expect(Gitlab::Ci::Tags::BulkInsert)
.to receive(:new) .to receive(:new)
.with(statuses, tag_list_by_build) .with(statuses)
.and_return(inserter) .and_return(inserter)
expect(inserter).to receive(:insert!) expect(inserter).to receive(:insert!)
described_class.bulk_insert_tags!(statuses, tag_list_by_build) described_class.bulk_insert_tags!(statuses)
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