Rate limit REST runner job lists filtered by completed status
What does this MR do and why?
Context
In !268206 (merged), we rate limited CiRunner.jobs when it is filtered by a completed status, because the filter makes the database read the runner's jobs until it fills a page. GET /runners/:id/jobs filters the same way, so in this step we apply the same bound to the REST endpoint. The figures behind it are in https://gitlab.com/gitlab-org/gitlab/-/work_items/638239#note_4175478465.
This MR
- Rate limits calls filtered by
success,failed, orcanceled, per user, on the same limit and application setting asCiRunner.jobs, so a user has one budget across both APIs. Over the limit, the endpoint returns429 Too Many Requests. - Logs calls over that limit instead of blocking them, in both APIs, until a second flag enforces it. New rate limits roll out in log mode first, so the logs show who a limit would block before it blocks anyone. The limit key now uses
staged_rule. No instance enforces it yet, becauseCiRunner.jobs's flag is still off everywhere. - Returns no jobs without querying when the
statusis one a runner's jobs cannot have, or when the user has no authorized project. Both requests matched nothing but still read every job of the runner.
This caps how often one user can make GitLab read through a runner's job history over REST.
Feature Flags:
ci_rate_limit_rest_runner_job_list_completed_statuses: the REST changes above, with the limit in log modeci_enforce_runner_job_list_completed_statuses_limit: blocks calls over the limit, in GraphQL and REST
No changelog entry while the limit is behind the disabled flag. The entry comes with the flag-removal MR.
References
- https://gitlab.com/gitlab-org/gitlab/-/work_items/638239+
- Rate limit runner job lists filtered by complet... (!268206 - merged)
- https://gitlab.com/gitlab-org/gitlab/-/work_items/638937+
- https://gitlab.com/gitlab-org/gitlab/-/work_items/638961+
- Roll out a key in log mode
How to set up and validate locally
- In a Rails console:
Feature.enable(:ci_rate_limit_rest_runner_job_list_completed_statuses) ApplicationSetting.current.update!(runner_job_list_completed_statuses_limit_per_user: 1) - Wait a minute, so the web server picks up the new flag and setting.
- Run this twice, with an administrator's personal access token and the ID of any runner that has run jobs:
Both return
curl --header "PRIVATE-TOKEN: <your_access_token>" "http://gdk.test:3000/api/v4/runners/<id>/jobs?status=failed"200. Inlog/api_json.log, the second request'srate_limit_stateincludesapplimiter_runner_job_list_completed_statuses:limit_runner_job_list_completed_status_filters_by_user_log:log. - Run
Feature.enable(:ci_enforce_runner_job_list_completed_statuses_limit)and wait a minute. - Run the
curltwice more. The second returns429with{"message":{"error":"This endpoint has been requested too many times. Try again later."}}. The enforced limit counts from zero, so the first still returns200. - Run it with
status=running. It returns200, because the limit applies only tosuccess,failed, andcanceled. - Run it with
status=pending. It returns[]. Inlog/api_json.log, itsdb_ci_countis 2 lower than thestatus=runningrequest, because it skips the count and the page queries.
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.