Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guava’s RateLimiter per minutes instead of seconds?

I'm trying to rate-limit the the number of accounts a user can create with my REST API.

I would have liked to use Guava's RateLimiter to only allow an IP to create, let's say, 5 accounts within 10 minutes, but the RateLimiter.create method only takes a double specifying the number of permits "per second".

Is there a way to configure RateLimiter to release permits at a granularity greater than one second?

like image 511
Johny19 Avatar asked Dec 30 '14 20:12

Johny19


2 Answers

From the RateLimiter.create javadoc:

When the incoming request rate exceeds permitsPerSecond the rate limiter will release one permit every (1.0 / permitsPerSecond) seconds.

So you can set permitsPerSecond to less than 1.0 to release a permit less often than once per second.

In your specific case, five accounts in ten minutes simplifies to one account per two minutes, which is one account per 120 seconds. You'd pass 1.0/120 for permitsPerSecond.

In your use case you probably want to accommodate bursty requests for account creations. The RateLimiter specification doesn't seem to define what happens to unused permits, but the default implementation, SmoothRateLimiter, seems to let permits accrue up to some maximum to satisfy bursts. This class is not public, so there's no javadoc documentation, but the SmoothRateLimiter source has a lengthy comment with a detailed discussion of the current behavior.

like image 61
Jeffrey Bosboom Avatar answered Sep 20 '22 14:09

Jeffrey Bosboom


I think I came upon the same problem as in the original question, and based on Louis Wasserman's comment this is what I drew up:

import com.google.common.util.concurrent.RateLimiter;
import java.time.Duration;

public class Titrator {

    private final int numDosesPerPeriod;
    private final RateLimiter rateLimiter;
    private long numDosesAvailable;
    private transient final Object doseLock;

    public Titrator(int numDosesPerPeriod, Duration period) {
        this.numDosesPerPeriod = numDosesPerPeriod;
        double numSeconds = period.getSeconds() + period.getNano() / 1000000000d;
        rateLimiter = RateLimiter.create(1 / numSeconds);
        numDosesAvailable = 0L;
        doseLock = new Object();
    }

    /**
     * Consumes a dose from this titrator, blocking until a dose is available.
     */
    public void consume() {
        synchronized (doseLock) {
            if (numDosesAvailable == 0) { // then refill
                rateLimiter.acquire();
                numDosesAvailable += numDosesPerPeriod;
            }
            numDosesAvailable--;
        }
    }

}

The dose meted out by the Titrator is analogous to a permit from a RateLimiter. This implementation assumes that when you consume your first dose, the clock starts ticking on the dosage period. You can consume your max doses per period as fast as you want, but when you reach your max, you have to wait until the period elapses before you can get another dose.

For a tryConsume() analog to RateLimiter's tryAcquire, you would check that numDosesAvailable is positive.

like image 32
user4851 Avatar answered Sep 19 '22 14:09

user4851