Destroy merge request diffs in batches
What does this MR do and why?
- Contributes to #352511 (closed)
- Feature flag: #372060 (closed)
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.
-
I have evaluated the MR acceptance checklist for this MR.