Skip to content

Streaming Concurrency Limit

URI: https://docs.ankatech.co/errors/streaming-concurrency-limit
HTTP Status: 429 Too Many Requests

When you see this

Too many true-streaming requests were already in flight when this one arrived. Two bounds are enforced independently: a global bound protecting the thread pools of the instance that received the request, and a per-tenant bound, held across every instance of the deployment, so one tenant's share of the deployment is bounded. The service is healthy and the request is well formed — what is exhausted is the allowance, which is why this is a 429 and not a 503. The response carries Retry-After: 5; an admitted request runs and a refused one is answered immediately, because queueing would hold the very thread the bound exists to protect.

Common Causes

  • More concurrent streaming uploads than the global bound admits on the instance that received the request.
  • One tenant running more concurrent streaming operations than the per-tenant bound admits. The bound counts the tenant's streams on every instance together, so a tenant that spreads its streams across instances still reaches it at the same number.
  • Long-lived streaming requests holding their slot: the permit is held across the whole asynchronous cycle, not released when the request thread unwinds.

The two bounds are configurable, and the detail states both values as the answering instance is configured:

  • ankasecure.streaming.max-concurrent-requests (environment variable ANKASECURE_STREAMING_MAX_CONCURRENT_REQUESTS, default 32) — the global bound, per instance.
  • ankasecure.streaming.max-concurrent-requests-per-tenant (environment variable ANKASECURE_STREAMING_MAX_CONCURRENT_REQUESTS_PER_TENANT, default 8) — the per-tenant bound, across the deployment.

The per-tenant bound is kept in Redis as one lease per in-flight stream, which expires on its own if the instance holding it stops. When Redis cannot be reached the request is not admitted on a guess: it is refused before any payload byte is read with 503 and the problem type dependency-not-ready, carrying its own Retry-After, not with this 429.

Response Example

{
  "type": "https://docs.ankatech.co/errors/streaming-concurrency-limit",
  "title": "Too Many Concurrent Streaming Requests",
  "status": 429,
  "detail": "Too many concurrent streaming requests. At most 32 may be in flight on this instance and at most 8 for any one tenant across the deployment. Retry shortly.",
  "instance": "/api/v3/migration/stream/analyze-pkcs7",
  "correlationId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "timestamp": 1730000000
}

Content-Type: application/problem+json — the response follows RFC 9457 Problem Details.

How to Resolve

  1. Retry after the delay the Retry-After header names. Occupancy frees as in-flight streams complete.
  2. Reduce client-side concurrency so it stays under the per-tenant bound the detail states.
  3. If the workload legitimately needs more capacity, raise ankasecure.streaming.max-concurrent-requests, ankasecure.streaming.max-concurrent-requests-per-tenant, or both. The two bounds count different things and neither is checked against the other: the global bound counts the streams of one instance, the per-tenant bound a tenant's streams on every instance together. A per-tenant bound above the global one is accepted; with one instance the global bound refuses first, so the part above it takes effect only when more than one instance serves streams. A value below 1 for either bound refuses startup.

For full schema definitions, examples, and interactive testing, see the Developer Hub Reference.