When many requests happen simultaneously, they all try to modify the same rate limit values in the database. Because Convex provides strong transactions, requests will never overwrite each other incorrectly, so your rate limiter will never allow more requests than configured.However, high contention for these values causes optimistic concurrency control (OCC) conflicts. Convex automatically retries these conflicts with backoff, but it’s better to avoid them in the first place.
Sharding breaks up the total capacity into individual buckets, or “shards”. When consuming capacity, the rate limiter checks a random shard. While you might occasionally get rate limited when capacity exists elsewhere, you’ll never violate the rate limit’s upper bound.
const rateLimiter = new RateLimiter(components.rateLimiter, { // Use sharding to increase throughput without compromising on correctness llmTokens: { kind: "token bucket", rate: 40000, period: MINUTE, shards: 10 }, llmRequests: { kind: "fixed window", rate: 1000, period: MINUTE, shards: 10 },});
The implementation uses an advanced load balancing technique: it checks two random shards and uses the one with more capacity. This keeps shards relatively balanced.The rate limiter will also combine the capacity of both shards if neither has enough on their own, based on the power of two choices technique.
If you want a rate like { rate: 100, period: SECOND } and you’re flexible on the overall period, you can shard it by increasing both the rate and period proportionally:
// Original: too fast, needs sharding{ rate: 100, period: SECOND }// Better: same rate, more capacity per shard{ shards: 50, rate: 250, period: 2.5 * SECOND }// Best: much better capacity per shard{ shards: 50, rate: 1000, period: 10 * SECOND }
This gives each shard more capacity while maintaining the same overall rate limit.
Occasional false negatives: With sharding, you might occasionally be rate limited even when capacity exists in other shards. This is the trade-off for avoiding OCC conflicts.