Skip to content

Fix rate limiter memory leak and update defaults#9

Merged
ConsoleTVs merged 1 commit into
mainfrom
fix/rate-limit-memory-eviction
Apr 12, 2026
Merged

Fix rate limiter memory leak and update defaults#9
ConsoleTVs merged 1 commit into
mainfrom
fix/rate-limit-memory-eviction

Conversation

@ConsoleTVs
Copy link
Copy Markdown
Member

Summary

  • Fix unbounded memory growth in the rate limit registry by adding TTL-based eviction with a background cleanup goroutine
  • Update default rate limits from 10 req/s (burst 20) to 15 req/s (burst 30)

Problem

The rateLimitRegistry stored a map[string]*rate.Limiter that grew unbounded — every unique IP that hit the server got a permanent entry with no eviction. On a public-facing server seeing 100K+ unique IPs per day, this is a slow memory leak (~20MB/day) that only resets on process restart.

Solution

TTL Eviction

  • Each registry entry now tracks a lastSeen timestamp, updated on every access
  • A background goroutine sweeps the registry at a configurable interval (default: every 1 minute)
  • Entries idle longer than MaxIdleTime (default: 5 minutes) are deleted
  • registry.close() stops the goroutine cleanly
  • An evicted IP that returns later gets a fresh limiter with a full token bucket — functionally equivalent to the old behavior since an idle limiter's tokens would have been full anyway

Updated Defaults

  • 10 req/s → 15 req/s (better handles SPA page loads with 15-20 parallel requests)
  • Burst 20 → burst 30 (maintains 2:1 burst-to-rate ratio)
  • These values align with Echo's documented examples and sit in the reasonable middle of the ecosystem

New Options

  • CleanupInterval (default 1m) — how often the cleanup goroutine runs
  • MaxIdleTime (default 5m) — how long an entry can be idle before eviction

Testing

  • 9 existing black-box tests updated and passing
  • 12 new internal tests covering: entry creation, reuse, lastSeen updates, eviction of idle entries, preservation of active entries, goroutine stop, post-eviction behavior, and withDefaults for new options
  • framework/middleware remains at 100% coverage
  • All modules pass: go test ./contract/... ./router/... ./problem/... ./framework/...

…e defaults

The rate limit registry previously grew unbounded — every unique IP
that hit the server got a permanent map entry with no eviction.
On public-facing services this is an unbounded memory growth vector.

Changes:
- Add rateLimitEntry with lastSeen timestamp per key
- Add background cleanup goroutine that sweeps stale entries
- Add CleanupInterval (default 1m) and MaxIdleTime (default 5m) options
- Update defaults from 10 req/s burst 20 to 15 req/s burst 30
- Add registry.close() to stop the cleanup goroutine
- Add registry.size() for testability
- Add comprehensive internal tests for eviction behavior
- Maintain 100% middleware coverage
@ConsoleTVs ConsoleTVs marked this pull request as ready for review April 12, 2026 10:33
@ConsoleTVs ConsoleTVs merged commit 9be9e44 into main Apr 12, 2026
1 check passed
@ConsoleTVs ConsoleTVs deleted the fix/rate-limit-memory-eviction branch April 12, 2026 10:33
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.

1 participant