upstash / ratelimit-js

Rate limiting library for serverless runtimes
https://ratelimit-with-upstash.vercel.app/
MIT License
1.71k stars 34 forks source link

Is there a way to combine rate limits? #126

Open david8z opened 4 hours ago

david8z commented 4 hours ago

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 commented 3 hours ago

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