CI jobs can time out on POST /api/v4/jobs/request due to unbounded CI_MERGE_REQUEST_APPROVED lazy preload

Summary

POST /api/v4/jobs/request can return HTTP 500 (Rack::Timeout::RequestTimeoutException) ~60s after a runner picks up a job, instead of a valid payload. Affected jobs hang until their timeout with no trace output. It occurs on merge request pipelines where resolving the predefined CI_MERGE_REQUEST_APPROVED variable triggers an expensive, unbounded preload.

Root cause

An interaction between two independent changes, amplified by scale:

  1. Unbounded preload (d2bd00b413c0, Feb 2025). The EE approval-variable preloader unconditionally preloads merge_request_diff → merge_request_diff_commits → commit_author/committer over all commits. That data is only needed when the project has "Prevent approval by users who add commits" enabled (ee/app/models/approval_state.rbmerge_request.committers), but it fires regardless of the setting and regardless of commit count.
  2. Lazy resolution moved to the request path (!234842 (merged), 19.0). Removing the ci_lazy_predefined_variables flag made CI_MERGE_REQUEST_APPROVED resolve lazily/unconditionally, shifting that preload out of pipeline creation and into /jobs/request, inside the 60s Rack window.

Trigger: Ci::BuildRunnerPresenter#git_depth_value calls variables.to_hash, forcing evaluation of every lazy variable (including CI_MERGE_REQUEST_APPROVED) just to read GIT_DEPTH. On MRs with thousands of commits, the preload exhausts the timeout budget.

Impact

  • MR pipelines fail silently: jobs are picked up, /jobs/request 500s after ~60s, and jobs hang to timeout with no trace.
  • Worst on MRs with large diffs (thousands of commits); affects hosted and self-managed runners (server-side).

Steps to reproduce

  1. Create an MR whose diff contains a large number of commits (order of thousands).
  2. Run a merge request pipeline.
  3. POST /api/v4/jobs/request takes ~60s and returns HTTP 500 (Rack::Timeout), with CI_MERGE_REQUEST_APPROVED resolution in the backtrace.

Proposed fix

Behind feature flag ci_optimize_merge_request_approved_resolution (default off):

  1. Scope the commit preload so it only runs when merge_requests_disable_committers_approval? is enabled (the only case that reads it).
  2. Change git_depth_value to a targeted variables["GIT_DEPTH"] lookup instead of to_hash.

Follow-up (separate, approvals/mergeability domain)

When "Prevent approval by users who add commits" is enabled, the preload is genuinely needed — but deriving committer identity by loading every diff commit is itself unbounded. Computing committer user IDs via a bounded query should be tracked separately.