ratelimit-js icon indicating copy to clipboard operation
ratelimit-js copied to clipboard

Is there a way to combine rate limits?

Open david8z opened this issue 1 year ago • 5 comments

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.

david8z avatar Oct 18 '24 04:10 david8z

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");
};

david8z avatar Oct 18 '24 04:10 david8z

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.

fahreddinozcan avatar Oct 20 '24 19:10 fahreddinozcan

Hey @fahreddinozcan basically the idea is finding an efficient way of combining two limits.

david8z avatar Oct 26 '24 04:10 david8z

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.

AtiqGauri avatar Nov 28 '24 14:11 AtiqGauri

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.

devoffus avatar Mar 06 '25 09:03 devoffus