Skip fork MR lookup for non-forks in Ci::Pipeline#all_merge_requests

What does this MR do and why?

For a branch pipeline, Ci::Pipeline#all_merge_requests scopes its commit lookup to a set of target project ids, collected by plucking target_project_id from every fork MR on (source_project_id, source_branch):

target_project_ids = merge_requests_for_source_project.from_fork.pluck(:target_project_id)
target_project_ids << project_id

from_fork is where('source_project_id <> target_project_id'), with no LIMIT and no state filter, so the pluck walks the entire (project, branch) MR history. On projects that accumulate a very large history for one branch, the scan can exceed the statement timeout and 500 the pipeline page, which is what appears to be happening in request-for-help#4998 (details in the investigation comment). A non-fork project pays that full scan cost to typically collect nothing.

This MR adds a guard behind the ci_skip_fork_mr_lookup_for_non_forks feature flag (gitlab_com_derisk): when the project is not a fork, skip the pluck and use [project_id] directly. When it is a fork, the pluck still runs, now with .distinct (the consumer only needs the distinct set of ids). The guard treats "not a fork" as a proxy for "no cross-project MRs sourced from this project", which holds for the standard fork workflow. With the flag off, behavior is byte-for-byte unchanged.

Related: https://gitlab.com/gitlab-com/request-for-help/-/work_items/4998+.

Query plans

The changed query is the fork-target pluck. Plans captured on the GitLab.com production main replica.

Before (current master), on a large non-fork project — cold cache

SELECT target_project_id FROM merge_requests
WHERE source_project_id = 17679285 AND source_branch = 'master'
AND (source_project_id <> target_project_id);

Explain Output

https://postgres.ai/console/gitlab/gitlab-production-main/sessions/53566/commands/155743

After — non-fork project (the common case)

No query is issued. project.forked? is false, so the pluck is skipped and target_project_ids = [project_id].

After — genuine fork, with .distinct — cold cache

SELECT DISTINCT target_project_id FROM merge_requests
WHERE source_project_id = 16983597 AND source_branch = 'master'
AND (source_project_id <> target_project_id);

Explain Output

https://postgres.ai/console/gitlab/gitlab-production-main/sessions/53566/commands/155747

Feature flag

ci_skip_fork_mr_lookup_for_non_forks (gitlab_com_derisk, default_enabled: false), actor: project.

How to verify

This is a performance change, not a behavior change: #all_merge_requests returns the exact same MRs.

Production validation: after enabling for the affected group, confirm the merge_requests fork-pluck query disappears from the slow-query logs and the group's pipeline pages stop timing out.

Database review

  • This modifies the query built by Ci::Pipeline#all_merge_requests (via the from_fork scope). Please review the scope usage.
  • Old and new raw SQL and query plans are above.
Edited by Sahil Sharma

Merge request reports

Loading