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 'lib/gitlab/database/ci_builds_partitioning.rb')
-rw-r--r--lib/gitlab/database/ci_builds_partitioning.rb224
1 files changed, 224 insertions, 0 deletions
diff --git a/lib/gitlab/database/ci_builds_partitioning.rb b/lib/gitlab/database/ci_builds_partitioning.rb
new file mode 100644
index 00000000000..9f8b19f2d23
--- /dev/null
+++ b/lib/gitlab/database/ci_builds_partitioning.rb
@@ -0,0 +1,224 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ class CiBuildsPartitioning
+ include AsyncDdlExclusiveLeaseGuard
+
+ ATTEMPTS = 5
+ LOCK_TIMEOUT = 10.seconds
+ LEASE_TIMEOUT = 30.minutes
+
+ FK_NAME = :fk_e20479742e_p
+ TEMP_FK_NAME = :temp_fk_e20479742e_p
+ NEXT_PARTITION_ID = 101
+ BUILDS_PARTITION_NAME = 'gitlab_partitions_dynamic.ci_builds_101'
+ ANNOTATION_PARTITION_NAME = 'gitlab_partitions_dynamic.ci_job_annotations_101'
+ RUNNER_MACHINE_PARTITION_NAME = 'gitlab_partitions_dynamic.ci_runner_machine_builds_101'
+
+ def initialize(logger: Gitlab::AppLogger)
+ @connection = ::Ci::ApplicationRecord.connection
+ @timing_configuration = Array.new(ATTEMPTS) { [LOCK_TIMEOUT, 3.minutes] }
+ @logger = logger
+ end
+
+ def execute
+ return unless can_execute?
+
+ try_obtain_lease do
+ swap_foreign_keys
+ create_new_ci_builds_partition
+ create_new_job_annotations_partition
+ create_new_runner_machine_partition
+ end
+
+ rescue StandardError => e
+ log_info("Failed to execute: #{e.message}")
+ end
+
+ private
+
+ attr_reader :connection, :timing_configuration, :logger
+
+ delegate :quote_table_name, :quote_column_name, to: :connection
+
+ def swap_foreign_keys
+ if new_foreign_key_exists?
+ log_info('Foreign key already renamed, nothing to do')
+
+ return
+ end
+
+ with_lock_retries do
+ connection.execute drop_old_foreign_key_sql
+
+ rename_constraint :p_ci_builds_metadata, TEMP_FK_NAME, FK_NAME
+
+ each_partition do |partition|
+ rename_constraint partition.identifier, TEMP_FK_NAME, FK_NAME
+ end
+ end
+
+ log_info('Foreign key successfully renamed')
+ end
+
+ def create_new_ci_builds_partition
+ if connection.table_exists?(BUILDS_PARTITION_NAME)
+ log_info('p_ci_builds partition exists, nothing to do')
+ return
+ end
+
+ with_lock_retries do
+ connection.execute new_ci_builds_partition_sql
+ end
+
+ log_info('Partition for p_ci_builds successfully created')
+ end
+
+ def create_new_job_annotations_partition
+ if connection.table_exists?(ANNOTATION_PARTITION_NAME)
+ log_info('p_ci_job_annotations partition exists, nothing to do')
+ return
+ end
+
+ with_lock_retries do
+ connection.execute new_job_annotations_partition_sql
+ end
+
+ log_info('Partition for p_ci_job_annotations successfully created')
+ end
+
+ def create_new_runner_machine_partition
+ if connection.table_exists?(RUNNER_MACHINE_PARTITION_NAME)
+ log_info('p_ci_runner_machine_builds partition exists, nothing to do')
+ return
+ end
+
+ with_lock_retries do
+ connection.execute new_runner_machine_partition_sql
+ end
+
+ log_info('Partition for p_ci_runner_machine_builds successfully created')
+ end
+
+ def can_execute?
+ return false if process_disabled?
+ return false unless Gitlab.com?
+
+ if vacuum_running?
+ log_info('Autovacuum detected')
+
+ return false
+ end
+
+ true
+ end
+
+ def process_disabled?
+ ::Feature.disabled?(:complete_p_ci_builds_partitioning)
+ end
+
+ def new_foreign_key_exists?
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ Gitlab::Database::PostgresForeignKey
+ .by_constrained_table_name_or_identifier(:p_ci_builds_metadata)
+ .by_referenced_table_name(:p_ci_builds)
+ .by_name(FK_NAME)
+ .exists?
+ end
+ end
+
+ def vacuum_running?
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ Gitlab::Database::PostgresAutovacuumActivity
+ .wraparound_prevention
+ .for_tables(%i[ci_builds ci_builds_metadata])
+ .any?
+ end
+ end
+
+ def drop_old_foreign_key_sql
+ <<~SQL.squish
+ SET LOCAL statement_timeout TO '11s';
+
+ LOCK TABLE ci_builds, p_ci_builds_metadata IN ACCESS EXCLUSIVE MODE;
+
+ ALTER TABLE p_ci_builds_metadata DROP CONSTRAINT #{FK_NAME};
+ SQL
+ end
+
+ def rename_constraint(table_name, old_name, new_name)
+ connection.execute <<~SQL
+ ALTER TABLE #{quote_table_name(table_name)}
+ RENAME CONSTRAINT #{quote_column_name(old_name)} TO #{quote_column_name(new_name)}
+ SQL
+ end
+
+ def new_ci_builds_partition_sql
+ <<~SQL
+ SET LOCAL statement_timeout TO '11s';
+
+ LOCK ci_pipelines, ci_stages IN SHARE ROW EXCLUSIVE MODE;
+ LOCK TABLE ONLY p_ci_builds IN ACCESS EXCLUSIVE MODE;
+
+ CREATE TABLE IF NOT EXISTS #{BUILDS_PARTITION_NAME}
+ PARTITION OF p_ci_builds
+ FOR VALUES IN (#{NEXT_PARTITION_ID});
+ SQL
+ end
+
+ def new_job_annotations_partition_sql
+ <<~SQL
+ SET LOCAL statement_timeout TO '11s';
+
+ LOCK TABLE p_ci_builds IN SHARE ROW EXCLUSIVE MODE;
+ LOCK TABLE ONLY p_ci_job_annotations IN ACCESS EXCLUSIVE MODE;
+
+ CREATE TABLE IF NOT EXISTS #{ANNOTATION_PARTITION_NAME}
+ PARTITION OF p_ci_job_annotations
+ FOR VALUES IN (#{NEXT_PARTITION_ID});
+ SQL
+ end
+
+ def new_runner_machine_partition_sql
+ <<~SQL
+ SET LOCAL statement_timeout TO '11s';
+
+ LOCK TABLE p_ci_builds IN SHARE ROW EXCLUSIVE MODE;
+ LOCK TABLE ONLY p_ci_runner_machine_builds IN ACCESS EXCLUSIVE MODE;
+
+ CREATE TABLE IF NOT EXISTS #{RUNNER_MACHINE_PARTITION_NAME}
+ PARTITION OF p_ci_runner_machine_builds
+ FOR VALUES IN (#{NEXT_PARTITION_ID});
+ SQL
+ end
+
+ def with_lock_retries(&block)
+ Gitlab::Database::WithLockRetries.new(
+ timing_configuration: timing_configuration,
+ connection: connection,
+ logger: logger,
+ klass: self.class
+ ).run(raise_on_exhaustion: true, &block)
+ end
+
+ def each_partition(&block)
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ Gitlab::Database::PostgresPartitionedTable.each_partition(:p_ci_builds_metadata, &block)
+ end
+ end
+
+ def log_info(message)
+ logger.info(message: message, class: self.class.to_s)
+ end
+
+ def connection_db_config
+ ::Ci::ApplicationRecord.connection_db_config
+ end
+
+ def lease_timeout
+ LEASE_TIMEOUT
+ end
+ end
+ end
+end