Use backups if unavailable (not just down)

Using haproxy, I want:

  • A pool of 'main' servers and 'backup' servers, though they don't necessarily have to be in separate pools.
  • Each backend has a low 'maxconn' (in this case 1)
  • Clients should not wait in a queue. If there are no immediately available servers in the 'main' pool they should be shunted to the 'backup' pool without delay.

Right now I have one backend, 'main' servers have an absurdly high weighting and it 'works'.

acl use_backend + connslots is along the right lines but without the patch in my own answer it isn't perfect.

Bonus points for not requiring a modified haproxy binary.


Solution 1:

The correct way is to add an ACL in the frontend which checks the amount of connections on the server, and then makes a decision based on that.

The config below will check the "monitor_conns" frontend and if there are 500 or more connections, they will be sent to the "backups" backend, otherwise they'll go to the "regular" backend.

Here's an untested example:

frontend monitor_conns
  bind *:80
  acl too_many_conns fe_conn 500
  use_backend backups if too_many_conns
  default_backend regular

backend backups
  ... your config
  server backupsrv 192.168.0.101:80 check port 80 maxconn 1000 inter 1s rise 1 fall 1

backend regular
  ... your config
  server regularsrv 192.168.0.100:80 check port 80 maxconn 500 inter 1s rise 1 fall 1

It's just an example, but it should give you an idea on how to proceed.

Solution 2:

Old question, but I face the same problem, and here is my solution:

You can check front end conn using acl, and use backend that have extra server for that extra server I mean your backup server

So the config will look like this

frontend frontend1 127.0.0.1:9200
    mode tcp
    acl max_conn_reached fe_conn gt 15
    acl production_almost_dead nbsrv(prod1) lt 2

    default_backend prod1
    use_backend prod1_and_prod2 if max_conn_reached OR production_almost_dead

backend prod1
    mode tcp
    balance leastconn
    server se_prod1 127.0.0.1:8001 check maxconn 10
    server se_prod2 127.0.0.1:8002 check maxconn 10

backend prod1_and_prod2
    mode tcp
    balance leastconn

    server se_prod1 127.0.0.1:8001 check maxconn 10
    server se_prod2 127.0.0.1:8002 check maxconn 10

    server se_backup1 127.0.0.1:8003 check maxconn 10
    server se_backup2 127.0.0.1:8004 check maxconn 10

The front end will use backup server (together with production server) if connection on frontend is greater than 15 or one service on backend1 is down

Solution 3:

The following seems to work for me but it has required patching haproxy-1.4.15/src/backend.c:

# diff haproxy-1.4.15/src/backend.c backend.c
1298a1299,1333
> /* set test->i to the number of enabled servers on the proxy */
> static int
> acl_fetch_connfree(struct proxy *px, struct session *l4, void *l7, int dir,
>                     struct acl_expr *expr, struct acl_test *test)
> {
>         struct server *iterator;
>         test->flags = ACL_TEST_F_VOL_TEST;
>         if (expr->arg_len) {
>                 /* another proxy was designated, we must look for it */
>                 for (px = proxy; px; px = px->next)
>                         if ((px->cap & PR_CAP_BE) && !strcmp(px->id, expr->arg.str))
>                                 break;
>         }
>         if (!px)
>                 return 0;
>
>         test->i = 0;
>         iterator = px->srv;
>         while (iterator) {
>                 if ((iterator->state & SRV_RUNNING) == 0) {
>                         iterator = iterator->next;
>                         continue;
>                 }
>                 if (iterator->maxconn == 0) {
>                         test->i = -1;
>                         return 1;
>                 }
>
>                 test->i += (iterator->maxconn - (iterator->cur_sess + iterator->nbpend));
>                 iterator = iterator->next;
>         }
>
>         return 1;
> }
>
1461a1497
>       { "connfree", acl_parse_int,   acl_fetch_connfree, acl_match_int, ACL_USE_NOTHING },

I can then use connfree in my acl:

frontend frontend1
    bind *:12345
    acl main_full connfree(main) eq 0
    use_backend backup if main_full
    default_backend     main

backend main
    balance leastconn
    default-server maxconn 1 maxqueue 1
    server main2 10.0.0.1:12345 check
    server main1 10.0.0.2:12345 check

backend backup
    balance leastconn
    default-server maxconn 1 maxqueue 1
    server backup1 10.0.1.1:12345 check
    server backup2 10.0.1.2:12345 check

Hopefully comparing acl_fetch_connfree() to acl_fetch_connslots() will make the change obvious:

old = (maxconn - current conns) + (maxqueue - pending conns)

new = maxconn - (current conns + pending conns)