Rate limit the metric definitions export per IP

What does this MR do and why?

GET /api/v4/usage_data/metric_definitions is public by design: it's documented as unauthenticated, and its spec asserts 200 without a token. Each response is about 3.8 MB of uncompressed YAML. On GitLab.com, nearly every request lands on a Puma worker that hasn't built the YAML yet, which costs about 0.85s of CPU. Nothing limits how often a client can call it.

This MR adds an ApplicationRateLimiter limit of 30 requests per minute per IP address (metric_definitions_download). Authenticated requests are counted per IP too, because they cost the same.

The rule is a staged_rule. Until the rate_limit_metric_definitions feature flag is enabled, requests over the limit are logged (rate_limit_state ends in limit_metric_definition_downloads_by_ip_log:log) but not blocked. When the flag is on, they get 429 with Retry-After. The limit is checked before the YAML is built, so blocked requests skip that cost.

  • Feature flag: gitlab_com_derisk, with an instance actor, as for pages_deployments_api_unauthenticated. Rollout issue: #639898
  • Limit: 30 is the top of the 10–30 range discussed in the issue, because GitLab.com CI runners share egress IPs. Log mode will show whether real callers come close.
  • Docs: a 429 entry in both OpenAPI files, a row in the non-configurable limits page, and a pointer from the API page.

This is the per-IP limit from the issue's suggested path. Edge caching and compression for YAML are separate follow-ups with Infrastructure.

References

Screenshots or screen recordings

Not applicable: no UI changes.

How to set up and validate locally

  1. With the flag disabled (the default), request the endpoint 31 times. Every request returns 200:

    for i in $(seq 31); do curl --silent --output /dev/null --write-out '%{http_code}\n' "http://gdk.test:3000/api/v4/usage_data/metric_definitions"; done

    In log/api_json.log, the 31st request has a rate_limit_state entry ending in limit_metric_definition_downloads_by_ip_log:log.

  2. In the Rails console, enable the flag:

    Feature.enable(:rate_limit_metric_definitions)
  3. Wait a minute for the window to reset, then repeat the loop. The 31st request returns 429 with a Retry-After header.

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.

🤖 Generated with Claude Code

Edited by Patrick Taylor

Merge request reports

Loading
Loading