Is there a way to combine rate limits?
What is the optimal way to combine two rate limits together?
We could define:
export const ratelimit = {
a: new Ratelimit({
redis,
limiter: Ratelimit.fixedWindow(1000, "60 s"),
}),
b: new Ratelimit({
redis,
limiter: Ratelimit.fixedWindow(3, "10 s"),
})
]
And then await both:
await Promise.all(
ratelimit.a.blockUntilReady("a"),
ratelimit.b.blockUntilReady("b")
)
But this doesn't assures that once resolved none are blocked as one could resolve much faster than the other one.
Ended up implementing this but unsure if there is a better way:
export const rateLimit = async (domain: string, maxRetries = 3) => {
for (let i = 0; i < maxRetries; i++) {
try {
const { success, pending, reset } =
await ratelimit.handinger.blockUntilReady("api", 180_000);
const msToReset = Math.max(0, reset - Date.now());
const { success: successDomain, pending: pendingDomain } =
await ratelimit.handingerDomain.blockUntilReady(domain, msToReset);
waitUntil(Promise.all([pending, pendingDomain]));
if (success && successDomain) {
return;
}
throw new Error("Handinger Ratelimit exceeded");
} catch (error) {
if (i === maxRetries - 1) throw error;
console.warn(
`Rate limit retry ${i + 1}/${maxRetries} failed. Retrying...`,
);
await new Promise((resolve) => setTimeout(resolve, 10000));
}
}
throw new Error("Handinger Ratelimit exceeded");
};
Hey @david8z, what is it that you're trying to achieve here? If you could provide more details about the logic/purpose, I could come up with something.
Hey @fahreddinozcan basically the idea is finding an efficient way of combining two limits.
It would be beneficial to implement multiple rate limits. There are various reasons why one might require multiple rate limits, such as: Scenario where we provide a rate limit for the current month and another rate limit for a per-minute or per-day rate limit for abuse protection. A function that efficiently combines multiple rate limit validation would be helpful.
For combining two independent fixed-window rate limits (1000 requests/60s and 3 requests/10s), I think the simplest and most efficient method is to use Promise.all with blockUntilReady, assuming it reserves the slot upon resolution.
Here's example:
const [resultA, resultB] = await Promise.all([
ratelimit.a.blockUntilReady("a"),
ratelimit.b.blockUntilReady("b")
]);
if (resultA.success && resultB.success) {
// Proceed with the request
} else {
throw new Error("Rate limit exceeded");
}
The optimal way to combine these two rate limits is to use Promise.all to await both blockUntilReady methods in parallel and proceed only if both succeed. This balances efficiency and correctness, leveraging the rate limiter’s built-in reservation mechanism to ensure both limits (1000 requests/60s and 3 requests/10s) are respected for each request.