nikolaposa / rate-limit

🚔 General purpose rate limiter implementation.
MIT License
271 stars 47 forks source link

Stand into queue and wait for your turn? #31

Closed mityukov closed 4 years ago

mityukov commented 4 years ago

Hi, the API I consume has very tight limit: no more than 1 request per second.

At the moment I use very naïve system of "rate-limiting":

protected function apiCall(…$params)
{
    while ($this->getRequestsNum() >= 1) {
        sleep(1);
    }

    $this->increaseRequestsNum();

    // .. do the request
}

function getRequestsNum()
{
        $ts = time();
        $lastCallTs = cache($this->accessCacheKey()); // Redis underneath

        return $lastCallTs < $ts ? 0 : 1;
}

function increaseRequestsNum()
{
    Cache::forever($this->accessCacheKey(), time());
}

The problem is: it doesn't queue… Here'a an example:

10:00:00.0 : first php process uses API 10:00:00.5 : second request comes and tries to use the api => It get's sleep(1)d 10:00:01.0 : third request comes and it's Ok to use API, because whole second has passed since the first request 10:00:01.5 : second request "awakes" and tries to access API again => sorry, busy, go to sleep

I've done some logging and found out that under certain loads some requests may have tens (if not hundreds) of attempts before it's not busy, while the requests, that came later — get their data.

So, there's obviously a need not only for a way to tell if the limit is over, but also some mechanism to serve all waiting processes in proper order.

Do you have any insights?

nikolaposa commented 4 years ago

As for your rate limiting implementation, part with while/sleep is completely off. Rate limiting is supposed to be a security mechanism on the server, your solution basically clogs it. The client needs to adjust to the limitation imposed by the server, not the other way around.

And what does this issue has to do with this library?

mityukov commented 4 years ago

@nikolaposa this limiting is used for consuming external API in my code. If I send more requests than allowed, they disable API and that causes troubles.

You have "General purpose rate limiter" here, so, it should work in both directions. Just wanted to ask if you had any experience in "what's beyond you've realised that limit has been reached?". So, one option is to throw an exception/exit. But what if you need to do that action anyway, and you realise that you may have to wait for it, in order not to break some rate limitations. That's my case anyway.

mitmelon commented 4 years ago

@mityukov mityukov in contribution... I think what you need to do is that, once the limit is reached is to either send a request to your db controller to either deactivate the user or pause...

If(limit is reached){
//Log info to database
//Block User or Pause
}

Its advised you use the silent namespace for this so you can customise your events or actions once limit is reached...

nikolaposa commented 4 years ago

You have "General purpose rate limiter" here, so, it should work in both directions.

@mityukov I didn't imagine it to work in both directions. It is "general purpose" because you can use it to rate limit not only APIs, but access to any application/website endpoints or pages, or any other resource for which you need to control the rate of requests. The point is that this library was designed to be used on the side of a resource that provides some service, and not on the side of those who consume it.

mityukov commented 4 years ago

@mitmelon it is my code that consumes rate-limited API and my task it not to abuse it, or else that API will be blocked for my site.

I didn't imagine it to work in both directions

Ok. Sorry for bothering then. Maybe, you could suggest the proper term for my case? I'm not native in English, so, "rate limiting" was the best I could thunk out =)

nikolaposa commented 4 years ago

Well I'm not a native English speaker neither, but I think "rate limit retry" might be the term you are looking for. :) I quickly found a couple of articles: