Skip to content

feat: support live reloading of individual rate limits#2469

Merged
cstockton merged 4 commits into
masterfrom
cs/feat-ratelimit-reloads
Apr 9, 2026
Merged

feat: support live reloading of individual rate limits#2469
cstockton merged 4 commits into
masterfrom
cs/feat-ratelimit-reloads

Conversation

@cstockton

Copy link
Copy Markdown
Contributor

Move API limiter setup out of internal/api and into a dedicated internal/api/apilimiter package, then wire it into serve-time config reloads so rate limit changes are picked up without restarting the service.

This change replaces the old LimiterOptions type with an apilimiter.Limiter instance passed through api.WithLimiter(...). API construction now defaults to apilimiter.New(...) when no limiter is injected, and tests are updated to use the new option-based wiring.

The new apilimiter package centralizes:

  • construction of all ratelimit and tollbooth limiters
  • mapping between config/env vars and limiter fields
  • copy/update logic for reusing existing limiter state where possible
  • structured logging for limiter changes during config reload

On config reload in serve():

  • keep track of the previously active limiter set
  • call previousLim.Update(...) against the latest config
  • build the new API with the updated limiter set
  • store the new API, reload apiworker config, and retain the latest limiter for the next reload cycle

This fixes the prior behavior where hot config reload rebuilt the API but kept stale limiter settings, meaning rate-limit changes were not applied until process restart.

Additional ratelimit changes:

  • persist the original parsed conf.Rate value in conf.Rate via val
  • add GetRateValue() for logging/comparison purposes
  • extend ratelimit.Limiter with Config() so limiters can expose their backing configuration
  • add ratelimit.Equal(...) helper to compare limiters, configs, and rate strings consistently
  • store conf.Rate on BurstLimiter and IntervalLimiter and expose Config()
  • add String() methods to identify limiter type in tests/debug output
  • rename IntervalLimiter.limit to events for clarity

Behavioral note:

  • BurstLimiter documentation now matches implementation for non-positive event counts: burst size becomes 0, so no events are allowed

Tests:

  • update API tests to inject limiters through api.WithLimiter
  • update options tests to validate apilimiter.New
  • expand ratelimit tests to cover type identification and equality semantics
  • add dedicated apilimiter tests that verify only the expected fields change when each config/env-backed limiter value is modified

Move API limiter setup out of internal/api and into a dedicated
internal/api/apilimiter package, then wire it into serve-time config
reloads so rate limit changes are picked up without restarting the
service.

This change replaces the old LimiterOptions type with an apilimiter.Limiter
instance passed through api.WithLimiter(...). API construction now defaults
to apilimiter.New(...) when no limiter is injected, and tests are updated
to use the new option-based wiring.

The new apilimiter package centralizes:
- construction of all ratelimit and tollbooth limiters
- mapping between config/env vars and limiter fields
- copy/update logic for reusing existing limiter state where possible
- structured logging for limiter changes during config reload

On config reload in serve():
- keep track of the previously active limiter set
- call previousLim.Update(...) against the latest config
- build the new API with the updated limiter set
- store the new API, reload apiworker config, and retain the latest limiter
  for the next reload cycle

This fixes the prior behavior where hot config reload rebuilt the API but
kept stale limiter settings, meaning rate-limit changes were not applied
until process restart.

Additional ratelimit changes:
- persist the original parsed conf.Rate value in conf.Rate via val
- add GetRateValue() for logging/comparison purposes
- extend ratelimit.Limiter with Config() so limiters can expose their
  backing configuration
- add ratelimit.Equal(...) helper to compare limiters, configs, and rate
  strings consistently
- store conf.Rate on BurstLimiter and IntervalLimiter and expose Config()
- add String() methods to identify limiter type in tests/debug output
- rename IntervalLimiter.limit to events for clarity

Behavioral note:
- BurstLimiter documentation now matches implementation for non-positive
  event counts: burst size becomes 0, so no events are allowed

Tests:
- update API tests to inject limiters through api.WithLimiter
- update options tests to validate apilimiter.New
- expand ratelimit tests to cover type identification and equality semantics
- add dedicated apilimiter tests that verify only the expected fields change
  when each config/env-backed limiter value is modified
@cstockton cstockton requested a review from a team as a code owner April 7, 2026 18:29
@blacksmith-sh

This comment has been minimized.

@cstockton

Copy link
Copy Markdown
Contributor Author

Found 2 test failures on Blacksmith runners:

Failures

Test View Logs
github.com/supabase/auth/internal/conf/TestRateDecode View Logs
github.com/supabase/auth/internal/reloader/TestWatchNotify View Logs
Fix in Cursor

The view logs links end up asking to act on my behalf on github just to see the test failures, not a fan of that.

When I fixed the tests it no longer re-ran tests like it use to. Is this part of the blacksmith changes or just a potential tweak needed to actions?

Comment thread internal/ratelimit/ratelimit_test.go Outdated
Comment thread cmd/serve_cmd.go
Co-authored-by: fadymak <dev@fadymak.com>
@blacksmith-sh

This comment has been minimized.

@cstockton cstockton merged commit d03d796 into master Apr 9, 2026
5 of 6 checks passed
@cstockton cstockton deleted the cs/feat-ratelimit-reloads branch April 9, 2026 16:51
fadymak pushed a commit that referenced this pull request Apr 28, 2026
🤖 I have created a release *beep* *boop*
---


##
[2.189.0](v2.188.1...v2.189.0)
(2026-04-23)


### Features

* add PKCE support for `/resend`
([#2401](#2401))
([2af904a](2af904a))
* improve parallelization in github workflows and Makefile
([#2436](#2436))
([9d0c4b3](9d0c4b3))
* **passkeys:** add CAPTCHA to options endpoint for authentication
([#2416](#2416))
([c7b58be](c7b58be))
* support live reloading of individual rate limits
([#2469](#2469))
([d03d796](d03d796))


### Bug Fixes

* ensure identities are returned in a consistent order across DB engines
([#2465](#2465))
([e49a3e5](e49a3e5))
* ensure SSO providers tests are order-independent
([#2466](#2466))
([983ade6](983ade6))
* exempt PKCE recovery sessions from require-current-password check
([#2502](#2502))
([7f88985](7f88985))
* **indexworker:** skip index creation on OrioleDB
([#2481](#2481))
([dd56ae9](dd56ae9))
* **passkeys:** modify the passkeys request and response shapes
([#2475](#2475))
([2d8f2b6](2d8f2b6))
* prevent reuse of flow state
([#2483](#2483))
([88dcb2d](88dcb2d))
* return JSON response for unmatched routes instead of plain text
([#2457](#2457))
([7337e21](7337e21))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: supabase-releaser[bot] <223506987+supabase-releaser[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants