HAProxy: Reloading Your Config With Minimal Service Impact

HAProxy is a high performance load balancer. It is very light-weight, and free, making it a great option if you are in the market for a load balancer and need to keep your costs down.

Lately we’ve been making a lot of load balancer changes at work to accommodate new systems and services. Even though we have two load balancers running with keepalived taking care of any failover situations, I was thinking about how we go about reloading our configuration files. In the event of a change, the “common” way to get the changes to take effect is to run /etc/init.d/haproxy restart. This is bad for a couple major reasons:

  1. You are temporarily shutting your load balancer down
  2. You are severing any current connections going through the load balancer

You might say, “if you have two load balancers with keepalived, restarting the service should be fine since keepalived will handle the failover.” This, however, isn’t always true. Keepalived uses advertisements to determine when to fail over. The default advertisement interval is 1 second (configurable in keepalived.conf). The skew time helps to keep everyone from trying to transition at once. It is a number between 0 and 1, based on the formula (256 – priority) / 256. As defined in the RFC, the backup must receive an advertisement from the master every (3 * advert_int) + skew_time seconds. If it doesn’t hear anything from the master, it takes over.

Let’s assume you are using the default interval of 1 second. On my test machine, this is the duration of time it takes to restart haproxy:

# time /etc/init.d/haproxy restart
 * Restarting haproxy haproxy
   ...done.

real    0m0.022s
user    0m0.000s
sys     0m0.016s

In this situation, haproxy would restart much faster than your 1 second interval. You could get lucky and happen to restart it just before the check, but luck is not consistent enough to be useful. Also, in very high-traffic situations, you’ll be causing a lot of connection issues. So we cannot rely on keepalived to solve the first problem, and it definitely doesn’t solve the second problem.

After sifting through haproxy documentation (the text-based documentation, not the man page) (/usr/share/doc/haproxy/haproxy-en.txt.gz on Ubuntu), I came across this:

    313
    314     global
    315         daemon
    316         quiet
    317         nbproc  2
    318         pidfile /var/run/haproxy-private.pid
    319
    320     # to stop only those processes among others :
    321     # kill $(</var/run/haproxy-private.pid)
    322
    323     # to reload a new configuration with minimal service impact and without
    324     # breaking existing sessions :
    325     # haproxy -f haproxy.cfg -p $(</var/run/haproxy-private.pid) -st $(</var/run/haproxy-private.pid)

That last command is the one of interest. The -p asks the process to write down each of its children’s pids to the specified pid file, and the -st specifies a list of pids to send a SIGTERM to after startup. But it does this in an interesting way:

    609 The '-st' and '-sf' command line options are used to inform previously running
    610 processes that a configuration is being reloaded. They will receive the SIGTTOU
    611 signal to ask them to temporarily stop listening to the ports so that the new
    612 process can grab them. If anything wrong happens, the new process will send
    613 them a SIGTTIN to tell them to re-listen to the ports and continue their normal
    614 work. Otherwise, it will either ask them to finish (-sf) their work then softly
    615 exit, or immediately terminate (-st), breaking existing sessions. A typical use
    616 of this allows a configuration reload without service interruption :
    617
    618  # haproxy -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid)

The end-result is a reload of the configuration file which is not visible by the customer. It also solves the second problem! Let’s look at an example of the command and look at the time compared to our above example:

# time haproxy -f /etc/haproxy.cfg -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid)

real    0m0.018s
user    0m0.000s
sys     0m0.004s

I’ve specified the config file I want to use and the pid file haproxy is currently using. The $(cat /var/run/haproxy.pid) takes the output of cat /var/run/haproxy.pid and passes it in to the -sf parameter as a list, which is what it is expecting. You will notice that the time is actually faster too (.012s sys, and .004s real). It may not seem like much, but if you are dealing with very high volumes of traffic, this can be pretty important. Luckily for us it doesn’t matter because we’ve been able to reload the haproxy configuration without dropping any connections and without causing any customer-facing issues.

UPDATE: There is a reload in some of the init.d scripts (I haven’t checked every OS, so this can vary), but it uses the -st option which will break existing sessions, as opposed to using -sf to do a graceful hand-off. You can modify the haproxy_reload() function to use the -sf if you want. I also find it a bit confusing that the documentation uses $(cat /path/to/pidfile) whereas this haproxy_reload() function uses $(<$PIDFILE). Either should work, but really, way to lead by example…

  1. avatar

    what I was looking for, thanks

  2. avatar
    • jens
    • November 3rd, 2010 4:16am

    Hello Michael,

    thanks, absolutely great and helpfull article! Thanks! Jens

  3. avatar
    • Ronald
    • December 1st, 2010 11:06pm

    Hi Michael, I’m having issue with haproxy restart, I can’t restart my load balancer it says socket port are being used, I was wondering if above script need to put on another script to execute or just type it in command line?

    • avatar

      Hello Ronald! Are you using the init script to restart haproxy? If so, it should handle everything you need by just running `/etc/init.d/haproxy restart` from the command line (or in a script if you want to wrap the command for any reason). But it sounds like haproxy isn’t being shut down properly when doing a restart if the socket is still being used. You can try the modifications I noted to the init script and use the “reload” option to see if that helps. Good luck!

  4. avatar
    • Ronald
    • December 2nd, 2010 3:56am

    Yes thanks working now, your a savior…. Can I ask you 1 more thing regarding multiple routing of domain and subdomain in haproxy?

  5. avatar

    nice work indeed. Subscribing to your feeds

  6. avatar
    • Marcus Bointon
    • January 11th, 2011 1:17pm

    Nicely observed. For everyone’s peace of mind, you might like to know that the stock haproxy package in Ubuntu 10.04 does indeed use the -sf option when you ask it to reload, and the init script uses ‘$(cat $PIDFILE)’, which seems good to me.

  7. avatar
    • Musa
    • June 27th, 2011 8:02am

    Hi Michael,

    I have been bugged by the error i face when i try to restart the HAproxy :P
    Could you kindly tell me which specific changes i need to make in my HAproxy/ubuntu 10.04 to solve the following error:

    # /etc/init.d/haproxy restart
    .: 12: Can’t open /etc/rc.d/init.d/functions

    (i am running it as root)

    • avatar

      Musa – that directory structure doesn’t exist in Ubuntu. My best guess is that you’ve installed a version of HAProxy (or the init script itself) that is not meant to be used on Ubuntu, and it is looking for a file that would normally be in that place in the OS it was meant for. I don’t have a box right now to confirm, but I think that path is one you would find in Redhat and Redhat-derived distros.

  8. avatar
    • Manisha Mahawar
    • January 12th, 2012 4:38pm

    Hi Micheal,

    I am using HAProxy on RedHat and have below configuration.
    global
    daemon
    maxconn 1024
    log 127.0.0.1 local1 info

    defaults
    log global
    balance roundrobin
    mode http
    retries 3
    option redispatch
    timeout connect 300000ms
    timeout client 300000ms
    timeout server 300000ms

    listen epgs
    bind *:80
    server server1 127.0.0.1:8080 maxconn 1 check
    server server2 epg01.lab.mystrotv.com:8080 maxconn 1 check
    stats uri /stats

    I started firing 5000 request to HAProxy using JMeter. While JMeter is firing the request I removed the server2 from configuration file and fired “haproxy -f /etc/haproxy.cfg -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid) command. I noticed connection refused for 2 requests in my JMeter log.

    Do you know which configuration I should use to make HAProxy not drop any request?

    Thanks for your help in advance.
    Manisha