Commit dc96209c authored by Grzegorz Bizon's avatar Grzegorz Bizon

Improve pending builds migration to use better batching

parent 30e8e272
...@@ -3,41 +3,48 @@ ...@@ -3,41 +3,48 @@
class CopyPendingBuildsToPendingBuildsTable < ActiveRecord::Migration[6.0] class CopyPendingBuildsToPendingBuildsTable < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
BUILDS_MAX_SIZE = 1 << (32 - 1) BATCH_SIZE = 10000
PENDING_BUILDS_BATCH_SIZE = 1000
PENDING_BUILDS_MAX_BATCHES = BUILDS_MAX_SIZE / PENDING_BUILDS_BATCH_SIZE
disable_ddl_transaction! disable_ddl_transaction!
class Build < ActiveRecord::Base
include EachBatch
self.table_name = 'ci_builds'
self.inheritance_column = :_type_disabled
scope :pending, -> do
where(status: 'pending')
.where(type: 'Ci::Build')
.where('updated_at > ?', 24.hours.ago)
.order('id ASC')
end
end
def up def up
1.step do |i| Build.pending.limit(1).pluck(:id).first.try do |id|
inserts = execute <<~SQL Build.where('id >= ?', id).each_batch(of: BATCH_SIZE) do |batch|
WITH pending_builds AS ( min_id, max_id = batch.pluck('MIN(id), MAX(id)').first
SELECT id,
project_id execute <<~SQL
FROM ci_builds WITH pending_builds AS (
WHERE status = 'pending' SELECT id,
AND type = 'Ci::Build' project_id
AND NOT EXISTS ( FROM ci_builds
SELECT 1 FROM ci_pending_builds WHERE status = 'pending'
WHERE ci_pending_builds.build_id = ci_builds.id AND type = 'Ci::Build'
) AND NOT EXISTS (
LIMIT #{PENDING_BUILDS_BATCH_SIZE} SELECT 1 FROM ci_pending_builds
), inserts AS ( WHERE ci_pending_builds.build_id = ci_builds.id
)
AND id BETWEEN #{min_id} AND #{max_id}
)
INSERT INTO ci_pending_builds (build_id, project_id) INSERT INTO ci_pending_builds (build_id, project_id)
SELECT id, SELECT id,
project_id project_id
FROM pending_builds FROM pending_builds
ON CONFLICT DO NOTHING ON CONFLICT DO NOTHING
RETURNING id SQL
)
SELECT COUNT(*) FROM inserts;
SQL
break if inserts.values.flatten.first.to_i == 0
if i > PENDING_BUILDS_MAX_BATCHES
raise 'There are too many pending builds in your database! Aborting.'
end end
end end
end end
......
...@@ -13,13 +13,14 @@ RSpec.describe CopyPendingBuildsToPendingBuildsTable do ...@@ -13,13 +13,14 @@ RSpec.describe CopyPendingBuildsToPendingBuildsTable do
namespaces.create!(id: 123, name: 'sample', path: 'sample') namespaces.create!(id: 123, name: 'sample', path: 'sample')
projects.create!(id: 123, name: 'sample', path: 'sample', namespace_id: 123) projects.create!(id: 123, name: 'sample', path: 'sample', namespace_id: 123)
builds.create!(id: 1, project_id: 123, status: 'pending', type: 'Ci::Build') builds.create!(id: 1, project_id: 123, status: 'pending', type: 'Ci::Build', created_at: 4.days.ago, updated_at: 4.days.ago)
builds.create!(id: 2, project_id: 123, status: 'pending', type: 'GenericCommitStatus') builds.create!(id: 2, project_id: 123, status: 'pending', type: 'Ci::Build')
builds.create!(id: 3, project_id: 123, status: 'pending', type: 'GenericCommitStatus') builds.create!(id: 3, project_id: 123, status: 'pending', type: 'GenericCommitStatus')
builds.create!(id: 4, project_id: 123, status: 'pending', type: 'Ci::Bridge') builds.create!(id: 4, project_id: 123, status: 'pending', type: 'GenericCommitStatus')
builds.create!(id: 5, project_id: 123, status: 'running', type: 'Ci::Build') builds.create!(id: 5, project_id: 123, status: 'pending', type: 'Ci::Bridge')
builds.create!(id: 6, project_id: 123, status: 'pending', type: 'Ci::Build') builds.create!(id: 6, project_id: 123, status: 'pending', type: 'Ci::Build')
builds.create!(id: 7, project_id: 123, status: 'created', type: 'Ci::Build') builds.create!(id: 7, project_id: 123, status: 'running', type: 'Ci::Build')
builds.create!(id: 8, project_id: 123, status: 'created', type: 'Ci::Build')
end end
context 'when there are new pending builds present' do context 'when there are new pending builds present' do
...@@ -27,13 +28,13 @@ RSpec.describe CopyPendingBuildsToPendingBuildsTable do ...@@ -27,13 +28,13 @@ RSpec.describe CopyPendingBuildsToPendingBuildsTable do
migrate! migrate!
expect(queue.all.count).to eq 2 expect(queue.all.count).to eq 2
expect(queue.all.pluck(:build_id)).to match_array([1, 6]) expect(queue.all.pluck(:build_id)).to match_array([2, 6])
end end
end end
context 'when there are pending builds already migrated present' do context 'when there are pending builds already migrated present' do
before do before do
queue.create!(id: 1, build_id: 1, project_id: 123) queue.create!(id: 1, build_id: 2, project_id: 123)
end end
it 'does not copy entries that have already been copied' do it 'does not copy entries that have already been copied' do
...@@ -42,31 +43,33 @@ RSpec.describe CopyPendingBuildsToPendingBuildsTable do ...@@ -42,31 +43,33 @@ RSpec.describe CopyPendingBuildsToPendingBuildsTable do
migrate! migrate!
expect(queue.all.count).to eq 2 expect(queue.all.count).to eq 2
expect(queue.all.pluck(:build_id)).to match_array([1, 6]) expect(queue.all.pluck(:build_id)).to match_array([2, 6])
end end
end end
context 'when there is more than one batch of pending builds to migrate' do context 'when there is more than one batch of pending builds to migrate' do
before do before do
stub_const("#{described_class}::PENDING_BUILDS_BATCH_SIZE", 1) stub_const("#{described_class}::BATCH_SIZE", 2)
end end
it 'correctly migrates data and exits after doing that' do it 'correctly migrates data and exits after doing that' do
migrate! migrate!
expect(queue.all.count).to eq 2 expect(queue.all.count).to eq 2
expect(queue.all.pluck(:build_id)).to match_array([1, 6]) expect(queue.all.pluck(:build_id)).to match_array([2, 6])
end end
end end
context 'when batches limit has been exceeded' do context 'when an old build has been recently updated' do
before do before do
stub_const("#{described_class}::PENDING_BUILDS_BATCH_SIZE", 1) builds.find(1).touch
stub_const("#{described_class}::PENDING_BUILDS_MAX_BATCHES", 1)
end end
it 'raises an error' do it 'migrates it as well' do
expect { migrate! }.to raise_error(StandardError, /too many pending builds/) migrate!
expect(queue.all.count).to eq 3
expect(queue.all.pluck(:build_id)).to match_array([1, 2, 6])
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