Skip to content

Add concurrent request rate limiter and optimize middleware stack - #5361

Open
serdarozerr wants to merge 14 commits into
cloudfoundry:mainfrom
sap-contributions:feature/concurrent-ratelimiter
Open

serdarozerr wants to merge 14 commits into
cloudfoundry:mainfrom
sap-contributions:feature/concurrent-ratelimiter

Conversation

@serdarozerr

@serdarozerr serdarozerr commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Concurrent request rate limiter: Introduced ConcurrencyRateLimiter middleware that limits the number of concurrent in-flight requests per user (or IP for unauthenticated requests). Supports separate logging_limit (warn only) and blocking_limit (return 429) for smooth rollout. if both are active, blocking_limit takes precedence and logging_limit is ignored. Disabled by default via config. Backed by Redis (with TTL-based safety cleanup) or in-memory store for single-process deployments. Counter increments use an atomic Lua script to guarantee no phantom counts. So rejected requests never increment the counter. Admins are rate limited the same as regular users (unlike the service broker rate limiter, which exempts them).

Middleware stack optimization: Split SecurityContextSetter into two concerns — token decoding only (configure_token_only) happens early in the stack, and user DB lookup (configure_user) is deferred to a new UserContextSetter middleware placed after rate limiters. This means rate limiting never triggers a DB lookup for blocked requests. RequestMetrics is also moved after rate limiters so that blocked 429 responses are excluded from request metrics, keeping metrics focused on requests that actually reached the application.

Changes

  • middleware/concurrency_rate_limiter.rb — new ConcurrencyRateLimiter middleware
  • middleware/service_broker_rate_limiter.rb — refactored onto the shared base class
  • middleware/base_concurrency_rate_limiter.rb — shared base for the two concurrency limiters
  • middleware/concurrent_request_counter.rb — ConcurrentRequestCounter with RedisStore/InMemoryStore
  • middleware/mixins/admin.rb — extracted admin? helper
  • middleware/user_context_setter.rb — new middleware for deferred user DB lookup
  • middleware/security_context_setter.rb — now calls configure_token_only instead of full configure
  • lib/cloud_controller/security/security_context_configurer.rb — added configure_token_only and configure_user methods
  • lib/cloud_controller/rack_app_builder.rb — wired new middlewares, moved RequestMetrics after rate limiters
  • lib/cloud_controller/config_schemas/api_schema.rb — added concurrency_rate_limiter config schema
  • Retry-After header added on 429 responses from the concurrent rate limiter.

… done.

Introduces ConcurrencyRateLimiter middleware to limit concurrent requests
per user across all endpoints. Supports separate logging and blocking
thresholds for smooth rollout, Redis and in-memory store backends,
thread-safe singleton limiter instance, and RetryAfter header estimation
based on request duration. Disabled by default via config.

Also separates user DB lookup from token decoding into a dedicated
UserContextSetter middleware, and moves RequestMetrics after rate limiters
in the middleware stack.
Comment thread lib/cloud_controller/security/security_context_configurer.rb Outdated
Comment thread lib/cloud_controller/security_context.rb Outdated
Comment thread lib/cloud_controller/security/security_context_configurer.rb
Comment thread spec/unit/middleware/user_context_setter_spec.rb Outdated
Comment thread spec/unit/lib/cloud_controller/security/security_context_configurer_spec.rb Outdated
Comment thread middleware/concurrency_rate_limiter.rb Outdated
Comment thread middleware/concurrency_rate_limiter.rb Outdated
Comment thread config/cloud_controller.yml
Comment thread middleware/concurrency_rate_limiter.rb Outdated
Comment thread lib/cloud_controller/rack_app_builder.rb Outdated
stephanme added a commit that referenced this pull request Sep 15, 2026
- implementation on #5361 was improved and reaches more throughput now
- all measurements run for 300s
- more detailed explanation how to determine max throughput
- some more arguments for replacing nginx by a dedicated CAPI router
- Introduce ConcurrentRequestCounter with InMemoryStore and RedisStore
  backends using atomic Lua scripts for check-and-increment; rejected
  requests do not increment the counter (no phantom count)
- Add ConcurrencyRateLimiter and ServiceBrokerRateLimiter middlewares
  backed by the new counter
- Extract UserId, InternalOrRootApi, TooManyRequests, BasicAuth mixins
- Derive redis_connection_pool_size and redis_counter_ttl_seconds from
  puma max_threads and request_timeout_in_seconds in config
- Add unit tests for all new components
@serdarozerr serdarozerr changed the title Add concurrent request rate limiter and optimize middleware stack WIP: Add concurrent request rate limiter and optimize middleware stack Sep 18, 2026
@serdarozerr serdarozerr changed the title WIP: Add concurrent request rate limiter and optimize middleware stack Add concurrent request rate limiter and optimize middleware stack Sep 23, 2026
Extract a shared BaseConcurrencyRateLimiter that both
ConcurrencyRateLimiter and ServiceBrokerRateLimiter inherit, removing
the duplicated call/increment/decrement logic. Pull the admin? check
into a reusable Admin mixin.

Simplify RackAppBuilder#build by moving each limiter's option hash into
its own helper method, dropping the rubocop length disables.

Drop the dead redis_connection_pool_size/redis_counter_ttl_seconds
kwargs. Each RedisStore now takes only the socket and resolves its pool
size from puma max_threads (and TTL from request_timeout_in_seconds)
itself, applied to the concurrent, expiring, and statsd counters.

Update specs accordingly, including coverage for the set and unset
pool-size paths, and add admin mixin specs.
@philippthun
philippthun force-pushed the feature/concurrent-ratelimiter branch from 842ca76 to bce3d81 Compare October 14, 2026 15:47
Emit the rejecting limiter's class name on every 429 so nginx can log
which rate limiter blocked the request.
@philippthun
philippthun force-pushed the feature/concurrent-ratelimiter branch from eb01c7c to cb4c8c3 Compare October 15, 2026 11:34

This branch has not been deployed

No deployments
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