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/toconfig/feature_flags/beta/(the flag directory must match the flagtype, so the move is required). typechanges fromgitlab_com_derisktobeta.default_enabledchanges fromfalsetotrue.
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 ongitlab-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.