Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/loose_foreign_keys/deleted_record_spec.rb')
-rw-r--r--spec/models/loose_foreign_keys/deleted_record_spec.rb149
1 files changed, 133 insertions, 16 deletions
diff --git a/spec/models/loose_foreign_keys/deleted_record_spec.rb b/spec/models/loose_foreign_keys/deleted_record_spec.rb
index cd5068bdb52..07ffff746a5 100644
--- a/spec/models/loose_foreign_keys/deleted_record_spec.rb
+++ b/spec/models/loose_foreign_keys/deleted_record_spec.rb
@@ -5,31 +5,148 @@ require 'spec_helper'
RSpec.describe LooseForeignKeys::DeletedRecord, type: :model do
let_it_be(:table) { 'public.projects' }
- let_it_be(:deleted_record_1) { described_class.create!(partition: 1, fully_qualified_table_name: table, primary_key_value: 5) }
- let_it_be(:deleted_record_2) { described_class.create!(partition: 1, fully_qualified_table_name: table, primary_key_value: 1) }
- let_it_be(:deleted_record_3) { described_class.create!(partition: 1, fully_qualified_table_name: 'public.other_table', primary_key_value: 3) }
- let_it_be(:deleted_record_4) { described_class.create!(partition: 1, fully_qualified_table_name: table, primary_key_value: 1) } # duplicate
+ describe 'class methods' do
+ let_it_be(:deleted_record_1) { described_class.create!(fully_qualified_table_name: table, primary_key_value: 5) }
+ let_it_be(:deleted_record_2) { described_class.create!(fully_qualified_table_name: table, primary_key_value: 1) }
+ let_it_be(:deleted_record_3) { described_class.create!(fully_qualified_table_name: 'public.other_table', primary_key_value: 3) }
+ let_it_be(:deleted_record_4) { described_class.create!(fully_qualified_table_name: table, primary_key_value: 1) } # duplicate
- describe '.load_batch_for_table' do
- it 'loads records and orders them by creation date' do
- records = described_class.load_batch_for_table(table, 10)
+ describe '.load_batch_for_table' do
+ it 'loads records and orders them by creation date' do
+ records = described_class.load_batch_for_table(table, 10)
- expect(records).to eq([deleted_record_1, deleted_record_2, deleted_record_4])
+ expect(records).to eq([deleted_record_1, deleted_record_2, deleted_record_4])
+ end
+
+ it 'supports configurable batch size' do
+ records = described_class.load_batch_for_table(table, 2)
+
+ expect(records).to eq([deleted_record_1, deleted_record_2])
+ end
end
- it 'supports configurable batch size' do
- records = described_class.load_batch_for_table(table, 2)
+ describe '.mark_records_processed' do
+ it 'updates all records' do
+ records = described_class.load_batch_for_table(table, 10)
+ described_class.mark_records_processed(records)
- expect(records).to eq([deleted_record_1, deleted_record_2])
+ expect(described_class.status_pending.count).to eq(1)
+ expect(described_class.status_processed.count).to eq(3)
+ end
end
end
- describe '.mark_records_processed' do
- it 'updates all records' do
- described_class.mark_records_processed([deleted_record_1, deleted_record_2, deleted_record_4])
+ describe 'sliding_list partitioning' do
+ let(:connection) { described_class.connection }
+ let(:partition_manager) { Gitlab::Database::Partitioning::PartitionManager.new(described_class) }
+
+ describe 'next_partition_if callback' do
+ let(:active_partition) { described_class.partitioning_strategy.active_partition.value }
+
+ subject(:value) { described_class.partitioning_strategy.next_partition_if.call(active_partition) }
+
+ context 'when the partition is empty' do
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when the partition has records' do
+ before do
+ described_class.create!(fully_qualified_table_name: 'public.table', primary_key_value: 1, status: :processed)
+ described_class.create!(fully_qualified_table_name: 'public.table', primary_key_value: 2, status: :pending)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when the first record of the partition is older than PARTITION_DURATION' do
+ before do
+ described_class.create!(
+ fully_qualified_table_name: 'public.table',
+ primary_key_value: 1,
+ created_at: (described_class::PARTITION_DURATION + 1.day).ago)
+
+ described_class.create!(fully_qualified_table_name: 'public.table', primary_key_value: 2)
+ end
+
+ it { is_expected.to eq(true) }
+
+ context 'when the lfk_automatic_partition_creation FF is off' do
+ before do
+ stub_feature_flags(lfk_automatic_partition_creation: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+ end
+
+ describe 'detach_partition_if callback' do
+ let(:active_partition) { described_class.partitioning_strategy.active_partition.value }
+
+ subject(:value) { described_class.partitioning_strategy.detach_partition_if.call(active_partition) }
+
+ context 'when the partition contains unprocessed records' do
+ before do
+ described_class.create!(fully_qualified_table_name: 'public.table', primary_key_value: 1, status: :processed)
+ described_class.create!(fully_qualified_table_name: 'public.table', primary_key_value: 2, status: :pending)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when the partition contains only processed records' do
+ before do
+ described_class.create!(fully_qualified_table_name: 'public.table', primary_key_value: 1, status: :processed)
+ described_class.create!(fully_qualified_table_name: 'public.table', primary_key_value: 2, status: :processed)
+ end
+
+ it { is_expected.to eq(true) }
+
+ context 'when the lfk_automatic_partition_dropping FF is off' do
+ before do
+ stub_feature_flags(lfk_automatic_partition_dropping: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+ end
+
+ describe 'the behavior of the strategy' do
+ it 'moves records to new partitions as time passes', :freeze_time do
+ # We start with partition 1
+ expect(described_class.partitioning_strategy.current_partitions.map(&:value)).to eq([1])
+
+ # it's not a day old yet so no new partitions are created
+ partition_manager.sync_partitions
+
+ expect(described_class.partitioning_strategy.current_partitions.map(&:value)).to eq([1])
+
+ # add one record so the next partition will be created
+ described_class.create!(fully_qualified_table_name: 'public.table', primary_key_value: 1)
+
+ # after traveling forward a day
+ travel(described_class::PARTITION_DURATION + 1.second)
+
+ # a new partition is created
+ partition_manager.sync_partitions
+
+ expect(described_class.partitioning_strategy.current_partitions.map(&:value)).to eq([1, 2])
+
+ # and we can insert to the new partition
+ expect { described_class.create!(fully_qualified_table_name: table, primary_key_value: 5) }.not_to raise_error
+
+ # after processing old records
+ LooseForeignKeys::DeletedRecord.for_partition(1).update_all(status: :processed)
+
+ partition_manager.sync_partitions
+
+ # the old one is removed
+ expect(described_class.partitioning_strategy.current_partitions.map(&:value)).to eq([2])
- expect(described_class.status_pending.count).to eq(1)
- expect(described_class.status_processed.count).to eq(3)
+ # and we only have the newly created partition left.
+ expect(described_class.count).to eq(1)
+ end
end
end
end