Forum Discussion

Nigel88_321901's avatar
Nigel88_321901
Icon for Nimbostratus rankNimbostratus
Jan 16, 2018

iRule rate limiting with queuing of requests

We have a requirement to perform rate limiting on our F5 for a particular application. We need to limit HTTP requests to 14 per second, if this is exceeded then we need to then start queuing the HTTP requests. The queue size needs to be lets say 250 requests in size and requests fed back in at 14 per second. If the queued 250 requests are exceeded a 429 HTTP response is sent.

So far using various articles/ forum discussions I've managed to get the rate limiting working (see below) but can't figure out if we are able to queue requests, it doesn't look like we can. Any ideas please?

The version we are running is BIG-IP 11.6.2 Build 1.0.507 Hotfix HF1.

when RULE_INIT {
set static::maxRate 14
set static::timeout 1
}
when HTTP_REQUEST {
if { [HTTP::method] eq "GET" } {
set getCount [table key -count -subtable [IP::client_addr]]
log local0. "getCount=$getCount"
if { $getCount < $static::maxRate } {
    incr getCount 1
    table set -subtable [IP::client_addr] $getCount "ignore" $static::timeout $static::windowSecs
} else {
    log local0. "This user $user has exceeded the number of requests allowed."
    HTTP::respond 429 content "Request blockedExceeded requests/sec limit."

    return
}
}
}

1 Reply

  • Hi Nigel88,

     

    your requirement was somewhat interesting for me - so I decided to give it a try... 😉

     

    The iRule below is my interpretation of your requirement. The iRule is implementing a Sliding-Window based request limiter with configurable max-rate and interval settings. Once the configured request/sec value has been reached, a second Sliding-Window limiter is executed to maintain the required request queue. If a free slot in the request queue is available, the ongoing HTTP request will be paused for a configurable retry interval. After the retry interval has been elapsed the current request/sec limiter value will be evaluated again and depending on the outcome the ongoing HTTP request will be released from the queue or paused for another retry interval (till the queue timeout has been reached). If the queue slots are exhausted or if the queue timeout has been reached a HTTP-Error Code 429 will be send to the client.

     

    Let me know if this fits your requirement...

     

    Note: The implemented queue is not FIFO (first in first out) based. So the queued requests may be released/droped in an unsorted manner. But it should be sufficient to catch certain bursty traffic spikes...

     

    when RULE_INIT {
    
         Tweak the request limit and interval 
        set static::request_limit 14                ;count
        set static::request_interval 1              ;sec
    
         Tweak the queue size, timeout and retry interval
        set static::queue_size 250                  ;count
        set static::queue_timeout 5                 ;sec
        set static::queue_retry_interval 1000       ;msec
    }
    
    when HTTP_REQUEST {
    
         Enforce limits only on GET requests...
    
        if { [HTTP::method] eq "GET" } then {
    
             Calculate a unique request ID to track the request...
            set request_id "[TMM::cmp_unit][clock clicks]"
    
             Compute table labels for request and queued limiter...
            set request_label "R_[HTTP::host]:[IP::client_addr]"
            set queue_label "Q_[HTTP::host]"
    
             Checking if request limit has been reached...
    
            if { [set request_rate [table keys -count -subtable $request_label]] < $static::request_limit } then {
    
                 Max request rate is not exceeded. Insert a new entry to request limiter table...
                table set -subtable $request_label $request_id "1" indef $static::request_interval 
                log local0.debug "Debug: $request_id : Allow: Request rate for $request_label is $request_rate / $static::request_interval seconds"
                 Allowing the request to pass...
    
            } else {
    
                 Max request rate is exceeded. Holding the request to check queue slots...
                log local0.debug "Debug: $request_id : Hold: Request rate for $request_label is $request_rate / $static::request_interval seconds"
    
                 Checking if queue has a free slot....
                if { [set queue_size [table keys -count -subtable $queue_label]] < $static::queue_size } then {
    
                     Queue is not exceeded. Insert a new entry to queue limiter table...
                    table set -subtable $queue_label $request_id "1" indef $static::queue_timeout 
                    log local0.debug "Debug: $request_id : Queued: Queue counter for $queue_label is $queue_size"
    
                     Initialize queue timer...
                    set queue_time 0
    
                     Enforcing the queue loop timeout timer...
                    while { $static::queue_timeout * 1000 > $queue_time } {
    
                         Pausing the ongoing HTTP request for queue retry interval
                        after $static::queue_retry_interval
    
                         Checking if request limit has been lowered...
                        if { [set request_rate [table keys -count -subtable $request_label]] < $static::request_limit } then {
    
                             Max request rate has been lowered. Insert a new entry to our request rate limiter...
                            table set -subtable $request_label $request_id "1" indef $static::request_interval 
                            log local0.debug "Debug: $request_id : Queue Release: Requests rate for $request_label is now $request_rate / $static::request_interval seconds"
    
                             Removing this request from request queue
                            table delete -subtable $queue_label $request_id
    
                             Exiting this iRule and allowing the request to pass...
                            return
    
                        } else {
    
                             Max request rate is still exceeded. Queueing the request another cycle...
    
                            log local0.debug "Debug: $request_id : Queue Retry: Requests rate for $request_label is now $request_rate / $static::request_interval seconds"
                            incr queue_time $static::queue_retry_interval
    
                        }
                    }
    
                     Queued request has been timed out. Dropping the request...
                    log local0.debug "Debug: $request_id : Queue Fail: Requests rate for $request_label is now $request_rate / $static::request_interval seconds"
                    HTTP::respond 429 content "Request Denied: Exceeded requests/sec limits"
    
                } else {
    
                     Maximum queue size has been exceeded. Dropping the request...
                    log local0.debug "Debug: $request_id : Queue Exceeded: Queue counter for $queue_label is $queue_size"
                    HTTP::respond 429 content "Request Denied: Exceeded requests/sec limits"
    
                }
            }
        }
    }
    

    Cheers, Kai