diff options
Diffstat (limited to 'lib/gitlab/database/partitioning')
7 files changed, 90 insertions, 109 deletions
diff --git a/lib/gitlab/database/partitioning/detached_partition_dropper.rb b/lib/gitlab/database/partitioning/detached_partition_dropper.rb index 3e7ddece20b..593824384b5 100644 --- a/lib/gitlab/database/partitioning/detached_partition_dropper.rb +++ b/lib/gitlab/database/partitioning/detached_partition_dropper.rb @@ -9,13 +9,10 @@ module Gitlab Gitlab::AppLogger.info(message: "Checking for previously detached partitions to drop") Postgresql::DetachedPartition.ready_to_drop.find_each do |detached_partition| - connection.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)) - - drop_detached_partition(detached_partition.table_name) - - detached_partition.destroy! + if partition_attached?(qualify_partition_name(detached_partition.table_name)) + unmark_partition(detached_partition) + else + drop_partition(detached_partition) end rescue StandardError => e Gitlab::AppLogger.error(message: "Failed to drop previously detached partition", @@ -27,31 +24,100 @@ module Gitlab private + def unmark_partition(detached_partition) + connection.transaction do + # Another process may have already encountered this case and deleted this entry + next unless try_lock_detached_partition(detached_partition.id) + + # The current partition was scheduled for deletion incorrectly + # Dropping it now could delete in-use data and take locks that interrupt other database activity + Gitlab::AppLogger.error(message: "Prevented an attempt to drop an attached database partition", partition_name: detached_partition.table_name) + detached_partition.destroy! + end + end + + def drop_partition(detached_partition) + remove_foreign_keys(detached_partition) + + connection.transaction do + # Another process may have already dropped the table and deleted this entry + next unless try_lock_detached_partition(detached_partition.id) + + drop_detached_partition(detached_partition.table_name) + + detached_partition.destroy! + end + end + + def remove_foreign_keys(detached_partition) + partition_identifier = qualify_partition_name(detached_partition.table_name) + + # We want to load all of these into memory at once to get a consistent view to loop over, + # since we'll be deleting from this list as we go + fks_to_drop = PostgresForeignKey.by_constrained_table_identifier(partition_identifier).to_a + fks_to_drop.each do |foreign_key| + drop_foreign_key_if_present(detached_partition, foreign_key) + end + end + + # Drops the given foreign key for the given detached partition, but only if another process has not already + # detached the partition first. This method must be safe to call even if the associated partition table has already + # been detached, as it could be called by multiple processes at once. + def drop_foreign_key_if_present(detached_partition, foreign_key) + # It is important to only drop one foreign key per transaction. + # Dropping a foreign key takes an ACCESS EXCLUSIVE lock on both tables participating in the foreign key. + + partition_identifier = qualify_partition_name(detached_partition.table_name) + with_lock_retries do + connection.transaction(requires_new: false) do + next unless try_lock_detached_partition(detached_partition.id) + + # Another process may have already dropped this foreign key + next unless PostgresForeignKey.by_constrained_table_identifier(partition_identifier).where(name: foreign_key.name).exists? + + connection.execute("ALTER TABLE #{connection.quote_table_name(partition_identifier)} DROP CONSTRAINT #{connection.quote_table_name(foreign_key.name)}") + + Gitlab::AppLogger.info(message: "Dropped foreign key for previously detached partition", + partition_name: detached_partition.table_name, + referenced_table_name: foreign_key.referenced_table_identifier, + foreign_key_name: foreign_key.name) + end + end + end + def drop_detached_partition(partition_name) partition_identifier = qualify_partition_name(partition_name) - if partition_detached?(partition_identifier) - connection.drop_table(partition_identifier, if_exists: true) + connection.drop_table(partition_identifier, if_exists: true) - Gitlab::AppLogger.info(message: "Dropped previously detached partition", partition_name: partition_name) - else - Gitlab::AppLogger.error(message: "Attempt to drop attached database partition", partition_name: partition_name) - end + Gitlab::AppLogger.info(message: "Dropped previously detached partition", partition_name: partition_name) end def qualify_partition_name(table_name) "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.#{table_name}" end - def partition_detached?(partition_identifier) + def partition_attached?(partition_identifier) # PostgresPartition checks the pg_inherits view, so our partition will only show here if it's still attached # and thus should not be dropped - !Gitlab::Database::PostgresPartition.for_identifier(partition_identifier).exists? + Gitlab::Database::PostgresPartition.for_identifier(partition_identifier).exists? + end + + def try_lock_detached_partition(id) + Postgresql::DetachedPartition.lock.find_by(id: id).present? end def connection Postgresql::DetachedPartition.connection end + + def with_lock_retries(&block) + Gitlab::Database::WithLockRetries.new( + klass: self.class, + logger: Gitlab::AppLogger, + connection: connection + ).run(raise_on_exhaustion: true, &block) + end end end end diff --git a/lib/gitlab/database/partitioning/monthly_strategy.rb b/lib/gitlab/database/partitioning/monthly_strategy.rb index 4cdde5bf2f1..c93e775d7ed 100644 --- a/lib/gitlab/database/partitioning/monthly_strategy.rb +++ b/lib/gitlab/database/partitioning/monthly_strategy.rb @@ -96,10 +96,6 @@ module Gitlab def oldest_active_date (Date.today - retain_for).beginning_of_month end - - def connection - ActiveRecord::Base.connection - end end end end diff --git a/lib/gitlab/database/partitioning/multi_database_partition_dropper.rb b/lib/gitlab/database/partitioning/multi_database_partition_dropper.rb deleted file mode 100644 index 769b658bae4..00000000000 --- a/lib/gitlab/database/partitioning/multi_database_partition_dropper.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Database - module Partitioning - class MultiDatabasePartitionDropper - def drop_detached_partitions - Gitlab::AppLogger.info(message: "Dropping detached postgres partitions") - - each_database_connection do |name, connection| - Gitlab::Database::SharedModel.using_connection(connection) do - Gitlab::AppLogger.debug(message: "Switched database connection", connection_name: name) - - DetachedPartitionDropper.new.perform - end - end - - Gitlab::AppLogger.info(message: "Finished dropping detached postgres partitions") - end - - private - - def each_database_connection - databases.each_pair do |name, connection_wrapper| - yield name, connection_wrapper.scope.connection - end - end - - def databases - Gitlab::Database.databases - end - end - end - end -end diff --git a/lib/gitlab/database/partitioning/multi_database_partition_manager.rb b/lib/gitlab/database/partitioning/multi_database_partition_manager.rb deleted file mode 100644 index 5a93e3fb1fb..00000000000 --- a/lib/gitlab/database/partitioning/multi_database_partition_manager.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Database - module Partitioning - class MultiDatabasePartitionManager - def initialize(models) - @models = models - end - - def sync_partitions - Gitlab::AppLogger.info(message: "Syncing dynamic postgres partitions") - - models.each do |model| - Gitlab::Database::SharedModel.using_connection(model.connection) do - Gitlab::AppLogger.debug(message: "Switched database connection", - connection_name: connection_name, - table_name: model.table_name) - - PartitionManager.new(model).sync_partitions - end - end - - Gitlab::AppLogger.info(message: "Finished sync of dynamic postgres partitions") - end - - private - - attr_reader :models - - def connection_name - Gitlab::Database::SharedModel.connection.pool.db_config.name - end - end - end - end -end diff --git a/lib/gitlab/database/partitioning/partition_monitoring.rb b/lib/gitlab/database/partitioning/partition_monitoring.rb index e5b561fc447..1a23f58285d 100644 --- a/lib/gitlab/database/partitioning/partition_monitoring.rb +++ b/lib/gitlab/database/partitioning/partition_monitoring.rb @@ -4,20 +4,12 @@ module Gitlab module Database module Partitioning class PartitionMonitoring - attr_reader :models + def report_metrics_for_model(model) + strategy = model.partitioning_strategy - def initialize(models = Gitlab::Database::Partitioning.registered_models) - @models = models - end - - def report_metrics - models.each do |model| - strategy = model.partitioning_strategy - - 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 + 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 private diff --git a/lib/gitlab/database/partitioning/replace_table.rb b/lib/gitlab/database/partitioning/replace_table.rb index 6f6af223fa2..a7686e97553 100644 --- a/lib/gitlab/database/partitioning/replace_table.rb +++ b/lib/gitlab/database/partitioning/replace_table.rb @@ -9,7 +9,8 @@ module Gitlab attr_reader :original_table, :replacement_table, :replaced_table, :primary_key_column, :sequence, :original_primary_key, :replacement_primary_key, :replaced_primary_key - def initialize(original_table, replacement_table, replaced_table, primary_key_column) + def initialize(connection, original_table, replacement_table, replaced_table, primary_key_column) + @connection = connection @original_table = original_table @replacement_table = replacement_table @replaced_table = replaced_table @@ -29,10 +30,8 @@ module Gitlab private + attr_reader :connection delegate :execute, :quote_table_name, :quote_column_name, to: :connection - def connection - @connection ||= ActiveRecord::Base.connection - end def default_sequence(table, column) "#{table}_#{column}_seq" diff --git a/lib/gitlab/database/partitioning/time_partition.rb b/lib/gitlab/database/partitioning/time_partition.rb index e09ca483549..649687bdd12 100644 --- a/lib/gitlab/database/partitioning/time_partition.rb +++ b/lib/gitlab/database/partitioning/time_partition.rb @@ -87,7 +87,7 @@ module Gitlab end def conn - @conn ||= ActiveRecord::Base.connection + @conn ||= Gitlab::Database::SharedModel.connection end end end |