Skip to content

Destroy merge request diffs in batches

What does this MR do and why?

Problem

Project delete causes a timeout error, because of DELETE FROM projects query.

Solution

projects table has multiple triggers on cascade delete. One of them took significant time to process. https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/11757/commands/41704

      {
        "Trigger Name": "RI_ConstraintTrigger_a_20354",
        "Constraint Name": "fk_06067f5644",
        "Relation": "merge_request_diffs",
        "Time": 24507.703,
        "Calls": 11344
      },

Extract merge_request_diffs deletion process into a separate statement.

Database

SELECT "merge_requests"."iid" FROM "merge_requests" WHERE "merge_requests"."target_project_id" = 33887545 ORDER BY "merge_requests"."iid" ASC LIMIT 1

https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/11757/commands/41705

SELECT "merge_requests"."iid" FROM "merge_requests" WHERE "merge_requests"."target_project_id" = 33887545 AND "merge_requests"."iid" >= 1 ORDER BY "merge_requests"."iid" ASC LIMIT 1 OFFSET 100

https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/11757/commands/41706

DELETE FROM "merge_request_diffs" WHERE "merge_request_diffs"."id" IN (SELECT "merge_request_diffs"."id" FROM "merge_request_diffs" WHERE "merge_request_diffs"."merge_request_id" IN (SELECT "merge_requests"."id" FROM "merge_requests" WHERE "merge_requests"."target_project_id" = 33 AND "merge_requests"."iid" >= 1 AND "merge_requests"."iid" < 101) LIMIT 1000)

https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/11825/commands/41952

Screenshots or screen recordings

Before

Project Destroy (81.7ms)  DELETE FROM "projects" WHERE "projects"."id" = 33 /*application:console,db_config_name:main,line:/app/services/projects/destroy_service.rb:146:in `destroy_project_related_records'*/

After

MergeRequest Load (0.1ms)  SELECT "merge_requests"."iid" FROM "merge_requests" WHERE "merge_requests"."target_project_id" = 33 ORDER BY "merge_requests"."iid" ASC LIMIT 1 /*application:console,db_config_name:main,line:/app/models/concerns/each_batch.rb:62:in `each_batch'*/
MergeRequest Load (0.1ms)  SELECT "merge_requests"."iid" FROM "merge_requests" WHERE "merge_requests"."target_project_id" = 33 AND "merge_requests"."iid" >= 1 ORDER BY "merge_requests"."iid" ASC LIMIT 1 OFFSET 100 /*application:console,db_config_name:main,line:/app/models/concerns/each_batch.rb:81:in `block in each_batch'*/
MergeRequestDiff Destroy (11.0ms)  DELETE FROM "merge_request_diffs" WHERE "merge_request_diffs"."id" IN (SELECT "merge_request_diffs"."id" FROM "merge_request_diffs" WHERE "merge_request_diffs"."merge_request_id" IN (SELECT "merge_requests"."id" FROM "merge_requests" WHERE "merge_requests"."target_project_id" = 33 AND "merge_requests"."iid" >= 1 AND "merge_requests"."iid" < 101) LIMIT 1000) /*application:console,db_config_name:main,line:/app/services/projects/destroy_service.rb:195:in `block (2 levels) in destroy_merge_request_diffs!'*/
MergeRequestDiff Destroy (0.4ms)  DELETE FROM "merge_request_diffs" WHERE "merge_request_diffs"."id" IN (SELECT "merge_request_diffs"."id" FROM "merge_request_diffs" WHERE "merge_request_diffs"."merge_request_id" IN (SELECT "merge_requests"."id" FROM "merge_requests" WHERE "merge_requests"."target_project_id" = 33 AND "merge_requests"."iid" >= 1 AND "merge_requests"."iid" < 101) LIMIT 1000) /*application:console,db_config_name:main,line:/app/services/projects/destroy_service.rb:195:in `block (2 levels) in destroy_merge_request_diffs!'*/
MergeRequest Load (0.2ms)  SELECT "merge_requests"."iid" FROM "merge_requests" WHERE "merge_requests"."target_project_id" = 33 AND "merge_requests"."iid" >= 101 ORDER BY "merge_requests"."iid" ASC LIMIT 1 OFFSET 100 /*application:console,db_config_name:main,line:/app/models/concerns/each_batch.rb:81:in `block in each_batch'*/
MergeRequestDiff Destroy (7.8ms)  DELETE FROM "merge_request_diffs" WHERE "merge_request_diffs"."id" IN (SELECT "merge_request_diffs"."id" FROM "merge_request_diffs" WHERE "merge_request_diffs"."merge_request_id" IN (SELECT "merge_requests"."id" FROM "merge_requests" WHERE "merge_requests"."target_project_id" = 33 AND "merge_requests"."iid" >= 101) LIMIT 1000) /*application:console,db_config_name:main,line:/app/services/projects/destroy_service.rb:195:in `block (2 levels) in destroy_merge_request_diffs!'*/
MergeRequestDiff Destroy (0.5ms)  DELETE FROM "merge_request_diffs" WHERE "merge_request_diffs"."id" IN (SELECT "merge_request_diffs"."id" FROM "merge_request_diffs" WHERE "merge_request_diffs"."merge_request_id" IN (SELECT "merge_requests"."id" FROM "merge_requests" WHERE "merge_requests"."target_project_id" = 33 AND "merge_requests"."iid" >= 101) LIMIT 1000) /*application:console,db_config_name:main,line:/app/services/projects/destroy_service.rb:195:in `block (2 levels) in destroy_merge_request_diffs!'*/
...
Project Destroy (52.0ms)  DELETE FROM "projects" WHERE "projects"."id" = 33 /*application:console,db_config_name:main,line:/app/services/projects/destroy_service.rb:146:in `destroy_project_related_records'*/

How to set up and validate locally

Feature.enable(:extract_mr_diff_deletions)

user = User.first
project = FactoryBot.create(:project, :repository, namespace: user.namespace)

# create 200 merge requests
200.times { |i| FactoryBot.create(:merge_request, source_project: project, target_branch: "test-#{i}") }

# show SQL queries in console
ActiveRecord::Base.logger = Logger.new(STDOUT)

# execute project deletion process and roll it back (to avoid constant project recreation)`
ActiveRecord::Base.transaction { Projects::DestroyService.new(project, user, {}).execute; raise ActiveRecord::Rollback; }

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Vasilii Iakliushin

Merge request reports

Loading