summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb')
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb147
1 files changed, 147 insertions, 0 deletions
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
index 86f79b213ae..44ef0b307fe 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
@@ -480,6 +480,153 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
end
end
+ describe '#finalize_backfilling_partitioned_table' do
+ let(:source_table) { 'todos' }
+ let(:source_column) { 'id' }
+
+ context 'when the table is not allowed' do
+ let(:source_table) { :this_table_is_not_allowed }
+
+ it 'raises an error' do
+ expect(migration).to receive(:assert_table_is_allowed).with(source_table).and_call_original
+
+ expect do
+ migration.finalize_backfilling_partitioned_table source_table
+ end.to raise_error(/#{source_table} is not allowed for use/)
+ end
+ end
+
+ context 'when the partitioned table does not exist' do
+ it 'raises an error' do
+ expect(migration).to receive(:table_exists?).with(partitioned_table).and_return(false)
+
+ expect do
+ migration.finalize_backfilling_partitioned_table source_table
+ end.to raise_error(/could not find partitioned table for #{source_table}/)
+ end
+ end
+
+ context 'finishing pending background migration jobs' do
+ let(:source_table_double) { double('table name') }
+ let(:raw_arguments) { [1, 50_000, source_table_double, partitioned_table, source_column] }
+
+ before do
+ allow(migration).to receive(:table_exists?).with(partitioned_table).and_return(true)
+ allow(migration).to receive(:copy_missed_records)
+ allow(migration).to receive(:execute).with(/VACUUM/)
+ end
+
+ it 'finishes remaining jobs for the correct table' do
+ expect_next_instance_of(described_class::JobArguments) do |job_arguments|
+ expect(job_arguments).to receive(:source_table_name).and_call_original
+ end
+
+ expect(Gitlab::BackgroundMigration).to receive(:steal)
+ .with(described_class::MIGRATION_CLASS_NAME)
+ .and_yield(raw_arguments)
+
+ expect(source_table_double).to receive(:==).with(source_table.to_s)
+
+ migration.finalize_backfilling_partitioned_table source_table
+ end
+ end
+
+ context 'when there is missed data' do
+ let(:partitioned_model) { Class.new(ActiveRecord::Base) }
+ let(:timestamp) { Time.utc(2019, 12, 1, 12).round }
+ let!(:todo1) { create(:todo, created_at: timestamp, updated_at: timestamp) }
+ let!(:todo2) { create(:todo, created_at: timestamp, updated_at: timestamp) }
+ let!(:todo3) { create(:todo, created_at: timestamp, updated_at: timestamp) }
+ let!(:todo4) { create(:todo, created_at: timestamp, updated_at: timestamp) }
+
+ let!(:pending_job1) do
+ create(:background_migration_job,
+ class_name: described_class::MIGRATION_CLASS_NAME,
+ arguments: [todo1.id, todo2.id, source_table, partitioned_table, source_column])
+ end
+
+ let!(:pending_job2) do
+ create(:background_migration_job,
+ class_name: described_class::MIGRATION_CLASS_NAME,
+ arguments: [todo3.id, todo3.id, source_table, partitioned_table, source_column])
+ end
+
+ let!(:succeeded_job) do
+ create(:background_migration_job, :succeeded,
+ class_name: described_class::MIGRATION_CLASS_NAME,
+ arguments: [todo4.id, todo4.id, source_table, partitioned_table, source_column])
+ end
+
+ before do
+ partitioned_model.primary_key = :id
+ partitioned_model.table_name = partitioned_table
+
+ allow(migration).to receive(:queue_background_migration_jobs_by_range_at_intervals)
+
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+
+ allow(Gitlab::BackgroundMigration).to receive(:steal)
+ allow(migration).to receive(:execute).with(/VACUUM/)
+ end
+
+ it 'idempotently cleans up after failed background migrations' do
+ expect(partitioned_model.count).to eq(0)
+
+ partitioned_model.insert!(todo2.attributes)
+
+ expect_next_instance_of(Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable) do |backfill|
+ allow(backfill).to receive(:transaction_open?).and_return(false)
+
+ expect(backfill).to receive(:perform)
+ .with(todo1.id, todo2.id, source_table, partitioned_table, source_column)
+ .and_call_original
+
+ expect(backfill).to receive(:perform)
+ .with(todo3.id, todo3.id, source_table, partitioned_table, source_column)
+ .and_call_original
+ end
+
+ migration.finalize_backfilling_partitioned_table source_table
+
+ expect(partitioned_model.count).to eq(3)
+
+ [todo1, todo2, todo3].each do |original|
+ copy = partitioned_model.find(original.id)
+ expect(copy.attributes).to eq(original.attributes)
+ end
+
+ expect(partitioned_model.find_by_id(todo4.id)).to be_nil
+
+ [pending_job1, pending_job2].each do |job|
+ expect(job.reload).to be_succeeded
+ end
+ end
+
+ it 'raises an error if no job tracking records are marked as succeeded' do
+ expect_next_instance_of(Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable) do |backfill|
+ allow(backfill).to receive(:transaction_open?).and_return(false)
+
+ expect(backfill).to receive(:perform).and_return(0)
+ end
+
+ expect do
+ migration.finalize_backfilling_partitioned_table source_table
+ end.to raise_error(/failed to update tracking record/)
+ end
+
+ it 'vacuums the table after loading is complete' do
+ expect_next_instance_of(Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable) do |backfill|
+ allow(backfill).to receive(:perform).and_return(1)
+ end
+
+ expect(migration).to receive(:disable_statement_timeout).and_call_original
+ expect(migration).to receive(:execute).with("VACUUM FREEZE ANALYZE #{partitioned_table}")
+
+ migration.finalize_backfilling_partitioned_table source_table
+ end
+ end
+ end
+
def filter_columns_by_name(columns, names)
columns.reject { |c| names.include?(c.name) }
end