Enable batched commit lookup in RefreshService by default

Beta rollout for the batched commit-lookup fix from !248701 (merged) (INC-12753). Moves config/feature_flags/gitlab_com_derisk/merge_request_refresh_batched_commit_lookup.yml to config/feature_flags/beta/ and flips default_enabled to true, so GitLab Self-Managed gets the faster path by default while still being able to disable it. The flag has been on at 100 percent on GitLab.com since 2026-08-26. No application code changes.

Detailed context for AI agents

What changed

Single file move plus two field edits in config/feature_flags/gitlab_com_derisk/merge_request_refresh_batched_commit_lookup.yml:

  • Directory moves from config/feature_flags/gitlab_com_derisk/ to config/feature_flags/beta/ (the flag directory must match the flag type, so the move is required).
  • type changes from gitlab_com_derisk to beta.
  • default_enabled changes from false to true.

No application code or spec changes are included.

Why the type must change

gitlab_com_derisk flags cannot be default_enabled: true. This is validated by Feature::Definition#validate_default_enabled! via can_be_default_enabled in lib/feature/shared.rb. Switching the type to beta is what makes default-on possible.

What the flag does

merge_request_refresh_batched_commit_lookup batches the commit-membership check that MergeRequests::RefreshService runs against open merge requests on a pushed branch. Without it, for every push, the service asks each open merge request individually whether its diff already contains any of the pushed commits (MergeRequestDiff#includes_any_commits?), re-serialising the full commit list into bind parameters each time, so cost scales with merge request count multiplied by push size. With it, the whole set resolves in one batched query per 1,000-commit chunk via MergeRequestDiff.ids_including_any_commits, independent of merge request count. Same merge requests are selected, same diffs are reloaded, no commits are dropped.

The only behaviour the flag decides is whether a merge request the push reaches only through its target branch gets its diff reloaded. A push to a merge request's own source branch reloads unconditionally, and a force push reloads every diff and skips the lookup entirely.

Why this exists

Corrective action for INC-12753 (gitlab-com/gl-infra/production#22645 (closed)), where large mirror pushes ran UpdateMergeRequestsWorker for 5-10 hours at cpu/wall ratio ~0.93 and degraded the shared urgent-cpu-bound Sidekiq shard.

Introduced by !248701 (merged) (merged 2026-08-19, deployed to production 2026-08-20). Feature issue: #608151 (closed). Rollout issue: #608174 (closed). The original milestone 19.3 is kept as-is in the YAML because that is when the flag was introduced.

Rollout history on GitLab.com (all recorded on the rollout issue)

  • 2026-08-20: enabled on staging, then on a single project (marc_shaw/test), then on gitlab-org/gitlab.
  • 2026-08-21: enabled on the project from the incident, then percentage of actors 10 -> 25 -> 50 (dialled back to 25 over the weekend).
  • 2026-08-25: back to 50 percent.
  • 2026-08-26: globally enabled.

It has been at 100 percent on GitLab.com for roughly 12 days with no incidents, no rollbacks, and no exception reports.

Performance verification

On GDK with synthetic history (30,000-commit push, 200 open merge requests on the branch): CPU 83.1s to 0.84s, queries 12,019 to 79, allocations 128M to 1.7M.

On production, verified from a test project: db_main_count 832 to 36, a 95.7 percent drop.

Rollback

Set the flag to false globally with chatops. No data migration and nothing to repair - the next push simply takes the old code path.

Test coverage

Existing spec coverage is unchanged and already covers both states: spec/services/merge_requests/refresh_service_spec.rb has default-enabled examples plus two self-contained contexts using stub_feature_flags(merge_request_refresh_batched_commit_lookup: false) for the old path.

  • bundle exec rspec spec/services/merge_requests/refresh_service_spec.rb - 99 examples, 0 failures.
  • bundle exec rspec spec/lib/feature/definition_spec.rb - 41 examples, 0 failures (validates the new type and file path).

Out of scope

Full removal of the flag and its checks. MergeRequestDiff#includes_any_commits? stays in place because it is still the only caller-side dependency of the flag-off path, which this MR keeps working. Removing the flag, the Feature.enabled? check, the old code path, and that now-single-purpose model predicate is a follow-up cleanup once beta is over.

The source branch is named 608174-remove-batched-commit-lookup-ff from an earlier plan to delete the flag outright. The branch name is stale; this MR only flips the default.

Edited by Marc Shaw

Merge request reports

Loading
Loading