Add an application rate limit to merge request rebase
What does this MR do and why?
You can rebase a merge request in three ways: the REST API (PUT /projects/:id/merge_requests/:merge_request_iid/rebase), the Rebase button, and the /rebase quick action. All three schedule the same rebase through MergeRequest#rebase_async, and none of them has an application rate limit.
This MR adds a merge_requests_rebase limit of 30 rebases per minute for each user, shared by the three entry points. It's behind the merge_requests_rebase_rate_limit feature flag, which is off by default. Admins can change the limit with the merge_requests_rebase_limit application setting (stored in the rate_limits jsonb column, so no migration), or set it to 0 to turn it off.
Each entry point checks the limit after its own permission checks, so requests that fail them don't count. Over the limit:
| Entry point | Response |
|---|---|
| REST API | 429 Too Many Requests with Retry-After, like other API rate limits |
| Rebase button | 429 with merge_error. The widget shows "Failed to rebase: Too many rebases have been requested. Try again later." |
/rebase quick action |
Nothing is scheduled, and the quick action shows "Too many rebases have been requested. Try again later.", like its other rebase failures |
Evidence for the default limit from production logs: !267972 (comment 4168788537)
Rollback: disable merge_requests_rebase_rate_limit, or set merge_requests_rebase_limit to 0.
Before enabling on GitLab.com
The remaining rate limit process steps (stakeholders, release post, Criticality 2 change request, and the GitLab.com rate limits docs) are tracked in the rollout issue, #638595. The flag is off by default, so merging doesn't change any limit.
References
- Related to https://gitlab.com/gitlab-org/gitlab/-/work_items/606716
- Rollout issue: #638595
Detailed context for reviewers and AI agents
Shape
merge_requests_rebase_limitin therate_limitsjsonb application setting, with JSON schema entry, numericality validation through the existing list, the helper's visible attributes, and a settings API parameter. The setting follows the thread resolution limit (!264595 (merged)).- Labkit rule
merge_requests_rebase(limit_merge_request_rebases_by_user), characteristicuser, period 1 minute. Gitlab::MergeRequestRebaseRateLimitholds the key, scope, and flag check, so the three entry points stay consistent. It followsGitlab::NotesCreateRateLimit(!265720 (merged)).- The controller and the API call
check_merge_request_rebase_rate_limit!(check_rate_limit!), so throttled requests get the standard response and an auth log entry. As with other application rate limits, requests that send the rate limit bypass header aren't limited. - The quick action calls
.throttled?and reports the limit through@execution_message[:rebase], like its other rebase failures.
- The controller and the API call
Choices to confirm
- Key per user, not per merge request. The limit counts all of a user's rebases, whichever merge request they're for.
- Default 30 every minute per user. Agreed with product. Last week it would have blocked 257 rebase requests (0.09%) from 6 users, all mainly using automated clients. The busiest minute for one user was 60 requests. See the evidence comment.
- Flag:
gitlab_com_derisk, user actor, so we can roll out to ourselves first and then by percentage of users. - No Admin UI. Like the other flagged 19.6 limits, the setting is only available through the settings API and Rails console while it's in beta.
Docs
doc/api/settings.md (history entry and attribute row), doc/rate_limits/_index.md, and doc/api/openapi/openapi_v3.yaml.
How to set up and validate locally
-
In the Rails console, enable the flag and lower the limit:
Feature.enable(:merge_requests_rebase_rate_limit) ApplicationSetting.current.update!(merge_requests_rebase_limit: 2) -
Pick a merge request whose source branch you can push to.
-
Rebase it three times within a minute, waiting for each rebase to finish:
- API:
curl --request PUT --header "PRIVATE-TOKEN: <token>" "http://gdk.test:3000/api/v4/projects/<project id>/merge_requests/<iid>/rebase". The third request returns429. - Rebase button: the third click shows "Failed to rebase: Too many rebases have been requested. Try again later."
- Quick action: comment
/rebase. The third comment shows "Too many rebases have been requested. Try again later."
- API:
-
Disable the flag (
Feature.disable(:merge_requests_rebase_rate_limit)) and repeat. No rebase is limited. -
Re-enable the flag, set the limit to
0, and repeat. No rebase is limited.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.