Forum Discussion

edechaux's avatar
edechaux
Icon for Nimbostratus rankNimbostratus
Sep 26, 2017

Auto-failback irule, need help on optimisation

Hello experts,

 

I have implemented an auto-failback irule in order to bring a kind of high availability on a VMs pair that offers an NFS connector above Scality's SOFS. Because of cache coherency problem, both connectors must not be used at the same time for writing.

 

The irule makes sure any TCP connection is always directed toward the primary node. If it is not available, the connection is directed toward the backup node. When the main node is back online, open TCP connections are closed to force the client to open a new one.

 

The huge drawback is really high CPU usage (40% on one 10250v core for a 400 Mbps traffic) as it is fired upon each received TCP segment. How could I make it better ?

 

Here is the code, LTM version is 12.1 :

 

  Nom         : irule-failback-tcp
  Date        : 2017/09/26
  Version     : 1.0
  Description : Mets en place une règle d'auto-failback sur un pool.
                Si le membre principal est "up" les données lui seront
                toujours envoyées, même s'il faut pour cela forcer la fin
                d'une session TCP déjà établie.
                Dans le cas contraire, la gestion est laissée au bigip.
                N'a été testé que dans un pool de deux membres avec deux
                "Priority Group".
                En cas d'auto-failback, le client doit savoir gérer
                correctement (silencieusement) un TCP reset et ré-établir tout
                seul une connection.
 Utilisation  : Renseigner les trois variables s_mainMemberIP, s_backupMemberIP
                et i_memberPort.
                Si i_debug vaut 1 des messages de logs sont envoyés vers le
                logger0, sévérité info. Par défault il sont alors stockés dans
                le fichier /var/log/ltm.
 Attention    : Le debug est très verbeux, 5 lignes par segment TCP.
                L'irule est exécutée à chaque segment reçu, l'utilisation CPU
                est donc très importante, en moyenne 40% d'un coeur sur un
                10250v pour un traffic de 400 Mbps.


  Lors de la connection d'un client, initialisation de quelques variables et
+ activation de la collecte du payload.
when CLIENT_ACCEPTED {

      Variables à adapter en fonction du pool conserné.
    set s_mainMemberIP   "x.y.z.1"
    set s_backupMemberIP "x.y.z.2"
    set i_memberPort     2049

      Active des messages de debug si vaut 1
    set i_debug          0

      Récupération du payload et génération d'un évènement CLIENT_DATA.
    TCP::collect

}

  Une fois tout le payload du client collecté
when CLIENT_DATA {

      Récupération du nom du pool.
    set s_poolName       [LB::server pool]

      Récupération de l'IP du membre sélectionné.
    set s_targetMemberIP [LB::server addr]

     Récupération de l'état du membre principal.
    set s_mainStatus     [LB::status pool $s_poolName member \
                             $s_mainMemberIP   $i_memberPort]

    if { $i_debug == 1 } {
        log local0.info "1-Pool Name              : $s_poolName"
        log local0.info "2-Member port            : $i_memberPort"
        log local0.info "3-Target member          : $s_targetMemberIP"
        log local0.info "4-Main member status     : $s_mainStatus"
    }

      Si le membre principal est up...
    if { $s_mainStatus eq "up" } {

          ...et si le membre sélectionné n'est pas le principal, la session TCP
        + est terminée pour lui permettre de basculer sur le membre principal.
        if { $s_targetMemberIP != "" && $s_targetMemberIP != $s_mainMemberIP } {

            log local0.info "5-Auto-failback          : Active"
            reject

        } else {
          Si aucun membre n'a été sélectionné ou si c'est le principal il n'y
        + a rien à faire.

            if { $i_debug == 1 } {
                log local0.info "5-Auto-failback          : Non necessaire"
            }

        }

    } else { 
      Dans tous les autres cas (down, force offline, disabled) on laisse le
    + big-ip gérer tout seul.

        if { $i_debug == 1 } {
            log local0.info "5-Auto-failback          : Inactive"
        }

    }

      Le payload collecté est transféré au serveur.
    set i_bytesReleased  [TCP::release]

    if { $i_debug == 1 } {
        log local0.info "6-Bytes released         : $i_bytesReleased"
    }

      Réactivation de la collecte.
    TCP::collect

}

Many thanks.

 

1 Reply

  • I see the issue that you have - I tried to think of a better approach, but I could not find one that meets all the requirements.

    For your irule, the first suggestion is to separate out your constants into static variables, and set them in the rule INIT event. You are wasting time and memory by creating those per connection.

    when RULE_INIT {
          Variables à adapter en fonction du pool conserné.
        set static::s_mainMemberIP   "x.y.z.1"
        set static::s_backupMemberIP "x.y.z.2"
        set static::i_memberPort     2049
    
          Active des messages de debug si vaut 1
        set static::i_debug          0
    }
    

    Try that - I don't know how much CPU you will save, but it is worth a try.

    A better (less CPU intensive) approach would be to use iCall

    Add an event that detects when the primary pool member is marked up, then drop all connections to the standby pool member (with a tmsh delete sys conn ss-server ). All new connections will be balanced to the primary (assuming it has a higher priority setting).

    Or on any pool member down event, swap the priority setting of the pool members so that the currently active pool member has the highest priority until it is marked down, so it will always get the connections.