]> git.kaiwu.me - haproxy.git/commitdiff
OPTIM: server: remember the lowest known unused server ID
authorWilly Tarreau <w@1wt.eu>
Wed, 26 Aug 2026 16:40:45 +0000 (18:40 +0200)
committerWilly Tarreau <w@1wt.eu>
Wed, 26 Aug 2026 16:56:42 +0000 (18:56 +0200)
Tiburce Giroux reported that adding many servers without an explicit
ID took time in O(N^2) for N servers due to the lookup of the lowest
unused ID. Instead, let's just store this value after finding it, and
restart from there upon next "add server". In a test with 20k servers
the delay went down from 14.4 seconds to 66 milliseconds, or 218 times
faster. And it takes 408ms to add 50000 servers (~120k srv/s).

In practice we store the ID of the place to start searching from, so
typically id+1 after a successful addition at the last position. Upon
deletion we just reset the ID to the deleted server if it was before
so that next addition starts searching from there again. Thanks to
this, the lowest known hole is always the one allocated.

It would make sense to backport this to 3.4 since it's being increasingly
used in a dynamic way.

include/haproxy/proxy-t.h
src/server.c

index be3fd3a7c6c0646bf100f0f3bd81d7bf9c95b8d1..7b65e67a2d3dbfd5239d55c32de545f3baa504a3 100644 (file)
@@ -475,6 +475,7 @@ struct proxy {
                const char *file;               /* file where the section appears */
                struct ceb_node uuid_node;      /* place in the tree of used IDs, indexes <uuid> above */
                int line;                       /* line where the section appears */
+               uint first_unused_id;           /* lowest ID to start searching unused ones from */
                struct ceb_root *used_listener_id; /* list of listener IDs in use */
                struct ceb_root *used_server_id;   /* list of server IDs in use */
                struct ceb_root *used_server_name; /* list of server names in use */
index d38e9d1edba4c04f42cb265e5cbdea00c50de2b9..c7ed32d772f49a41bf2603fa02b76e169a8415c0 100644 (file)
@@ -6742,11 +6742,12 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct
         * operation are completed.
         */
        if (!srv->puid) {
-               srv->puid = server_get_next_id(be, 1);
+               srv->puid = server_get_next_id(be, be->conf.first_unused_id ? be->conf.first_unused_id : 1);
                if (!srv->puid) {
                        ha_alert("Cannot attach server : no id left in proxy\n");
                        goto out;
                }
+               be->conf.first_unused_id = srv->puid;
        }
 
        if (!srv_alloc_lb(srv, be)) {
@@ -6772,6 +6773,9 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct
 
        /* insert the server in the backend trees */
        server_index_id(be, srv);
+       if (be->conf.first_unused_id == srv->puid)
+               be->conf.first_unused_id = srv->puid + 1;
+
        _srv_register_name(srv, be);
        /* addr_key could be NULL if FQDN resolution is postponed (ie: add server from cli) */
        if (srv->addr_key)
@@ -7001,6 +7005,8 @@ static int cli_parse_delete_server(char **args, char *payload, struct appctx *ap
        proxy_take(srv->proxy);
 
        /* remove srv from addr_node tree */
+       if (srv->puid < be->conf.first_unused_id)
+               be->conf.first_unused_id = srv->puid; // search from there for next add.
        ceb32_item_delete(&be->conf.used_server_id, conf.puid_node, puid, srv);
        cebuis_item_delete(&be->conf.used_server_name, conf.name_node, id, srv);
        cebuis_item_delete(&be->used_server_addr, addr_node, addr_key, srv);