Commit c8b84ced authored by Simon Tomlinson's avatar Simon Tomlinson Committed by Patrick Bair

Prune database partitions older than a retention period

When a monthly-partitioned model is specified with
`partition_by :some_column, retain_for: 2.months`
this feature will detach partitions that are more than two months in the
past. A week later, a second worker will drop the tables that were
previously detached.

Changelog: added
parent bd19fdc4
# frozen_string_literal: true
module Postgresql
class DetachedPartition < ApplicationRecord
scope :ready_to_drop, -> { where('drop_after < ?', Time.current) }
end
end
......@@ -247,6 +247,15 @@
:idempotent: true
:tags:
- :exclude_from_kubernetes
- :name: cronjob:database_drop_detached_partitions
:worker_name: Database::DropDetachedPartitionsWorker
:feature_category: :database
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: cronjob:database_partition_management
:worker_name: Database::PartitionManagementWorker
:feature_category: :database
......
# frozen_string_literal: true
module Database
class DropDetachedPartitionsWorker
include ApplicationWorker
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :database
data_consistency :always
idempotent!
def perform
Gitlab::Database::Partitioning::DetachedPartitionDropper.new.perform
ensure
Gitlab::Database::Partitioning::PartitionMonitoring.new.report_metrics
end
end
end
---
name: drop_detached_partitions
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67056
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337155
milestone: '14.2'
type: development
group: group::database
default_enabled: false
---
name: partition_pruning_dry_run
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65093
rollout_issue_url:
milestone: '14.1'
name: partition_pruning
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67056
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337153
milestone: '14.2'
type: development
group: group::database
default_enabled: false
......@@ -546,6 +546,9 @@ Settings.cron_jobs['update_container_registry_info_worker']['job_class'] = 'Upda
Settings.cron_jobs['postgres_dynamic_partitions_manager'] ||= Settingslogic.new({})
Settings.cron_jobs['postgres_dynamic_partitions_manager']['cron'] ||= '21 */6 * * *'
Settings.cron_jobs['postgres_dynamic_partitions_manager']['job_class'] ||= 'Database::PartitionManagementWorker'
Settings.cron_jobs['postgres_dynamic_partitions_dropper'] ||= Settingslogic.new({})
Settings.cron_jobs['postgres_dynamic_partitions_dropper']['cron'] ||= '45 12 * * *'
Settings.cron_jobs['postgres_dynamic_partitions_dropper']['job_class'] ||= 'Database::DropDetachedPartitionsWorker'
Settings.cron_jobs['ci_platform_metrics_update_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['ci_platform_metrics_update_cron_worker']['cron'] ||= '47 9 * * *'
Settings.cron_jobs['ci_platform_metrics_update_cron_worker']['job_class'] = 'CiPlatformMetricsUpdateCronWorker'
......
# frozen_string_literal: true
class CreateDetachedPartitionsTable < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
def change
create_table_with_constraints :detached_partitions do |t|
t.timestamps_with_timezone null: false
t.datetime_with_timezone :drop_after, null: false
t.text :table_name, null: false
# Postgres identifier names can be up to 63 bytes
# See https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
t.text_limit :table_name, 63
end
end
end
136a375fbd7e1faf25e7f53e0677b8525811bd917892efa1430d204453bf2a1d
\ No newline at end of file
......@@ -12521,6 +12521,24 @@ CREATE SEQUENCE design_user_mentions_id_seq
ALTER SEQUENCE design_user_mentions_id_seq OWNED BY design_user_mentions.id;
CREATE TABLE detached_partitions (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
drop_after timestamp with time zone NOT NULL,
table_name text NOT NULL,
CONSTRAINT check_aecee24ba3 CHECK ((char_length(table_name) <= 63))
);
CREATE SEQUENCE detached_partitions_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE detached_partitions_id_seq OWNED BY detached_partitions.id;
CREATE TABLE diff_note_positions (
id bigint NOT NULL,
note_id bigint NOT NULL,
......@@ -20217,6 +20235,8 @@ ALTER TABLE ONLY design_management_versions ALTER COLUMN id SET DEFAULT nextval(
ALTER TABLE ONLY design_user_mentions ALTER COLUMN id SET DEFAULT nextval('design_user_mentions_id_seq'::regclass);
ALTER TABLE ONLY detached_partitions ALTER COLUMN id SET DEFAULT nextval('detached_partitions_id_seq'::regclass);
ALTER TABLE ONLY diff_note_positions ALTER COLUMN id SET DEFAULT nextval('diff_note_positions_id_seq'::regclass);
ALTER TABLE ONLY dora_daily_metrics ALTER COLUMN id SET DEFAULT nextval('dora_daily_metrics_id_seq'::regclass);
......@@ -21533,6 +21553,9 @@ ALTER TABLE ONLY design_management_versions
ALTER TABLE ONLY design_user_mentions
ADD CONSTRAINT design_user_mentions_pkey PRIMARY KEY (id);
ALTER TABLE ONLY detached_partitions
ADD CONSTRAINT detached_partitions_pkey PRIMARY KEY (id);
ALTER TABLE ONLY diff_note_positions
ADD CONSTRAINT diff_note_positions_pkey PRIMARY KEY (id);
# frozen_string_literal: true
module Gitlab
module Database
module Partitioning
class DetachedPartitionDropper
def perform
return unless Feature.enabled?(:drop_detached_partitions, default_enabled: :yaml)
Gitlab::AppLogger.info(message: "Checking for previously detached partitions to drop")
Postgresql::DetachedPartition.ready_to_drop.find_each do |detached_partition|
conn.transaction do
# Another process may have already dropped the table and deleted this entry
next unless (detached_partition = Postgresql::DetachedPartition.lock.find_by(id: detached_partition.id))
unless check_partition_detached?(detached_partition)
Gitlab::AppLogger.error(message: "Attempt to drop attached database partition", partition_name: detached_partition.table_name)
detached_partition.destroy!
next
end
drop_one(detached_partition)
end
rescue StandardError => e
Gitlab::AppLogger.error(message: "Failed to drop previously detached partition",
partition_name: detached_partition.table_name,
exception_class: e.class,
exception_message: e.message)
end
end
private
def drop_one(detached_partition)
conn.transaction do
conn.execute(<<~SQL)
DROP TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.#{conn.quote_table_name(detached_partition.table_name)}
SQL
detached_partition.destroy!
end
Gitlab::AppLogger.info(message: "Dropped previously detached partition", partition_name: detached_partition.table_name)
end
def check_partition_detached?(detached_partition)
# PostgresPartition checks the pg_inherits view, so our partition will only show here if it's still attached
# and thus should not be dropped
!PostgresPartition.for_identifier("#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.#{detached_partition.table_name}").exists?
end
def conn
@conn ||= ApplicationRecord.connection
end
end
end
end
end
......@@ -86,7 +86,7 @@ module Gitlab
end
def pruning_old_partitions?
Feature.enabled?(:partition_pruning_dry_run) && retain_for.present?
retain_for.present?
end
def oldest_active_date
......
......@@ -4,6 +4,8 @@ module Gitlab
module Database
module Partitioning
class PartitionManager
UnsafeToDetachPartitionError = Class.new(StandardError)
def self.register(model)
raise ArgumentError, "Only models with a #partitioning_strategy can be registered." unless model.respond_to?(:partitioning_strategy)
......@@ -16,6 +18,7 @@ module Gitlab
LEASE_TIMEOUT = 1.minute
MANAGEMENT_LEASE_KEY = 'database_partition_management_%s'
RETAIN_DETACHED_PARTITIONS_FOR = 1.week
attr_reader :models
......@@ -35,13 +38,16 @@ module Gitlab
partitions_to_create = missing_partitions(model)
create(partitions_to_create) unless partitions_to_create.empty?
if Feature.enabled?(:partition_pruning_dry_run)
if Feature.enabled?(:partition_pruning, default_enabled: :yaml)
partitions_to_detach = extra_partitions(model)
detach(partitions_to_detach) unless partitions_to_detach.empty?
end
end
rescue StandardError => e
Gitlab::AppLogger.error("Failed to create / detach partition(s) for #{model.table_name}: #{e.class}: #{e.message}")
Gitlab::AppLogger.error(message: "Failed to create / detach partition(s)",
table_name: model.table_name,
exception_class: e.class,
exception_message: e.message)
end
end
......@@ -54,7 +60,6 @@ module Gitlab
end
def extra_partitions(model)
return [] unless Feature.enabled?(:partition_pruning_dry_run)
return [] unless connection.table_exists?(model.table_name)
model.partitioning_strategy.extra_partitions
......@@ -74,7 +79,9 @@ module Gitlab
partitions.each do |partition|
connection.execute partition.to_sql
Gitlab::AppLogger.info("Created partition #{partition.partition_name} for table #{partition.table}")
Gitlab::AppLogger.info(message: "Created partition",
partition_name: partition.partition_name,
table_name: partition.table)
end
end
end
......@@ -89,7 +96,24 @@ module Gitlab
end
def detach_one_partition(partition)
Gitlab::AppLogger.info("Planning to detach #{partition.partition_name} for table #{partition.table}")
assert_partition_detachable!(partition)
connection.execute partition.to_detach_sql
Postgresql::DetachedPartition.create!(table_name: partition.partition_name,
drop_after: RETAIN_DETACHED_PARTITIONS_FOR.from_now)
Gitlab::AppLogger.info(message: "Detached Partition",
partition_name: partition.partition_name,
table_name: partition.table)
end
def assert_partition_detachable!(partition)
parent_table_identifier = "#{connection.current_schema}.#{partition.table}"
if (example_fk = PostgresForeignKey.by_referenced_table_identifier(parent_table_identifier).first)
raise UnsafeToDetachPartitionError, "Cannot detach #{partition.partition_name}, it would block while checking foreign key #{example_fk.name} on #{example_fk.constrained_table_identifier}"
end
end
def with_lock_retries(&block)
......
......@@ -16,6 +16,7 @@ module Gitlab
gauge_present.set({ table: model.table_name }, strategy.current_partitions.size)
gauge_missing.set({ table: model.table_name }, strategy.missing_partitions.size)
gauge_extra.set({ table: model.table_name }, strategy.extra_partitions.size)
end
end
......@@ -28,6 +29,10 @@ module Gitlab
def gauge_missing
@gauge_missing ||= Gitlab::Metrics.gauge(:db_partitions_missing, 'Number of database partitions currently expected, but not present')
end
def gauge_extra
@gauge_extra ||= Gitlab::Metrics.gauge(:db_partitions_extra, 'Number of database partitions currently attached to tables, but outside of their retention window and scheduled to be dropped')
end
end
end
end
......
......@@ -47,6 +47,13 @@ module Gitlab
SQL
end
def to_detach_sql
<<~SQL
ALTER TABLE #{conn.quote_table_name(table)}
DETACH PARTITION #{fully_qualified_partition}
SQL
end
def ==(other)
table == other.table && partition_name == other.partition_name && from == other.from && to == other.to
end
......
......@@ -7,10 +7,14 @@ module Gitlab
belongs_to :postgres_partitioned_table, foreign_key: 'parent_identifier', primary_key: 'identifier'
scope :by_identifier, ->(identifier) do
scope :for_identifier, ->(identifier) do
raise ArgumentError, "Partition name is not fully qualified with a schema: #{identifier}" unless identifier =~ /^\w+\.\w+$/
find(identifier)
where(primary_key => identifier)
end
scope :by_identifier, ->(identifier) do
for_identifier(identifier).first!
end
scope :for_parent_table, ->(name) { where("parent_identifier = concat(current_schema(), '.', ?)", name).order(:name) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::Partitioning::DetachedPartitionDropper do
include Database::TableSchemaHelpers
let(:connection) { ActiveRecord::Base.connection }
def expect_partition_present(name)
aggregate_failures do
expect(table_oid(name)).not_to be_nil
expect(Postgresql::DetachedPartition.find_by(table_name: name)).not_to be_nil
end
end
def expect_partition_removed(name)
aggregate_failures do
expect(table_oid(name)).to be_nil
expect(Postgresql::DetachedPartition.find_by(table_name: name)).to be_nil
end
end
before do
connection.execute(<<~SQL)
CREATE TABLE parent_table (
id bigserial not null,
created_at timestamptz not null,
primary key (id, created_at)
) PARTITION BY RANGE(created_at)
SQL
end
def create_partition(name:, table: 'parent_table', from:, to:, attached:, drop_after:)
from = from.beginning_of_month
to = to.beginning_of_month
full_name = "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.#{name}"
connection.execute(<<~SQL)
CREATE TABLE #{full_name}
PARTITION OF #{table}
FOR VALUES FROM ('#{from.strftime('%Y-%m-%d')}') TO ('#{to.strftime('%Y-%m-%d')}')
SQL
unless attached
connection.execute(<<~SQL)
ALTER TABLE #{table} DETACH PARTITION #{full_name}
SQL
end
Postgresql::DetachedPartition.create!(table_name: name,
drop_after: drop_after)
end
describe '#perform' do
context 'when the partition should not be dropped yet' do
it 'does not drop the partition' do
create_partition(name: 'test_partition',
from: 2.months.ago, to: 1.month.ago,
attached: false,
drop_after: 1.day.from_now)
subject.perform
expect_partition_present('test_partition')
end
end
context 'with a partition to drop' do
before do
create_partition(name: 'test_partition',
from: 2.months.ago,
to: 1.month.ago.beginning_of_month,
attached: false,
drop_after: 1.second.ago)
end
it 'drops the partition' do
subject.perform
expect(table_oid('test_partition')).to be_nil
end
context 'when the drop_detached_partitions feature flag is disabled' do
before do
stub_feature_flags(drop_detached_partitions: false)
end
it 'does not drop the partition' do
subject.perform
expect(table_oid('test_partition')).not_to be_nil
end
end
context 'when another process drops the table while the first waits for a lock' do
it 'skips the table' do
# Rspec's receive_method_chain does not support .and_wrap_original, so we need to nest here.
expect(Postgresql::DetachedPartition).to receive(:lock).and_wrap_original do |lock_meth|
locked = lock_meth.call
expect(locked).to receive(:find_by).and_wrap_original do |find_meth, *find_args|
# Another process drops the table then deletes this entry
Postgresql::DetachedPartition.where(*find_args).delete_all
find_meth.call(*find_args)
end
locked
end
expect(subject).not_to receive(:drop_one)
subject.perform
end
end
end
context 'when the partition to drop is still attached to its table' do
before do
create_partition(name: 'test_partition',
from: 2.months.ago,
to: 1.month.ago.beginning_of_month,
attached: true,
drop_after: 1.second.ago)
end
it 'does not drop the partition, but does remove the DetachedPartition entry' do
subject.perform
aggregate_failures do
expect(table_oid('test_partition')).not_to be_nil
expect(Postgresql::DetachedPartition.find_by(table_name: 'test_partition')).to be_nil
end
end
it 'removes the detached_partition entry' do
detached_partition = Postgresql::DetachedPartition.find_by!(table_name: 'test_partition')
subject.perform
expect(Postgresql::DetachedPartition.exists?(id: detached_partition.id)).to be_falsey
end
end
context 'with multiple partitions to drop' do
before do
create_partition(name: 'partition_1',
from: 3.months.ago,
to: 2.months.ago,
attached: false,
drop_after: 1.second.ago)
create_partition(name: 'partition_2',
from: 2.months.ago,
to: 1.month.ago,
attached: false,
drop_after: 1.second.ago)
end
it 'drops both partitions' do
subject.perform
expect_partition_removed('partition_1')
expect_partition_removed('partition_2')
end
context 'when the first drop returns an error' do
it 'still drops the second partition' do
expect(subject).to receive(:drop_one).ordered.and_raise('injected error')
expect(subject).to receive(:drop_one).ordered.and_call_original
subject.perform
# We don't know which partition we tried to drop first, so the tests here have to work with either one
expect(Postgresql::DetachedPartition.count).to eq(1)
errored_partition_name = Postgresql::DetachedPartition.first!.table_name
dropped_partition_name = (%w[partition_1 partition_2] - [errored_partition_name]).first
expect_partition_present(errored_partition_name)
expect_partition_removed(dropped_partition_name)
end
end
end
end
end
......@@ -237,16 +237,6 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy do
expect(subject).to contain_exactly(min_value_to_may)
end
context 'when the feature flag is toggled off' do
before do
stub_feature_flags(partition_pruning_dry_run: false)
end
it 'is empty' do
expect(subject).to eq([])
end
end
end
context 'with a time retention policy of 2 months' do
......@@ -258,16 +248,6 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy do
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-05-01', '2020-06-01', partition_name: 'partitioned_test_202005')
)
end
context 'when the feature flag is toggled off' do
before do
stub_feature_flags(partition_pruning_dry_run: false)
end
it 'is empty' do
expect(subject).to eq([])
end
end
end
end
end
......
......@@ -4,9 +4,14 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
include Database::PartitioningHelpers
include Database::TableSchemaHelpers
include ExclusiveLeaseHelpers
def has_partition(model, month)
Gitlab::Database::PostgresPartition.for_parent_table(model.table_name).any? do |partition|
Gitlab::Database::Partitioning::TimePartition.from_sql(model.table_name, partition.name, partition.condition).from == month
end
end
describe '.register' do
let(:model) { double(partitioning_strategy: nil) }
......@@ -111,14 +116,14 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
let(:extra_partitions) do
[
instance_double(Gitlab::Database::Partitioning::TimePartition, table: table, partition_name: 'foo1'),
instance_double(Gitlab::Database::Partitioning::TimePartition, table: table, partition_name: 'foo2')
instance_double(Gitlab::Database::Partitioning::TimePartition, table: table, partition_name: 'foo1', to_detach_sql: 'SELECT 1'),
instance_double(Gitlab::Database::Partitioning::TimePartition, table: table, partition_name: 'foo2', to_detach_sql: 'SELECT 2')
]
end
context 'with the partition_pruning_dry_run feature flag enabled' do
context 'with the partition_pruning feature flag enabled' do
before do
stub_feature_flags(partition_pruning_dry_run: true)
stub_feature_flags(partition_pruning: true)
end
it 'detaches each extra partition' do
......@@ -146,9 +151,9 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
end
end
context 'with the partition_pruning_dry_run feature flag disabled' do
context 'with the partition_pruning feature flag disabled' do
before do
stub_feature_flags(partition_pruning_dry_run: false)
stub_feature_flags(partition_pruning: false)
end
it 'returns immediately' do
......@@ -158,4 +163,128 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
end
end
end
describe '#detach_partitions' do
around do |ex|
travel_to(Date.parse('2021-06-23')) do
ex.run
end
end
subject { described_class.new([my_model]).sync_partitions }
let(:connection) { ActiveRecord::Base.connection }
let(:my_model) do
Class.new(ApplicationRecord) do
include PartitionedTable
self.table_name = 'my_model_example_table'
partitioned_by :created_at, strategy: :monthly, retain_for: 1.month
end
end
before do
connection.execute(<<~SQL)
CREATE TABLE my_model_example_table
(id serial not null, created_at timestamptz not null, primary key (id, created_at))
PARTITION BY RANGE (created_at);
CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.my_model_example_table_202104
PARTITION OF my_model_example_table
FOR VALUES FROM ('2021-04-01') TO ('2021-05-01');
CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.my_model_example_table_202105
PARTITION OF my_model_example_table
FOR VALUES FROM ('2021-05-01') TO ('2021-06-01');
SQL
# Also create all future partitions so that the sync is only trying to detach old partitions
my_model.partitioning_strategy.missing_partitions.each do |p|
connection.execute p.to_sql
end
end
def num_tables
connection.select_value(<<~SQL)
SELECT COUNT(*)
FROM pg_class
where relkind IN ('r', 'p')
SQL
end
it 'detaches exactly one partition' do
expect { subject }.to change { find_partitions(my_model.table_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA).size }.from(9).to(8)
end
it 'detaches the old partition' do
expect { subject }.to change { has_partition(my_model, 2.months.ago.beginning_of_month) }.from(true).to(false)
end
it 'deletes zero tables' do
expect { subject }.not_to change { num_tables }
end
it 'creates the appropriate PendingPartitionDrop entry' do
subject
pending_drop = Postgresql::DetachedPartition.find_by!(table_name: 'my_model_example_table_202104')
expect(pending_drop.drop_after).to eq(Time.current + described_class::RETAIN_DETACHED_PARTITIONS_FOR)
end
# Postgres 11 does not support foreign keys to partitioned tables
if Gitlab::Database.main.version.to_f >= 12
context 'when the model is the target of a foreign key' do
before do
connection.execute(<<~SQL)
create unique index idx_for_fk ON my_model_example_table(created_at);
create table referencing_table (
id bigserial primary key not null,
referencing_created_at timestamptz references my_model_example_table(created_at)
);
SQL
end
it 'does not detach partitions with a referenced foreign key' do
expect { subject }.not_to change { find_partitions(my_model.table_name).size }
end
end
end
end
context 'creating and then detaching partitions for a table' do
let(:connection) { ActiveRecord::Base.connection }
let(:my_model) do
Class.new(ApplicationRecord) do
include PartitionedTable
self.table_name = 'my_model_example_table'
partitioned_by :created_at, strategy: :monthly, retain_for: 1.month
end
end
before do
connection.execute(<<~SQL)
CREATE TABLE my_model_example_table
(id serial not null, created_at timestamptz not null, primary key (id, created_at))
PARTITION BY RANGE (created_at);
SQL
end
def num_partitions(model)
find_partitions(model.table_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA).size
end
it 'creates partitions for the future then drops the oldest one after a month' do
# 1 month for the current month, 1 month for the old month that we're retaining data for, headroom
expected_num_partitions = (Gitlab::Database::Partitioning::MonthlyStrategy::HEADROOM + 2.months) / 1.month
expect { described_class.new([my_model]).sync_partitions }.to change { num_partitions(my_model) }.from(0).to(expected_num_partitions)
travel 1.month
expect { described_class.new([my_model]).sync_partitions }.to change { has_partition(my_model, 2.months.ago.beginning_of_month) }.from(true).to(false).and(change { num_partitions(my_model) }.by(0))
end
end
end
......@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionMonitoring do
let(:models) { [model] }
let(:model) { double(partitioning_strategy: partitioning_strategy, table_name: table) }
let(:partitioning_strategy) { double(missing_partitions: missing_partitions, current_partitions: current_partitions) }
let(:partitioning_strategy) { double(missing_partitions: missing_partitions, current_partitions: current_partitions, extra_partitions: extra_partitions) }
let(:table) { "some_table" }
let(:missing_partitions) do
......@@ -19,6 +19,10 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionMonitoring do
[double, double]
end
let(:extra_partitions) do
[double, double, double]
end
it 'reports number of present partitions' do
subject
......@@ -30,5 +34,11 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionMonitoring do
expect(Gitlab::Metrics.registry.get(:db_partitions_missing).get({ table: table })).to eq(missing_partitions.size)
end
it 'reports number of extra partitions' do
subject
expect(Gitlab::Metrics.registry.get(:db_partitions_extra).get({ table: table })).to eq(extra_partitions.size)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Postgresql::DetachedPartition do
describe '#ready_to_drop' do
let_it_be(:drop_before) { Postgresql::DetachedPartition.create!(drop_after: 1.day.ago, table_name: 'old_table') }
let_it_be(:drop_after) { Postgresql::DetachedPartition.create!(drop_after: 1.day.from_now, table_name: 'new_table') }
it 'includes partitions that should be dropped before now' do
expect(Postgresql::DetachedPartition.ready_to_drop.to_a).to include(drop_before)
end
it 'does not include partitions that should be dropped after now' do
expect(Postgresql::DetachedPartition.ready_to_drop.to_a).not_to include(drop_after)
end
end
end
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Database::DropDetachedPartitionsWorker do
describe '#perform' do
subject { described_class.new.perform }
let(:dropper) { instance_double('DropDetachedPartitions', perform: nil) }
let(:monitoring) { instance_double('PartitionMonitoring', report_metrics: nil) }
before do
allow(Gitlab::Database::Partitioning::DetachedPartitionDropper).to receive(:new).and_return(dropper)
allow(Gitlab::Database::Partitioning::PartitionMonitoring).to receive(:new).and_return(monitoring)
end
it 'delegates to DropPartitionsPendingDrop' do
expect(dropper).to receive(:perform)
subject
end
it 'reports partition metrics' do
expect(monitoring).to receive(:report_metrics)
subject
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