class CleanupBuildStageMigration < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false TMP_INDEX = 'tmp_id_stage_partial_null_index'.freeze disable_ddl_transaction! class Build < ActiveRecord::Base include EachBatch self.table_name = 'ci_builds' self.inheritance_column = :_type_disabled end def up disable_statement_timeout do ## # We steal from the background migrations queue to catch up with the # scheduled migrations set. # Gitlab::BackgroundMigration.steal('MigrateBuildStage') ## # We add temporary index, to make iteration over batches more performant. # Conditional here is to avoid the need of doing that in a separate # migration file to make this operation idempotent. # unless index_exists_by_name?(:ci_builds, TMP_INDEX) add_concurrent_index(:ci_builds, :id, where: 'stage_id IS NULL', name: TMP_INDEX) end ## # We check if there are remaining rows that should be migrated (for example # if Sidekiq / Redis fails / is restarted, what could result in not all # background migrations being executed correctly. # # We migrate remaining rows synchronously in a blocking way, to make sure # that when this migration is done we are confident that all rows are # already migrated. # Build.where('stage_id IS NULL').each_batch(of: 50) do |batch| range = batch.pluck('MIN(id)', 'MAX(id)').first Gitlab::BackgroundMigration::MigrateBuildStage.new.perform(*range) end ## # We remove temporary index, because it is not required during standard # operations and runtime. # remove_concurrent_index_by_name(:ci_builds, TMP_INDEX) end end def down if index_exists_by_name?(:ci_builds, TMP_INDEX) disable_statement_timeout do remove_concurrent_index_by_name(:ci_builds, TMP_INDEX) end end end end