Skip to content

feat: make gateway rate limiter store pluggable#356

Closed
Agbasimere wants to merge 3 commits into
CalloraOrg:mainfrom
Agbasimere:feature/shared-rate-limiter-store-v2
Closed

feat: make gateway rate limiter store pluggable#356
Agbasimere wants to merge 3 commits into
CalloraOrg:mainfrom
Agbasimere:feature/shared-rate-limiter-store-v2

Conversation

@Agbasimere
Copy link
Copy Markdown
Contributor

Closes: #305

Summary

  • replace the gateway rate limiter with a pluggable store-backed implementation
  • keep InMemoryRateLimiter as the default fallback for local/dev use
  • add a PostgreSQL-backed shared token bucket store for multi-instance deployments
  • wire limiter selection through config and startup
  • update gateway/proxy routes to await async limiter checks
  • document the new env vars and shared-store behavior
  • add targeted tests for refill, exhaustion, malformed persisted data, and concurrency behavior

What Changed

Rate limiter

  • extracted a RateLimiterStore abstraction in src/services/rateLimiter.ts
  • preserved existing token-bucket semantics and RateLimitResult shape
  • kept InMemoryRateLimiter as the default implementation
  • added PostgresRateLimiterStore with lazy table creation and transactional bucket updates
  • validated persistent table names defensively to avoid unsafe SQL identifiers

Wiring

  • added config/env support for:
    • RATE_LIMIT_MAX_REQUESTS
    • RATE_LIMIT_WINDOW_MS
    • RATE_LIMIT_STORE
    • RATE_LIMIT_PG_TABLE
  • wired the configured limiter in src/index.ts
  • updated src/routes/gatewayRoutes.ts and src/routes/proxyRoutes.ts to await limiter checks

Docs

  • updated .env.example
  • documented shared gateway rate limiting and the new env vars in README.md

Behavior

  • token bucket refill behavior is unchanged
  • retryAfterMs behavior is unchanged
  • when RATE_LIMIT_STORE=postgres, limits are shared across instances
  • when persistence is not configured, the app falls back to in-memory buckets

Testing

  • npm run typecheck
  • npx jest --runInBand --forceExit src/services/rateLimiter.test.ts
  • npx jest --runInBand --forceExit src/config/index.test.ts src/config/env.test.ts src/routes/gatewayRoutes.test.ts
  • npx jest --runInBand --forceExit --coverage --collectCoverageFrom=src/services/rateLimiter.ts src/services/rateLimiter.test.ts

Test Notes

  • src/services/rateLimiter.ts coverage:
    • Statements: 100%
    • Branches: 95.23%
    • Functions: 100%
    • Lines: 100%
  • npm test -- src/services/rateLimiter.test.ts hit a local Jest worker spawn EPERM issue in this environment, so verification used the equivalent --runInBand form

Risk / Review Notes

  • limiter interface is now async, so all call sites must await check(...)
  • PostgreSQL-backed buckets rely on the configured shared DB for cross-instance coordination
  • the shared store creates its backing table lazily on first use

@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented May 27, 2026

@Agbasimere Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Agbasimere Agbasimere closed this May 27, 2026
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.

Replace in-memory InMemoryRateLimiter token buckets with a shared persistent store for multi-instance deployments

1 participant