Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I throttle my site's API users?

The legitimate users of my site occasionally hammer the server with API requests that cause undesirable results. I want to institute a limit of no more than say one API call every 5 seconds or n calls per minute (haven't figured out the exact limit yet). I could obviously log every API call in a DB and do the calculation on every request to see if they're over the limit, but all this extra overhead on EVERY request would be defeating the purpose. What are other less resource-intensive methods I could use to institute a limit? I'm using PHP/Apache/Linux, for what it's worth.

like image 625
scotts Avatar asked Sep 03 '09 19:09

scotts


People also ask

How do you throttle APIs?

One way to implement API throttling in distributed systems is to use sticky sessions. In this method, all requests from a user are always serviced by a particular server. However, this solution is not well-balanced or fault tolerant. The second solution to API throttling in distributed systems are locks.

How do you implement throttling for your website?

Choosing what to throttleThe layer of infrastructure used to implement throttling would typically be a load balancer or reverse-proxy positioned between the user-agents and the origin web servers. At this layer, the next simplest throttling mechanism would be to throttle by connection.

What is API throttling policy?

The API rejects requests that exceed the limit. You can configure multiple limits with window sizes ranging from milliseconds to years. The Throttling policy queues requests that exceed limits for possible processing in a subsequent window.

What is http throttling?

Throttling allows you to set the maximum number of concurrent messages that are processed by a particular endpoint. Increased message load and large message payloads can cause memory usage spikes that can decrease performance. Throttling limits resource consumption so that consistent performance is maintained.


2 Answers

Ok, there's no way to do what I asked without any writes to the server, but I can at least eliminate logging every single request. One way is by using the "leaky bucket" throttling method, where it only keeps track of the last request ($last_api_request) and a ratio of the number of requests/limit for the time frame ($minute_throttle). The leaky bucket never resets its counter (unlike the Twitter API's throttle which resets every hour), but if the bucket becomes full (user reached the limit), they must wait n seconds for the bucket to empty a little before they can make another request. In other words it's like a rolling limit: if there are previous requests within the time frame, they are slowly leaking out of the bucket; it only restricts you if you fill the bucket.

This code snippet will calculate a new $minute_throttle value on every request. I specified the minute in $minute_throttle because you can add throttles for any time period, such as hourly, daily, etc... although more than one will quickly start to make it confusing for the users.

$minute = 60; $minute_limit = 100; # users are limited to 100 requests/minute $last_api_request = $this->get_last_api_request(); # get from the DB; in epoch seconds $last_api_diff = time() - $last_api_request; # in seconds $minute_throttle = $this->get_throttle_minute(); # get from the DB if ( is_null( $minute_limit ) ) {     $new_minute_throttle = 0; } else {     $new_minute_throttle = $minute_throttle - $last_api_diff;     $new_minute_throttle = $new_minute_throttle < 0 ? 0 : $new_minute_throttle;     $new_minute_throttle += $minute / $minute_limit;     $minute_hits_remaining = floor( ( $minute - $new_minute_throttle ) * $minute_limit / $minute  );     # can output this value with the request if desired:     $minute_hits_remaining = $minute_hits_remaining >= 0 ? $minute_hits_remaining : 0; }  if ( $new_minute_throttle > $minute ) {     $wait = ceil( $new_minute_throttle - $minute );     usleep( 250000 );     throw new My_Exception ( 'The one-minute API limit of ' . $minute_limit          . ' requests has been exceeded. Please wait ' . $wait . ' seconds before attempting again.' ); } # Save the values back to the database. $this->save_last_api_request( time() ); $this->save_throttle_minute( $new_minute_throttle ); 
like image 122
scotts Avatar answered Sep 23 '22 23:09

scotts


You can control the rate with the token bucket algorithm, which is comparable to the leaky bucket algorithm. Note that you will have to share the state of the bucket (i.e. the amount of tokens) over processes (or whatever scope you want to control). So you might want to think about locking to avoid race conditions.

The good news: I did all of that for you: bandwidth-throttle/token-bucket

use bandwidthThrottle\tokenBucket\Rate; use bandwidthThrottle\tokenBucket\TokenBucket; use bandwidthThrottle\tokenBucket\storage\FileStorage;  $storage = new FileStorage(__DIR__ . "/api.bucket"); $rate    = new Rate(10, Rate::SECOND); $bucket  = new TokenBucket(10, $rate, $storage); $bucket->bootstrap(10);  if (!$bucket->consume(1, $seconds)) {     http_response_code(429);     header(sprintf("Retry-After: %d", floor($seconds)));     exit(); } 
like image 20
Markus Malkusch Avatar answered Sep 22 '22 23:09

Markus Malkusch