diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-20 14:10:13 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-20 14:10:13 +0300 |
commit | 0ea3fcec397b69815975647f5e2aa5fe944a8486 (patch) | |
tree | 7979381b89d26011bcf9bdc989a40fcc2f1ed4ff /lib/gitlab/database | |
parent | 72123183a20411a36d607d70b12d57c484394c8e (diff) |
Add latest changes from gitlab-org/gitlab@15-1-stable-eev15.1.0-rc42
Diffstat (limited to 'lib/gitlab/database')
23 files changed, 290 insertions, 114 deletions
diff --git a/lib/gitlab/database/background_migration/batched_migration.rb b/lib/gitlab/database/background_migration/batched_migration.rb index a90cae7aea2..d052d5adc4c 100644 --- a/lib/gitlab/database/background_migration/batched_migration.rb +++ b/lib/gitlab/database/background_migration/batched_migration.rb @@ -30,9 +30,23 @@ module Gitlab scope :created_after, ->(time) { where('created_at > ?', time) } - scope :for_configuration, ->(job_class_name, table_name, column_name, job_arguments) do - where(job_class_name: job_class_name, table_name: table_name, column_name: column_name) + scope :for_configuration, ->(gitlab_schema, job_class_name, table_name, column_name, job_arguments) do + relation = where(job_class_name: job_class_name, table_name: table_name, column_name: column_name) .where("job_arguments = ?", job_arguments.to_json) # rubocop:disable Rails/WhereEquals + + # This method is called from migrations older than the gitlab_schema column, + # check and add this filter only if the column exists. + relation = relation.for_gitlab_schema(gitlab_schema) if gitlab_schema_column_exists? + + relation + end + + def self.gitlab_schema_column_exists? + column_names.include?('gitlab_schema') + end + + scope :for_gitlab_schema, ->(gitlab_schema) do + where(gitlab_schema: gitlab_schema) end state_machine :status, initial: :paused do @@ -73,12 +87,13 @@ module Gitlab state_machine.states.map(&:name) end - def self.find_for_configuration(job_class_name, table_name, column_name, job_arguments) - for_configuration(job_class_name, table_name, column_name, job_arguments).first + def self.find_for_configuration(gitlab_schema, job_class_name, table_name, column_name, job_arguments) + for_configuration(gitlab_schema, job_class_name, table_name, column_name, job_arguments).first end - def self.active_migration - executable.queue_order.first + def self.active_migration(connection:) + for_gitlab_schema(Gitlab::Database.gitlab_schemas_for_connection(connection)) + .executable.queue_order.first end def self.successful_rows_counts(migrations) diff --git a/lib/gitlab/database/background_migration/batched_migration_runner.rb b/lib/gitlab/database/background_migration/batched_migration_runner.rb index 59ff9a9744f..388eb596ce2 100644 --- a/lib/gitlab/database/background_migration/batched_migration_runner.rb +++ b/lib/gitlab/database/background_migration/batched_migration_runner.rb @@ -54,7 +54,10 @@ module Gitlab # in order to prevent it being picked up by the background worker. Perform all pending jobs, # then keep running until migration is finished. def finalize(job_class_name, table_name, column_name, job_arguments) - migration = BatchedMigration.find_for_configuration(job_class_name, table_name, column_name, job_arguments) + migration = BatchedMigration.find_for_configuration( + Gitlab::Database.gitlab_schemas_for_connection(connection), + job_class_name, table_name, column_name, job_arguments + ) configuration = { job_class_name: job_class_name, diff --git a/lib/gitlab/database/batch_count.rb b/lib/gitlab/database/batch_count.rb index 49f56b5be97..92a41bb36ee 100644 --- a/lib/gitlab/database/batch_count.rb +++ b/lib/gitlab/database/batch_count.rb @@ -1,7 +1,11 @@ # frozen_string_literal: true # For large tables, PostgreSQL can take a long time to count rows due to MVCC. -# Implements a distinct and ordinary batch counter +# Implements: +# - distinct batch counter +# - ordinary batch counter +# - sum batch counter +# - average batch counter # Needs indexes on the column below to calculate max, min and range queries # For larger tables just set use higher batch_size with index optimization # @@ -22,6 +26,8 @@ # batch_distinct_count(Project.group(:visibility_level), :creator_id) # batch_sum(User, :sign_in_count) # batch_sum(Issue.group(:state_id), :weight)) +# batch_average(Ci::Pipeline, :duration) +# batch_average(MergeTrain.group(:status), :duration) module Gitlab module Database module BatchCount @@ -37,6 +43,10 @@ module Gitlab BatchCounter.new(relation, column: nil, operation: :sum, operation_args: [column]).count(batch_size: batch_size, start: start, finish: finish) end + def batch_average(relation, column, batch_size: nil, start: nil, finish: nil) + BatchCounter.new(relation, column: nil, operation: :average, operation_args: [column]).count(batch_size: batch_size, start: start, finish: finish) + end + class << self include BatchCount end diff --git a/lib/gitlab/database/batch_counter.rb b/lib/gitlab/database/batch_counter.rb index 417511618e4..522b598cd9d 100644 --- a/lib/gitlab/database/batch_counter.rb +++ b/lib/gitlab/database/batch_counter.rb @@ -6,6 +6,7 @@ module Gitlab FALLBACK = -1 MIN_REQUIRED_BATCH_SIZE = 1_250 DEFAULT_SUM_BATCH_SIZE = 1_000 + DEFAULT_AVERAGE_BATCH_SIZE = 1_000 MAX_ALLOWED_LOOPS = 10_000 SLEEP_TIME_IN_SECONDS = 0.01 # 10 msec sleep ALLOWED_MODES = [:itself, :distinct].freeze @@ -26,6 +27,7 @@ module Gitlab def unwanted_configuration?(finish, batch_size, start) (@operation == :count && batch_size <= MIN_REQUIRED_BATCH_SIZE) || (@operation == :sum && batch_size < DEFAULT_SUM_BATCH_SIZE) || + (@operation == :average && batch_size < DEFAULT_AVERAGE_BATCH_SIZE) || (finish - start) / batch_size >= MAX_ALLOWED_LOOPS || start >= finish end @@ -92,6 +94,7 @@ module Gitlab def batch_size_for_mode_and_operation(mode, operation) return DEFAULT_SUM_BATCH_SIZE if operation == :sum + return DEFAULT_AVERAGE_BATCH_SIZE if operation == :average mode == :distinct ? DEFAULT_DISTINCT_BATCH_SIZE : DEFAULT_BATCH_SIZE end diff --git a/lib/gitlab/database/consistency_checker.rb b/lib/gitlab/database/consistency_checker.rb index e398fef744c..bf60c76a085 100644 --- a/lib/gitlab/database/consistency_checker.rb +++ b/lib/gitlab/database/consistency_checker.rb @@ -3,9 +3,9 @@ module Gitlab module Database class ConsistencyChecker - BATCH_SIZE = 1000 - MAX_BATCHES = 25 - MAX_RUNTIME = 30.seconds # must be less than the scheduling frequency of the ConsistencyCheck jobs + BATCH_SIZE = 500 + MAX_BATCHES = 20 + MAX_RUNTIME = 5.seconds # must be less than the scheduling frequency of the ConsistencyCheck jobs delegate :monotonic_time, to: :'Gitlab::Metrics::System' diff --git a/lib/gitlab/database/gitlab_schema.rb b/lib/gitlab/database/gitlab_schema.rb index d7ea614e2af..baf4cc48424 100644 --- a/lib/gitlab/database/gitlab_schema.rb +++ b/lib/gitlab/database/gitlab_schema.rb @@ -75,8 +75,8 @@ module Gitlab return gitlab_schema end - # All tables from `information_schema.` are `:gitlab_shared` - return :gitlab_shared if schema_name == 'information_schema' + # All tables from `information_schema.` are marked as `internal` + return :gitlab_internal if schema_name == 'information_schema' return :gitlab_main if table_name.start_with?('_test_gitlab_main_') @@ -85,8 +85,8 @@ module Gitlab # All tables that start with `_test_` without a following schema are shared and ignored return :gitlab_shared if table_name.start_with?('_test_') - # All `pg_` tables are marked as `shared` - return :gitlab_shared if table_name.start_with?('pg_') + # All `pg_` tables are marked as `internal` + return :gitlab_internal if table_name.start_with?('pg_') # When undefined it's best to return a unique name so that we don't incorrectly assume that 2 undefined schemas belong on the same database :"undefined_#{table_name}" diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml index 036ce7d7631..71c323cb393 100644 --- a/lib/gitlab/database/gitlab_schemas.yml +++ b/lib/gitlab/database/gitlab_schemas.yml @@ -35,10 +35,11 @@ approval_project_rules_users: :gitlab_main approvals: :gitlab_main approver_groups: :gitlab_main approvers: :gitlab_main -ar_internal_metadata: :gitlab_shared +ar_internal_metadata: :gitlab_internal atlassian_identities: :gitlab_main audit_events_external_audit_event_destinations: :gitlab_main audit_events: :gitlab_main +audit_events_streaming_headers: :gitlab_main authentication_events: :gitlab_main award_emoji: :gitlab_main aws_roles: :gitlab_main @@ -121,6 +122,7 @@ ci_unit_tests: :gitlab_ci ci_variables: :gitlab_ci cluster_agents: :gitlab_main cluster_agent_tokens: :gitlab_main +cluster_enabled_grants: :gitlab_main cluster_groups: :gitlab_main cluster_platforms_kubernetes: :gitlab_main cluster_projects: :gitlab_main @@ -217,7 +219,6 @@ geo_event_log: :gitlab_main geo_events: :gitlab_main geo_hashed_storage_attachments_events: :gitlab_main geo_hashed_storage_migrated_events: :gitlab_main -geo_lfs_object_deleted_events: :gitlab_main geo_node_namespace_links: :gitlab_main geo_nodes: :gitlab_main geo_node_statuses: :gitlab_main @@ -266,6 +267,7 @@ integrations: :gitlab_main internal_ids: :gitlab_main ip_restrictions: :gitlab_main issuable_metric_images: :gitlab_main +issuable_resource_links: :gitlab_main issuable_severities: :gitlab_main issuable_slas: :gitlab_main issue_assignees: :gitlab_main @@ -465,7 +467,7 @@ routes: :gitlab_main saml_group_links: :gitlab_main saml_providers: :gitlab_main saved_replies: :gitlab_main -schema_migrations: :gitlab_shared +schema_migrations: :gitlab_internal scim_identities: :gitlab_main scim_oauth_access_tokens: :gitlab_main security_findings: :gitlab_main @@ -491,6 +493,7 @@ software_license_policies: :gitlab_main software_licenses: :gitlab_main spam_logs: :gitlab_main sprints: :gitlab_main +ssh_signatures: :gitlab_main status_check_responses: :gitlab_main status_page_published_incidents: :gitlab_main status_page_settings: :gitlab_main @@ -503,6 +506,7 @@ term_agreements: :gitlab_main terraform_states: :gitlab_main terraform_state_versions: :gitlab_main timelogs: :gitlab_main +timelog_categories: :gitlab_main todos: :gitlab_main token_with_ivs: :gitlab_main topics: :gitlab_main @@ -549,6 +553,7 @@ vulnerability_occurrences: :gitlab_main vulnerability_reads: :gitlab_main vulnerability_remediations: :gitlab_main vulnerability_scanners: :gitlab_main +vulnerability_state_transitions: :gitlab_main vulnerability_statistics: :gitlab_main vulnerability_user_mentions: :gitlab_main webauthn_registrations: :gitlab_main @@ -556,10 +561,13 @@ web_hook_logs: :gitlab_main web_hooks: :gitlab_main wiki_page_meta: :gitlab_main wiki_page_slugs: :gitlab_main +work_item_parent_links: :gitlab_main work_item_types: :gitlab_main x509_certificates: :gitlab_main x509_commit_signatures: :gitlab_main x509_issuers: :gitlab_main zentao_tracker_data: :gitlab_main +# dingtalk_tracker_data JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417 +dingtalk_tracker_data: :gitlab_main zoom_meetings: :gitlab_main batched_background_migration_job_transition_logs: :gitlab_shared diff --git a/lib/gitlab/database/load_balancing/configuration.rb b/lib/gitlab/database/load_balancing/configuration.rb index 0ddc745ebae..59b08fac7e9 100644 --- a/lib/gitlab/database/load_balancing/configuration.rb +++ b/lib/gitlab/database/load_balancing/configuration.rb @@ -41,8 +41,6 @@ module Gitlab end end - config.reuse_primary_connection! - config end @@ -61,44 +59,17 @@ module Gitlab disconnect_timeout: 120, use_tcp: false } - - # Temporary model for GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ - # To be removed with FF - @primary_model = nil end def db_config_name @model.connection_db_config.name.to_sym end - # With connection re-use the primary connection can be overwritten - # to be used from different model - def primary_connection_specification_name - primary_model_or_model_if_enabled.connection_specification_name - end - - def primary_model_or_model_if_enabled - if use_dedicated_connection? - @model - else - @primary_model || @model - end - end - - def use_dedicated_connection? - return true unless @primary_model # We can only use dedicated connection, if re-use of connections is disabled - return false unless ::Gitlab::SafeRequestStore.active? - - ::Gitlab::SafeRequestStore.fetch(:force_no_sharing_primary_model) do - ::Feature::FlipperFeature.table_exists? && ::Feature.enabled?(:force_no_sharing_primary_model) - end - end - - def primary_db_config - primary_model_or_model_if_enabled.connection_db_config + def connection_specification_name + @model.connection_specification_name end - def replica_db_config + def db_config @model.connection_db_config end @@ -131,30 +102,6 @@ module Gitlab service_discovery[:record].present? end - - # TODO: This is temporary code to allow re-use of primary connection - # if the two connections are pointing to the same host. This is needed - # to properly support transaction visibility. - # - # This behavior is required to support [Phase 3](https://gitlab.com/groups/gitlab-org/-/epics/6160#progress). - # This method is meant to be removed as soon as it is finished. - # - # The remapping is done as-is: - # export GITLAB_LOAD_BALANCING_REUSE_PRIMARY_<name-of-connection>=<new-name-of-connection> - # - # Ex.: - # export GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci=main - # - def reuse_primary_connection! - new_connection = ENV["GITLAB_LOAD_BALANCING_REUSE_PRIMARY_#{db_config_name}"] - return unless new_connection.present? - - @primary_model = Gitlab::Database.database_base_models[new_connection.to_sym] - - unless @primary_model - raise "Invalid value for 'GITLAB_LOAD_BALANCING_REUSE_PRIMARY_#{db_config_name}=#{new_connection}'" - end - end end end end diff --git a/lib/gitlab/database/load_balancing/connection_proxy.rb b/lib/gitlab/database/load_balancing/connection_proxy.rb index 1be63da8896..8799f8d8af8 100644 --- a/lib/gitlab/database/load_balancing/connection_proxy.rb +++ b/lib/gitlab/database/load_balancing/connection_proxy.rb @@ -23,6 +23,7 @@ module Gitlab insert update update_all + exec_insert_all ).freeze NON_STICKY_READS = %i( diff --git a/lib/gitlab/database/load_balancing/load_balancer.rb b/lib/gitlab/database/load_balancing/load_balancer.rb index 191ebe18b8a..40b76a1c028 100644 --- a/lib/gitlab/database/load_balancing/load_balancer.rb +++ b/lib/gitlab/database/load_balancing/load_balancer.rb @@ -104,12 +104,24 @@ module Gitlab # Yields a connection that can be used for both reads and writes. def read_write connection = nil + transaction_open = nil # In the event of a failover the primary may be briefly unavailable. # Instead of immediately grinding to a halt we'll retry the operation # a few times. retry_with_backoff do connection = pool.connection + transaction_open = connection.transaction_open? + yield connection + rescue StandardError => e + if transaction_open && connection_error?(e) + ::Gitlab::Database::LoadBalancing::Logger.warn( + event: :transaction_leak, + message: 'A write transaction has leaked during database fail-over' + ) + end + + raise e end end @@ -232,14 +244,14 @@ module Gitlab # host - An optional host name to use instead of the default one. # port - An optional port to connect to. def create_replica_connection_pool(pool_size, host = nil, port = nil) - db_config = @configuration.replica_db_config + db_config = @configuration.db_config env_config = db_config.configuration_hash.dup env_config[:pool] = pool_size env_config[:host] = host if host env_config[:port] = port if port - replica_db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new( + db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new( db_config.env_name, db_config.name + REPLICA_SUFFIX, env_config @@ -249,7 +261,7 @@ module Gitlab # as it will rewrite ActiveRecord::Base.connection ActiveRecord::ConnectionAdapters::ConnectionHandler .new - .establish_connection(replica_db_config) + .establish_connection(db_config) end # ActiveRecord::ConnectionAdapters::ConnectionHandler handles fetching, @@ -258,7 +270,7 @@ module Gitlab # rubocop:disable Database/MultipleDatabases def pool ActiveRecord::Base.connection_handler.retrieve_connection_pool( - @configuration.primary_connection_specification_name, + @configuration.connection_specification_name, role: ActiveRecord::Base.writing_role, shard: ActiveRecord::Base.default_shard ) || raise(::ActiveRecord::ConnectionNotEstablished) diff --git a/lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb b/lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb index 62dfe75a851..13afbd8fd37 100644 --- a/lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb +++ b/lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb @@ -39,18 +39,31 @@ module Gitlab end job['wal_locations'] = locations + job['wal_location_source'] = wal_location_source + end + + def wal_location_source + if ::Gitlab::Database::LoadBalancing.primary_only? || uses_primary? + ::Gitlab::Database::LoadBalancing::ROLE_PRIMARY + else + ::Gitlab::Database::LoadBalancing::ROLE_REPLICA + end end def wal_location_for(load_balancer) # When only using the primary there's no need for any WAL queries. return if load_balancer.primary_only? - if ::Gitlab::Database::LoadBalancing::Session.current.use_primary? + if uses_primary? load_balancer.primary_write_location else load_balancer.host.database_replica_location end end + + def uses_primary? + ::Gitlab::Database::LoadBalancing::Session.current.use_primary? + end end end end diff --git a/lib/gitlab/database/migration.rb b/lib/gitlab/database/migration.rb index 038af570dbc..ab8b6988c3d 100644 --- a/lib/gitlab/database/migration.rb +++ b/lib/gitlab/database/migration.rb @@ -37,6 +37,7 @@ module Gitlab class V1_0 < ActiveRecord::Migration[6.1] # rubocop:disable Naming/ClassAndModuleCamelCase include LockRetriesConcern include Gitlab::Database::MigrationHelpers::V2 + include Gitlab::Database::MigrationHelpers::AnnounceDatabase # When running migrations, the `db:migrate` switches connection of # ActiveRecord::Base depending where the migration runs. diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 0453b81d67d..4bb1d71ce18 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -945,8 +945,13 @@ module Gitlab end def ensure_batched_background_migration_is_finished(job_class_name:, table_name:, column_name:, job_arguments:, finalize: true) - migration = Gitlab::Database::BackgroundMigration::BatchedMigration - .for_configuration(job_class_name, table_name, column_name, job_arguments).first + Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_dml_mode! + + Gitlab::Database::BackgroundMigration::BatchedMigration.reset_column_information + migration = Gitlab::Database::BackgroundMigration::BatchedMigration.find_for_configuration( + Gitlab::Database.gitlab_schemas_for_connection(connection), + job_class_name, table_name, column_name, job_arguments + ) configuration = { job_class_name: job_class_name, @@ -966,7 +971,7 @@ module Gitlab "but it is '#{migration.status_name}':" \ "\t#{configuration}" \ "\n\n" \ - "Finalize it manually by running" \ + "Finalize it manually by running the following command in a `bash` or `sh` shell:" \ "\n\n" \ "\tsudo gitlab-rake gitlab:background_migrations:finalize[#{job_class_name},#{table_name},#{column_name},'#{job_arguments.to_json.gsub(',', '\,')}']" \ "\n\n" \ @@ -1494,6 +1499,20 @@ into similar problems in the future (e.g. when new tables are created). SQL end + def drop_sequence(table_name, column_name, sequence_name) + execute <<~SQL + ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} DROP DEFAULT; + DROP SEQUENCE IF EXISTS #{quote_table_name(sequence_name)} + SQL + end + + def add_sequence(table_name, column_name, sequence_name, start_value) + execute <<~SQL + CREATE SEQUENCE #{quote_table_name(sequence_name)} START #{start_value}; + ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT nextval(#{quote(sequence_name)}) + SQL + end + private def create_temporary_columns_and_triggers(table, columns, primary_key: :id, data_type: :bigint) diff --git a/lib/gitlab/database/migration_helpers/announce_database.rb b/lib/gitlab/database/migration_helpers/announce_database.rb new file mode 100644 index 00000000000..28710aab717 --- /dev/null +++ b/lib/gitlab/database/migration_helpers/announce_database.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Gitlab + module Database + module MigrationHelpers + module AnnounceDatabase + extend ActiveSupport::Concern + + def write(text = "") + if text.present? # announce/say + super("#{db_config_name}: #{text}") + else + super(text) + end + end + + def db_config_name + @db_config_name ||= Gitlab::Database.db_config_name(connection) + end + end + end + end +end diff --git a/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb b/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb index d8d07fcaf2d..b8d1d21a0d2 100644 --- a/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb +++ b/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb @@ -21,7 +21,7 @@ module Gitlab end end - def migrate(direction) + def exec_migration(conn, direction) if unmatched_schemas.any? migration_skipped return @@ -37,8 +37,9 @@ module Gitlab private def migration_skipped - say "Current migration is skipped since it modifies "\ - "'#{self.class.allowed_gitlab_schemas}' which is outside of '#{allowed_schemas_for_connection}'" + say "The migration is skipped since it modifies the schemas: #{self.class.allowed_gitlab_schemas}." + say "This database can only apply migrations in one of the following schemas: " \ + "#{allowed_schemas_for_connection}." end def validator_class diff --git a/lib/gitlab/database/migrations/batched_background_migration_helpers.rb b/lib/gitlab/database/migrations/batched_background_migration_helpers.rb index 7113c3686f1..4aaeaa7b365 100644 --- a/lib/gitlab/database/migrations/batched_background_migration_helpers.rb +++ b/lib/gitlab/database/migrations/batched_background_migration_helpers.rb @@ -67,10 +67,22 @@ module Gitlab batch_class_name: BATCH_CLASS_NAME, batch_size: BATCH_SIZE, max_batch_size: nil, - sub_batch_size: SUB_BATCH_SIZE + sub_batch_size: SUB_BATCH_SIZE, + gitlab_schema: nil ) + Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_dml_mode! - if Gitlab::Database::BackgroundMigration::BatchedMigration.for_configuration(job_class_name, batch_table_name, batch_column_name, job_arguments).exists? + if transaction_open? + raise 'The `queue_batched_background_migration` cannot be run inside a transaction. ' \ + 'You can disable transactions by calling `disable_ddl_transaction!` in the body of ' \ + 'your migration class.' + end + + gitlab_schema ||= gitlab_schema_from_context + + Gitlab::Database::BackgroundMigration::BatchedMigration.reset_column_information + + if Gitlab::Database::BackgroundMigration::BatchedMigration.for_configuration(gitlab_schema, job_class_name, batch_table_name, batch_column_name, job_arguments).exists? Gitlab::AppLogger.warn "Batched background migration not enqueued because it already exists: " \ "job_class_name: #{job_class_name}, table_name: #{batch_table_name}, column_name: #{batch_column_name}, " \ "job_arguments: #{job_arguments.inspect}" @@ -119,24 +131,77 @@ module Gitlab end end + if migration.respond_to?(:gitlab_schema) + migration.gitlab_schema = gitlab_schema + end + migration.save! migration end def finalize_batched_background_migration(job_class_name:, table_name:, column_name:, job_arguments:) - database_name = Gitlab::Database.db_config_name(connection) + Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_dml_mode! - unless ActiveRecord::Base.configurations.primary?(database_name) - raise 'The `#finalize_background_migration` is currently not supported when running in decomposed database, ' \ - 'and this database is not `main:`. For more information visit: ' \ - 'https://docs.gitlab.com/ee/development/database/migrations_for_multiple_databases.html' + if transaction_open? + raise 'The `finalize_batched_background_migration` cannot be run inside a transaction. ' \ + 'You can disable transactions by calling `disable_ddl_transaction!` in the body of ' \ + 'your migration class.' end - migration = Gitlab::Database::BackgroundMigration::BatchedMigration.find_for_configuration(job_class_name, table_name, column_name, job_arguments) + Gitlab::Database::BackgroundMigration::BatchedMigration.reset_column_information + + migration = Gitlab::Database::BackgroundMigration::BatchedMigration.find_for_configuration( + gitlab_schema_from_context, job_class_name, table_name, column_name, job_arguments) raise 'Could not find batched background migration' if migration.nil? - Gitlab::Database::BackgroundMigration::BatchedMigrationRunner.finalize(job_class_name, table_name, column_name, job_arguments, connection: connection) + with_restored_connection_stack do |restored_connection| + Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.with_suppressed do + Gitlab::Database::BackgroundMigration::BatchedMigrationRunner.finalize( + job_class_name, table_name, + column_name, job_arguments, + connection: restored_connection) + end + end + end + + # Deletes batched background migration for the given configuration. + # + # job_class_name - The background migration job class as a string + # table_name - The name of the table the migration iterates over + # column_name - The name of the column the migration will batch over + # job_arguments - Migration arguments + # + # Example: + # + # delete_batched_background_migration( + # 'CopyColumnUsingBackgroundMigrationJob', + # :events, + # :id, + # ['column1', 'column2']) + def delete_batched_background_migration(job_class_name, table_name, column_name, job_arguments) + Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_dml_mode! + + if transaction_open? + raise 'The `#delete_batched_background_migration` cannot be run inside a transaction. ' \ + 'You can disable transactions by calling `disable_ddl_transaction!` in the body of ' \ + 'your migration class.' + end + + Gitlab::Database::BackgroundMigration::BatchedMigration.reset_column_information + + Gitlab::Database::BackgroundMigration::BatchedMigration + .for_configuration( + gitlab_schema_from_context, job_class_name, table_name, column_name, job_arguments + ).delete_all + end + + def gitlab_schema_from_context + if respond_to?(:allowed_gitlab_schemas) # Gitlab::Database::Migration::V2_0 + Array(allowed_gitlab_schemas).first + else # Gitlab::Database::Migration::V1_0 + :gitlab_main + end end end end diff --git a/lib/gitlab/database/migrations/reestablished_connection_stack.rb b/lib/gitlab/database/migrations/reestablished_connection_stack.rb index d7cf482c32a..addc9d874af 100644 --- a/lib/gitlab/database/migrations/reestablished_connection_stack.rb +++ b/lib/gitlab/database/migrations/reestablished_connection_stack.rb @@ -17,7 +17,9 @@ module Gitlab original_handler = ActiveRecord::Base.connection_handler original_db_config = ActiveRecord::Base.connection_db_config - return yield if ActiveRecord::Base.configurations.primary?(original_db_config.name) + if ActiveRecord::Base.configurations.primary?(original_db_config.name) + return yield(ActiveRecord::Base.connection) + end # If the `ActiveRecord::Base` connection is different than `:main` # re-establish and configure `SharedModel` context accordingly @@ -43,7 +45,7 @@ module Gitlab ActiveRecord::Base.establish_connection :main # rubocop:disable Database/EstablishConnection Gitlab::Database::SharedModel.using_connection(base_model.connection) do - yield + yield(base_model.connection) end ensure ActiveRecord::Base.connection_handler = original_handler diff --git a/lib/gitlab/database/partitioning/monthly_strategy.rb b/lib/gitlab/database/partitioning/monthly_strategy.rb index 9c8cccb3dc6..0f08a47d754 100644 --- a/lib/gitlab/database/partitioning/monthly_strategy.rb +++ b/lib/gitlab/database/partitioning/monthly_strategy.rb @@ -40,6 +40,10 @@ module Gitlab # No-op, required by the partition manager end + def validate_and_fix + # No-op, required by the partition manager + end + private def desired_partitions diff --git a/lib/gitlab/database/partitioning/partition_manager.rb b/lib/gitlab/database/partitioning/partition_manager.rb index 3ee9a193b45..aac91eaadb1 100644 --- a/lib/gitlab/database/partitioning/partition_manager.rb +++ b/lib/gitlab/database/partitioning/partition_manager.rb @@ -22,15 +22,13 @@ module Gitlab connection_name: @connection_name ) - # Double-checking before getting the lease: - # The prevailing situation is no missing partitions and no extra partitions - return if missing_partitions.empty? && extra_partitions.empty? - only_with_exclusive_lease(model, lease_key: MANAGEMENT_LEASE_KEY) do - partitions_to_create = missing_partitions - create(partitions_to_create) unless partitions_to_create.empty? + model.partitioning_strategy.validate_and_fix + partitions_to_create = missing_partitions partitions_to_detach = extra_partitions + + create(partitions_to_create) unless partitions_to_create.empty? detach(partitions_to_detach) unless partitions_to_detach.empty? end rescue StandardError => e @@ -119,7 +117,8 @@ module Gitlab 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}" + 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 diff --git a/lib/gitlab/database/partitioning/sliding_list_strategy.rb b/lib/gitlab/database/partitioning/sliding_list_strategy.rb index e9865fb91d6..5cf32d3272c 100644 --- a/lib/gitlab/database/partitioning/sliding_list_strategy.rb +++ b/lib/gitlab/database/partitioning/sliding_list_strategy.rb @@ -48,9 +48,12 @@ module Gitlab default_value = current_default_value if extra.any? { |p| p.value == default_value } - Gitlab::AppLogger.error(message: "Inconsistent partition detected: partition with value #{current_default_value} should not be deleted because it's used as the default value.", - partition_number: current_default_value, - table_name: model.table_name) + Gitlab::AppLogger.error( + message: "Inconsistent partition detected: partition with value #{current_default_value} should " \ + "not be deleted because it's used as the default value.", + partition_number: current_default_value, + table_name: model.table_name + ) extra = extra.reject { |p| p.value == default_value } end @@ -73,6 +76,42 @@ module Gitlab current_partitions.empty? end + def validate_and_fix + return unless Feature.enabled?(:fix_sliding_list_partitioning) + return if no_partitions_exist? + + old_default_value = current_default_value + expected_default_value = active_partition.value + + if old_default_value != expected_default_value + with_lock_retries do + model.connection.execute("LOCK TABLE #{model.table_name} IN ACCESS EXCLUSIVE MODE") + + old_default_value = current_default_value + expected_default_value = active_partition.value + + if old_default_value == expected_default_value + Gitlab::AppLogger.warn( + message: "Table partitions or partition key default value have been changed by another process", + table_name: table_name, + default_value: expected_default_value + ) + raise ActiveRecord::Rollback + end + + model.connection.change_column_default(model.table_name, partitioning_key, expected_default_value) + Gitlab::AppLogger.warn( + message: "Fixed default value of sliding_list_strategy partitioning_key", + column: partitioning_key, + table_name: table_name, + connection_name: model.connection.pool.db_config.name, + old_value: old_default_value, + new_value: expected_default_value + ) + end + end + end + private def current_default_value @@ -95,6 +134,14 @@ module Gitlab raise "Add #{partitioning_key} to #{model.name}.ignored_columns to use it with SlidingListStrategy" end end + + def with_lock_retries(&block) + Gitlab::Database::WithLockRetries.new( + klass: self.class, + logger: Gitlab::AppLogger, + connection: model.connection + ).run(&block) + end end end end diff --git a/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics.rb b/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics.rb index 391375d472f..06e2b114c91 100644 --- a/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics.rb +++ b/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics.rb @@ -27,15 +27,9 @@ module Gitlab # to reduce amount of labels sort schemas used gitlab_schemas = gitlab_schemas.to_a.sort.join(",") - # Temporary feature to observe relation of `gitlab_schemas` to `db_config_name` - # depending on primary model - ci_dedicated_primary_connection = ::Ci::ApplicationRecord.connection_class? && - ::Ci::ApplicationRecord.load_balancer.configuration.use_dedicated_connection? - schemas_metrics.increment({ gitlab_schemas: gitlab_schemas, - db_config_name: db_config_name, - ci_dedicated_primary_connection: ci_dedicated_primary_connection + db_config_name: db_config_name }) end diff --git a/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb b/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb index 3f0176cb654..c51282c9a55 100644 --- a/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb +++ b/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb @@ -9,7 +9,7 @@ module Gitlab DMLNotAllowedError = Class.new(UnsupportedSchemaError) DMLAccessDeniedError = Class.new(UnsupportedSchemaError) - IGNORED_SCHEMAS = %i[gitlab_shared].freeze + IGNORED_SCHEMAS = %i[gitlab_shared gitlab_internal].freeze class << self def enabled? diff --git a/lib/gitlab/database/shared_model.rb b/lib/gitlab/database/shared_model.rb index f4c8fca8fa2..877866b9b23 100644 --- a/lib/gitlab/database/shared_model.rb +++ b/lib/gitlab/database/shared_model.rb @@ -20,6 +20,15 @@ module Gitlab "to '#{Gitlab::Database.db_config_name(connection)}'" end + # connection might not be yet adopted (returning nil, and no gitlab_schemas) + # in such cases it is fine to ignore such connections + gitlab_schemas = Gitlab::Database.gitlab_schemas_for_connection(connection) + + unless gitlab_schemas.nil? || gitlab_schemas.include?(:gitlab_shared) + raise "Cannot set `SharedModel` to connection from `#{Gitlab::Database.db_config_name(connection)}` " \ + "since this connection does not include `:gitlab_shared` schema." + end + self.overriding_connection = connection yield |