[CFT] multi server failover setup

Eric Wong normalperson at yhbt.net
Sat Sep 19 19:23:45 EDT 2009


I've been meaning to test this setup somewhere for a while, but never
got the right app/(real) traffic/hardware to do it with.  So maybe
somebody can try it out and let us know if this works...

It's for applications running the same code/data on a cluster of
machines, so this doesn't apply to the majority of low-traffic sites
out there.

The goal is to avoid having to run a dedicated load
balancer/proxy/virtual IP in front of the application servers
by using round-robin DNS for the general load-balancing case.

The immediate downside to this approach is that if one host goes
completely dead (or just the nginx instance), your clients will
have the burden of doing failover.  I know curl will failover if
DNS resolves multiple addresses but I'm not sure about other
HTTP clients...

This setup requires that nginx + unicorn run on all application
boxes.

The request flow for a 3 machine cluster would look like this:

             /--> host1(nginx --> unicorn)
            /
           /
    client ----> host2(nginx --> unicorn)
           \
            \
             \--> host3(nginx --> unicorn)


Now in the unicorn configs:

    # We configure unicorn to listen on both a UNIX socket and TCP:

    # First the UNIX socket socket
    listen "/tmp/sock", :backlog => 5 # fails quickly if overloaded

    # use a internal IP here since Unicorn should not talk to the
    # public...
    listen "#{internal_ip}:8080", :backlog => 1024 # fail slowly

    # the exact numbers for the :backlog values are flexible
    # the idea here is just to have a very low :backlog for the
    # UNIX domain socket and big one as a fallback for the
    # TCP socket


And the nginx configs:

    upstream unicorn_failover {
      # primary connection, "fail_timeout=0" is to ensure that
      # we always *try* to use the UNIX socket on every request
      # that comes in:
      server unix:/tmp/sock fail_timeout=0;

      # failover connections, "backup" ensures these will not
      # be used unless connections to unix:/tmp/sock are failing
      # it may be advisable to reorder these on a per-host basis
      # so "host1" does not connect to "host1_private" as its
      # first choice...
      server host1_private:8080 fail_timeout=0 backup;
      server host2_private:8080 fail_timeout=0 backup;
      server host3_private:8080 fail_timeout=0 backup;
    }


The idea is to have the majority of requests will use the UNIX
socket which is a bit faster than the TCP one.  However, if
_some_ of your machines start getting overloaded, nginx can
failover to using TCP, likely on a different host which may be
less loaded.

So under heavy load, you may end up with requests flowing like
this:

                              /------>-----\
                             /              \
             /--> host1(nginx --> unicorn)<--+-<-\
            /                \___/           |   |
           /                 /   \           |   |
    client ----> host2(nginx --> unicorn)    V   ^
           \                 \___/           |   |
            \                /   \           |   |
             \--> host3(nginx --> unicorn)<--/   |
                             \                   |
                              `------>----------'

All the extra lines from this diagram are the "backup" flows.

This should help address the problem of certain (rare) actions
being extremely slow while the majority of the actions still run
quickly.  It would help smooth out pathological cases where all
the slow actions somehow end up clustering on a small subset of
machines in the cluster while the rest of the machines are still
in the comfort zone.

This setup will not help under extreme load when the entire
cluster is at capacity, only the case where an unbalanced subset
of the cluster is maxed out.

Let me know if you have any questions/comments and especially
any results if you're brave enough to try this :)

-- 
Eric Wong


More information about the mongrel-unicorn mailing list