API traffic is dominated by a few automation-heavy customers whose bursts affect everyone. How do you introduce per-tenant quotas and priority without breaking existing integrations?
Show the full answer Hide the answer
Measure before you limit
Instrument per-tenant usage by endpoint, by cost, and by time of day first, and run it for long enough to see weekly and monthly cycles. The common surprise is that the heavy tenants are not the ones anyone suspected, and that a single scheduled batch job at 2am accounts for a disproportionate share.
Cost-weighted accounting matters more than request counts. A tenant making a thousand cheap lookups is not the same as one making ten full-table reports, and a request-count quota misallocates capacity between them.
Design the limits from the data
- Quotas set from observed usage, so that the initial limits do not affect any existing legitimate tenant. The first version of a quota system should reject nothing.
- Cost units rather than request counts — assign each endpoint a weight reflecting its actual resource consumption.
- Separate limits for interactive and batch traffic, ideally with a documented way for a tenant to declare which they are sending. A batch job that identifies itself can be given a large quota at low priority, which is better for everyone than pretending it is interactive.
- Burst allowance via token bucket, because legitimate integrations are bursty and a smooth-rate limiter penalises normal behaviour.
Weighted fair queueing over hard limits
Hard per-tenant limits waste capacity: a tenant is rejected while the system is idle because their fixed quota is exhausted. Weighted fair queueing allocates shares of the available capacity, so a tenant can exceed their nominal share when nobody else needs it and is constrained only under contention.
This is strictly better for both parties — higher utilisation for the provider, more headroom for the tenant — and is the model to aim for. Hard limits remain useful as an absolute backstop against a runaway client.
Rolling it out without breaking anyone
- Observe-only mode. The limiter runs and logs what it would have rejected. Run for weeks.
- Analyse the would-be rejections, per tenant. Every one of them is a conversation you would have had with a customer, and this is where the limits get corrected.
- Notify affected tenants individually, with their own data and a specific date.
- Enforce with soft failures first — a warning header, or a deliberate small delay, before outright rejection.
- Enforce, with a documented process for requesting an increase that is fast and reasonable.
- Publish the limits, which converts an invisible risk into a design constraint integrators can build against.
The structural options beyond limiting
- A separate endpoint or pool for bulk operations, with its own capacity and its own SLO. Giving batch traffic a proper home is better than constraining it in a shared one.
- Asynchronous and export APIs for large reads — submit a job, collect the result — which removes the expensive work from the synchronous path entirely and is usually what the heavy tenants actually wanted.
- Webhooks instead of polling, which addresses a large fraction of automation-driven load at its root: much polling exists only because there is no push alternative.
- Cell isolation, so tenants cannot affect one another regardless of quotas.
The commercial dimension
Quotas are a product decision as much as a technical one. Whether a heavy user is a problem to be limited or a customer to be upsold is a business question, and the architecture should support differentiated tiers so that the answer can be "pay for more" rather than only "use less."