Render release descriptions before the save and limit release writes

What does this MR do and why?

Context

Creating or updating a release renders its description inside the save transaction, so a large description holds a database connection for the whole render. Nothing bounds how often one user or project writes releases. The figures and the agreed approach are in https://gitlab.com/gitlab-org/gitlab/-/work_items/638256#note_4182618868.

This MR

  • The release services render the description before the save transaction and skip the save's own render, so the transaction holds only the writes. This covers a release that publishes to the CI/CD catalog, which saves inside the catalog version's transaction. The stored HTML is the same. A release that fails validation is not rendered first.
  • Release creates and updates through the REST API and GraphQL releaseCreate and releaseUpdate count toward two new rate limits, per project (release_writes_limit_per_project, default 300 per minute) and per user (release_writes_limit_per_user, default 600 per minute). Both are rate_limits application settings on Admin > Settings > Network and in the application settings API, and 0 disables each.
  • The check runs after the access check, so users without access to a project don't spend its budget, and before any tag, render or catalog work. Refused and failed attempts count.
  • Both keys start in log mode: writes over a limit are logged until that limit's flag is enabled, and then REST returns 429 Too Many Requests and GraphQL returns an error.
  • Docs: a new rate limit page under doc/rate_limits/api/, a line on the releases API, the settings API, and the GraphQL mutation descriptions. The Changelog entry comes with the flag-removal MR.

Feature Flag: release_description_render_before_save

Feature Flag: rate_limit_release_writes_per_project

Feature Flag: rate_limit_release_writes_per_user

References

Screenshots or screen recordings

The new Releases API rate limits block on Admin > Settings > Network. Before (master) and After (this branch) are attached at the end of this description.

How to set up and validate locally

  1. In a Rails console, set a small limit:
    ApplicationSetting.current.update!(release_writes_limit_per_project: 1)
  2. Wait a minute for the settings cache. With a personal access token (api scope) for a project where you can create releases, create two releases:
    for tag in v-a v-b; do
      curl --include --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
        --data "tag_name=$tag&ref=<default_branch>&description=test" "http://gdk.test:3000/api/v4/projects/<id>/releases"
    done
    Both return 201, and log/api_json.log shows rate_limit_state with limit_release_writes_by_project_log:log for the second request.
  3. Run Feature.enable(:rate_limit_release_writes_per_project), wait for the next minute, and repeat with new tags. The first returns 201, the second 429 with Retry-After: 60.
  4. Run Feature.enable(:release_description_render_before_save) and create or update a release with a description. description_html matches the same write with the flag off, and the request's db_main_txn_max_duration_s in log/api_json.log no longer includes the render.
  5. Clean up: Feature.remove the two flags and reset the setting to 300.

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.

before after

Edited by Leaminn Ma

Merge request reports

Loading
Loading