diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index c11d3543183507a6bf04a1a1e46e10e29ccc51c5..dc23cc96bb4b30dedf6c78c34d9f2444faca6ab3 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -83,6 +83,7 @@ class MergeRequest < ApplicationRecord has_one :merge_schedule, class_name: 'MergeRequests::MergeSchedule', inverse_of: :merge_request has_one :metrics, inverse_of: :merge_request, autosave: true + has_one :mergeability_status, class_name: 'MergeRequests::MergeabilityStatus', inverse_of: :merge_request belongs_to :latest_merge_request_diff, class_name: 'MergeRequestDiff' manual_inverse_association :latest_merge_request_diff, :merge_request diff --git a/app/models/merge_requests/mergeability_status.rb b/app/models/merge_requests/mergeability_status.rb new file mode 100644 index 0000000000000000000000000000000000000000..31435cf3cfb0e19ed5e4a496ef407d7946e89606 --- /dev/null +++ b/app/models/merge_requests/mergeability_status.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module MergeRequests + class MergeabilityStatus < ApplicationRecord + include PartitionedTable + + self.table_name = 'merge_requests_mergeability_statuses' + self.primary_key = 'merge_request_id' + + belongs_to :merge_request, inverse_of: :mergeability_status + belongs_to :project + + partitioned_by :merge_request_id, strategy: :int_range, partition_size: 100_000_000 + + validates :project, presence: true + validates :merge_request, presence: true + validates :status, presence: true + end +end diff --git a/db/docs/merge_requests_mergeability_statuses.yml b/db/docs/merge_requests_mergeability_statuses.yml new file mode 100644 index 0000000000000000000000000000000000000000..c1e5ddc02e3ad8a9a3d5d285d0ee4e7b926371dd --- /dev/null +++ b/db/docs/merge_requests_mergeability_statuses.yml @@ -0,0 +1,16 @@ +--- +table_name: merge_requests_mergeability_statuses +classes: +- MergeRequests::MergeabilityStatus +feature_categories: +- code_review_workflow +description: | + Stores the mergeability status for merge requests. This table was extracted from + the merge_requests table to reduce WAL pressure caused by frequent merge_status updates + on the heavily indexed merge_requests table. +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/XXXXX +milestone: "18.3" +gitlab_schema: gitlab_main_cell +sharding_key: + project_id: projects +table_size: small diff --git a/db/migrate/20250724040708_create_merge_request_mergeability_statuses.rb b/db/migrate/20250724040708_create_merge_request_mergeability_statuses.rb new file mode 100644 index 0000000000000000000000000000000000000000..9505bd5c5fd8bc9ca7fc69e6641186df564a4b62 --- /dev/null +++ b/db/migrate/20250724040708_create_merge_request_mergeability_statuses.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class CreateMergeRequestMergeabilityStatuses < Gitlab::Database::Migration[2.3] + include Gitlab::Database::PartitioningMigrationHelpers + + disable_ddl_transaction! + milestone '18.3' + + TABLE_NAME = :merge_requests_mergeability_statuses + SOURCE_TABLE_NAME = 'merge_requests' + PARTITION_SIZE = 100_000_000 + MIN_ID = 1 + + def up + create_table TABLE_NAME, id: false, options: 'PARTITION BY RANGE (merge_request_id)' do |t| + t.bigint :merge_request_id, null: false, primary_key: true, default: nil, index: false + t.bigint :project_id, null: false, index: true + t.datetime_with_timezone :prepared_at + t.timestamps_with_timezone null: false + t.column :status, :smallint, null: false, default: 0 + end + + create_partitions + end + + def down + drop_table TABLE_NAME + end + + private + + def create_partitions + max_id = Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.with_suppressed do + Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection.with_suppressed do + define_batchable_model(SOURCE_TABLE_NAME, connection: connection).maximum(:id) || MIN_ID + end + end + + create_int_range_partitions(TABLE_NAME, PARTITION_SIZE, MIN_ID, max_id) + end +end diff --git a/db/migrate/20250725012052_add_merge_request_id_fk_to_mergeability_statuses.rb b/db/migrate/20250725012052_add_merge_request_id_fk_to_mergeability_statuses.rb new file mode 100644 index 0000000000000000000000000000000000000000..81aa168d62d3049746ebb5ae8621eb484d797a0c --- /dev/null +++ b/db/migrate/20250725012052_add_merge_request_id_fk_to_mergeability_statuses.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AddMergeRequestIdFkToMergeabilityStatuses < Gitlab::Database::Migration[2.3] + include Gitlab::Database::PartitioningMigrationHelpers + + disable_ddl_transaction! + milestone '18.3' + + TABLE_NAME = :merge_requests_mergeability_statuses + + def up + add_concurrent_partitioned_foreign_key TABLE_NAME, :merge_requests, column: :merge_request_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key TABLE_NAME, column: :merge_request_id + end + end +end diff --git a/db/migrate/20250725012122_add_project_id_fk_to_mergeability_statuses.rb b/db/migrate/20250725012122_add_project_id_fk_to_mergeability_statuses.rb new file mode 100644 index 0000000000000000000000000000000000000000..7bb8176eb1e441f7b0239ea105f2a66b5ed15cb1 --- /dev/null +++ b/db/migrate/20250725012122_add_project_id_fk_to_mergeability_statuses.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# See https://docs.gitlab.com/ee/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddProjectIdFkToMergeabilityStatuses < Gitlab::Database::Migration[2.3] + include Gitlab::Database::PartitioningMigrationHelpers + + disable_ddl_transaction! + milestone '18.3' + + TABLE_NAME = :merge_requests_mergeability_statuses + + def up + add_concurrent_partitioned_foreign_key TABLE_NAME, :projects, column: :project_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key TABLE_NAME, column: :project_id + end + end +end diff --git a/db/schema_migrations/20250724040708 b/db/schema_migrations/20250724040708 new file mode 100644 index 0000000000000000000000000000000000000000..349c3bb82104fe00a54aa17c98fc3c5fc05ac138 --- /dev/null +++ b/db/schema_migrations/20250724040708 @@ -0,0 +1 @@ +c40be25c0076cf40fbe1f4e2cba5875ece213bdd71a81fbfa736e3c5d8ba0714 \ No newline at end of file diff --git a/db/schema_migrations/20250725012052 b/db/schema_migrations/20250725012052 new file mode 100644 index 0000000000000000000000000000000000000000..72e798c39d99dbbee6baeb1f1d9ac60eb5227cf8 --- /dev/null +++ b/db/schema_migrations/20250725012052 @@ -0,0 +1 @@ +65dbae7edee06dd36d8c7a69662699b057add0bbae6f21402f1bf958f0d8166b \ No newline at end of file diff --git a/db/schema_migrations/20250725012122 b/db/schema_migrations/20250725012122 new file mode 100644 index 0000000000000000000000000000000000000000..455b2c0e126f28b69d24be6d9804cc952b294ab1 --- /dev/null +++ b/db/schema_migrations/20250725012122 @@ -0,0 +1 @@ +ba0ba921477fbc434203c251e1bd8e646ad28290513eb5393e9c4e3cf7d3a636 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 550304725905a606f0fa152bfa7833c1c501872f..def8a83702c07f14c6b1652ea2724f491ce6c89e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -5060,6 +5060,16 @@ CREATE TABLE merge_request_commits_metadata ( ) PARTITION BY RANGE (project_id); +CREATE TABLE merge_requests_mergeability_statuses ( + merge_request_id bigint NOT NULL, + project_id bigint NOT NULL, + prepared_at timestamp with time zone, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + status smallint DEFAULT 0 NOT NULL +) +PARTITION BY RANGE (merge_request_id); + CREATE TABLE p_ai_active_context_code_enabled_namespaces ( id bigint NOT NULL, namespace_id bigint NOT NULL, @@ -31070,6 +31080,9 @@ ALTER TABLE ONLY merge_requests_closing_issues ALTER TABLE ONLY merge_requests_compliance_violations ADD CONSTRAINT merge_requests_compliance_violations_pkey PRIMARY KEY (id); +ALTER TABLE ONLY merge_requests_mergeability_statuses + ADD CONSTRAINT merge_requests_mergeability_statuses_pkey PRIMARY KEY (merge_request_id); + ALTER TABLE ONLY merge_requests ADD CONSTRAINT merge_requests_pkey PRIMARY KEY (id); @@ -36858,6 +36871,8 @@ CREATE UNIQUE INDEX index_merge_requests_compliance_violations_unique_columns ON CREATE INDEX index_merge_requests_for_latest_diffs_with_state_merged ON merge_requests USING btree (latest_merge_request_diff_id, target_project_id) WHERE (state_id = 3); +CREATE INDEX index_merge_requests_mergeability_statuses_on_project_id ON ONLY merge_requests_mergeability_statuses USING btree (project_id); + CREATE INDEX index_merge_requests_on_assignee_id ON merge_requests USING btree (assignee_id); CREATE INDEX index_merge_requests_on_author_id_and_created_at ON merge_requests USING btree (author_id, created_at); @@ -46268,6 +46283,9 @@ ALTER TABLE ONLY merge_request_diff_details ALTER TABLE ONLY packages_package_file_build_infos ADD CONSTRAINT fk_rails_871ca3ae21 FOREIGN KEY (package_file_id) REFERENCES packages_package_files(id) ON DELETE CASCADE; +ALTER TABLE merge_requests_mergeability_statuses + ADD CONSTRAINT fk_rails_871fe00a04 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE; + ALTER TABLE ONLY boards_epic_boards ADD CONSTRAINT fk_rails_874c573878 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; @@ -47165,6 +47183,9 @@ ALTER TABLE ONLY banned_users ALTER TABLE ONLY targeted_message_dismissals ADD CONSTRAINT fk_rails_fa9d2df3f9 FOREIGN KEY (targeted_message_id) REFERENCES targeted_messages(id) ON DELETE CASCADE; +ALTER TABLE merge_requests_mergeability_statuses + ADD CONSTRAINT fk_rails_fae63232c6 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; + ALTER TABLE ONLY operations_feature_flags_issues ADD CONSTRAINT fk_rails_fb4d2a7cb1 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE; diff --git a/lib/gitlab/import_export/base/relation_factory.rb b/lib/gitlab/import_export/base/relation_factory.rb index 7c63576b1f4e223d422058ac78befb8bcf13ab30..6946e6947203d4571d196d513097e39d4ff5c9d4 100644 --- a/lib/gitlab/import_export/base/relation_factory.rb +++ b/lib/gitlab/import_export/base/relation_factory.rb @@ -9,7 +9,7 @@ class RelationFactory IMPORTED_OBJECT_MAX_RETRIES = 5 - OVERRIDES = { user_contributions: :user, merge_schedule: 'MergeRequests::MergeSchedule' }.freeze + OVERRIDES = { user_contributions: :user, merge_schedule: 'MergeRequests::MergeSchedule', mergeability_status: 'MergeRequests::MergeabilityStatus' }.freeze EXISTING_OBJECT_RELATIONS = %i[].freeze # This represents all relations that have unique key on `project_id` or `group_id` diff --git a/scripts/migration_schema_validator.rb b/scripts/migration_schema_validator.rb index b977e843e854ccbeb5d34bcfd88c0f7a59917458..b7ad41f71baa7df420055939493428be998edd96 100644 --- a/scripts/migration_schema_validator.rb +++ b/scripts/migration_schema_validator.rb @@ -45,9 +45,18 @@ def validate! end # only check allowed to be skipped is validate_schema_on_rollback! + # validate_ignore_columns! + # validate_schema_on_migrate! + # validate_schema_version_files! + # + # if skip_validation? + # puts "\e[32m Label #{SKIP_VALIDATION_LABEL} is present, skipping schema validation\e[0m" + # return + # end + # + # validate_schema_on_rollback! + # validate_ignore_columns! should never be skipped, the ignore_column directive must always be present validate_ignore_columns! - validate_schema_on_migrate! - validate_schema_version_files! if skip_validation? puts "\e[32m Label #{SKIP_VALIDATION_LABEL} is present, skipping schema validation\e[0m" @@ -55,6 +64,8 @@ def validate! end validate_schema_on_rollback! + validate_schema_on_migrate! + validate_schema_version_files! end private diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index b25df89ce96f041782c38779274d7a964b694c7f..d34062dcb5a0f904e466171f8faa443b87de661e 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -225,6 +225,7 @@ merge_requests: - todos - target_project - source_project +- mergeability_status - merge_user - merge_request_diffs - merge_request_diff @@ -289,6 +290,9 @@ merge_requests: - approval_metrics external_pull_requests: - project +mergeability_status: +- merge_request +- project merge_request_diff: - merge_request - merge_request_diff_commits