Forum Discussion

Nigel88_321901's avatar
Nigel88_321901
Icon for Nimbostratus rankNimbostratus
Apr 19, 2018

iRule for rate limit unexpected behaviour

I have a requirement to implement rate limiting for HTTP requests and have acheived this somewhat through reading discussions and receving advice via this community. The iRules I have are not behaving as I'd expect. I've been carrying out some testing on two iRules referenced below. The results are not what I expect to see and I was hoping someone could help me understand what's going on and if it can be fixed.

Basically I am using httperf to send 20 requests per second for 10 seconds. The iRules are set to rate limit at 30 requests per second but are still being triggered. The below screenshot shows Perfmon configured to count GET requests received per second on our server.

The first block of httperf requests is against the VIP with no iRule enabled as you can see we are getting a nice steady flow of 20 requests per second.

The 2nd block of requests is whilst iRule 1 mentioned below is enabled. There seems to be rate limiting going on even though it's set to 30 per second.

The 3rd block of requests is whilst iRule 2 mentioned below is enabled. Again we see the same thing.

Any ideas? The rate limit also performs quite poorly with bursts of traffic, is there anything you can suggest to improve this?

iRule 1:

when RULE_INIT { 
    set static::maxRate 30
    set static::timeout 1 
} 
when HTTP_REQUEST {
    if { (([string tolower [HTTP::host]] equals "demo.company.com") or ([string tolower [HTTP::host]] equals "demo.company.com")) and [string tolower [HTTP::uri]] starts_with "/test/index" } then {  
        if { [set methodCount [table incr -mustexist "Count_[HTTP::method]"]] ne "" } then {
            if { $methodCount > $static::maxRate } then {
                log local0. "[IP::client_addr] exceeded max HTTP requests per second. URL is [HTTP::host][HTTP::uri]"
                HTTP::respond 429 content "Request blockedExceeded requests/sec limit." Retry-After 30
                return
            }   
        } else {
            table set "Count_[HTTP::method]" 1 indef $static::timeout
        }
        log local0. "[IP::client_addr]: methodCount=$methodCount using [HTTP::method]"
    }
}
  • iRule 2 https://devcentral.f5.com/questions/irule-rate-limiting-with-queuing-of-requests-57583comment64016:

    when RULE_INIT {
    
             Tweak the request limit and interval 
            set static::request_limit 30                ;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"
    
                    }
                }
            }
        }