iRule Optimisation Guidelines

I have been using F5’s Load Balancers Applicaiton Delivery Controllers (ADC) for over 7 years now, where I initially implemented them as a replacement for another Load Balancer product at a company.

Soon after installing the F5’s I started tinkering with creating iRules. I had some previous experience with the TCL language, but that’s a tale for another time, so felt relatively comfortable.

Whilst iRules are a powerful tool and have helped me out of many tight spots, usually by allowing me to fairly quickly identify problems (usually not related to the load balancer themselves, but rather weird application behavior), they need to be treated with care and like all things in the network, tested in a non production environment before deploying, to understand how they affect the traffic flow.

Thus the purpose of this post, as while iRules can add some interesting functionality they can also introduce latency, especially if they are not optimised appropriately.

Therefore here are some notes I gathered along the way…

  • Use a Profile first, if possible!
  • It’s better to use chained “if”/”elseif” statements than to use separate “if” statements.
  • It’s better to use “switch” than any form of “if”, whenever possible.
  • It’s better to use “switch” (even with -glob) instead of “matchclass” for comparisons of 100 elements or less.
  • Order your if/elseif’s with most frequent hits at the top. Maybe put a temp hit/log counter in your evaluations to identify their frequency.
  • It’s better to use a command like HTTP::uri than to place the value in a variable.
  • The amount of code in an unused switch statement or an un-called if block doesn’t seem to be a performance consideration.
  • Use your operators wisely “equals” is better than “contains”, “string match/switch-glob” is better than regular expressions. (regex is cool, but a CPU hog!)

Also when thinking of applying iRules to traffic flows you need to cater for both client to server and server to client. Thus when redirecting a HTTP to HTTPS via an iRule such as:

when HTTP_REQUEST {
  # save hostname for use in response
  set fqdn_name [HTTP::host]
}

You need to consider the responses from the server side, as it will not be aware that the client thinks it is communicating to it via HTTPS (SSL) as the client’s HTTPS session is actually terminating on the F5, and therefore the server behind the load balancer will send all redirects back to the client instructing it to respond on HTTP rather than HTTPS.

This can be catered for (as most things can) in an iRule such as:

when HTTP_REQUEST {
  # save hostname for use in response
  set fqdn_name [HTTP::host]
}
when HTTP_RESPONSE {
  if { [HTTP::is_redirect] }{
    if { [HTTP::header Location] starts_with "/" }{
      HTTP::header replace Location "https://$fqdn_name[HTTP::header Location]"
    } else {
      HTTP::header replace Location "[string map {"http://" "https://"} [HTTP::header Location]]"
    }
  }
}

However as per the first point above, a perhaps simpler and possibly more efficient way to handle this is via a profile. The LTM HTTP profile contains the “Rewrite Redirects” option which supports rewriting server-set redirects to the HTTPS protocol with a hostname matching the one requested by the client.

Variables

These can be powerful however should only be used if required, for example if you need to do repetitive evaluations on a value (keep variable names short).

For example rather than this:

when HTTP_REQUEST {
  set host [HTTP::host]
  set uri [HTTP::uri]
  if { $host equals "bob.com" } {
    log "Host = $host; URI = $uri"
    pool http_pool1
  }
}

Do this:

when HTTP_REQUEST {
  if { [HTTP::host] equals "bob.com" } {
    log "Host = [HTTP::host]; URI = [HTTP::uri]"
    pool http_pool1
  }
}

Whilst it may not seem like a big deal to use short yet concise variable names, each connection that comes in (and there may be 1000s) needs to store that variable in memory, thus it adds up…

Timing

The timing iRule command is a great tool to help you get the best performance out of your iRule. CPU cycles are stored on a per event level and you can turn “timing on” for any and all events.

These CPU metrics are stored and can be retrieved by the CLI, or the administrative GUI.

  • CPU Cycles/Request
  • Run Time (ms)/Request
  • Percent CPU Usage/Request
  • Max Concurrent Requests

Just make sure you turn those timing values off when you are done optimizing your iRule as they incur an overhead!

For example, to enable timing for the entire iRule:

when RULE_INIT {
   log local0. "Rule initialized with timing enabled."
}
when HTTP_REQUEST {
   pool my_pool
}

or for a single event:

when RULE_INIT {
   log local0. "Rule initialized -- timing not enabled."
}
when HTTP_REQUEST timing on {
   log local0. "This event only is being timed"
   pool my_pool
}

Persistence

There are many great examples for doing persistence, however (its been a while since I looked so this may have changed) most of them do not seem to consider the health of the pool member they are persisting too, which can cause issues.

Thus my recommendation is when using an iRule for persistency always check the pool status such as:

if { [LB::status pool <pool_name>  eq "up" }

This will stop a connection persisting to a node if the node goes down after the initial connection is established, and the connection is persisting to that node via whatever mechanism (cookie, hash, IP, etc…)

Good Luck with those iRules!