From ac2b3f7aa1dfa92b2da28414bf2cd3f334637632 Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Wed, 13 Nov 2024 14:07:35 +1300 Subject: [PATCH 1/3] Least connections algorithm Least connections algorithm --- src/vmod_dynamic.c | 73 ++++++++++++++++++++++++++++++++++++++++++-- src/vmod_dynamic.h | 6 ++++ src/vmod_dynamic.vcc | 13 +++++++- 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/vmod_dynamic.c b/src/vmod_dynamic.c index 7b2e8db..0b3a8b1 100644 --- a/src/vmod_dynamic.c +++ b/src/vmod_dynamic.c @@ -251,6 +251,54 @@ dom_wait_active(struct dynamic_domain *dom) DBG(NULL, dom, "wait-active ret %d", ret); } +/* find a healthy dynamic_ref with the least connections */ +static struct dynamic_ref * +dom_find_leastconn(VRT_CTX, struct dynamic_domain *dom) +{ + struct dynamic_ref *next, *best_next; + unsigned most_connections_available; + + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + CHECK_OBJ_NOTNULL(dom, DYNAMIC_DOMAIN_MAGIC); + + dom_wait_active(dom); + + if (dom->status > DYNAMIC_ST_ACTIVE) + return (NULL); + + next = VTAILQ_FIRST(&dom->refs); + best_next = NULL; + most_connections_available = 0; + + do { + CHECK_OBJ_ORNULL(next, DYNAMIC_REF_MAGIC); + if (next != NULL) + next = VTAILQ_NEXT(next, list); + if (next == NULL) + break; + + if (next->dir != creating && next->dir != NULL && VRT_Healthy(ctx, next->dir, NULL)) { + if (VALID_OBJ((struct backend *)next->dir->priv, BACKEND_MAGIC)) { + struct backend *be; + unsigned connections_available; + + CAST_OBJ_NOTNULL(be, next->dir->priv, BACKEND_MAGIC); + connections_available = be->max_connections > 0 ? be->max_connections - be->n_conn : - be->n_conn; + if (connections_available > most_connections_available) { + best_next = next; + most_connections_available = connections_available; + } + } + } + } while (1); + + if (best_next != NULL) { + return best_next; + } else { + return NULL; + } +} + /* find a healthy dynamic_ref */ static struct dynamic_ref * dom_find(VRT_CTX, struct dynamic_domain *dom, struct dynamic_ref *start, @@ -330,7 +378,7 @@ static VCL_BACKEND v_matchproto_(vdi_resolve_f) dom_resolve(VRT_CTX, VCL_BACKEND d) { struct dynamic_domain *dom; - struct dynamic_ref *r; + struct dynamic_ref *r = NULL; VCL_BACKEND n = NULL; CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); @@ -348,7 +396,12 @@ dom_resolve(VRT_CTX, VCL_BACKEND d) dynamic_gc_expired(dom->obj); Lck_Lock(&dom->mtx); - r = dom_find(ctx, dom, dom->current, NULL, NULL, 1); + if (dom->obj->algorithm == LEAST) { + r = dom_find_leastconn(ctx, dom); + } + if (r == NULL) { + r = dom_find(ctx, dom, dom->current, NULL, NULL, 1); + } dom->current = r; if (r != NULL) VRT_Assign_Backend(&n, r->dir); @@ -1308,6 +1361,18 @@ dynamic_ttl_parse(const char *s) INCOMPL(); } +static inline enum dynamic_algorithm_e +dynamic_algorithm_parse(const char *algorithm_s) +{ + switch (algorithm_s[0]) { + case 'R': return RR; break; + case 'L': return LEAST; break; + default: INCOMPL(); + } + INCOMPL(); + NEEDLESS(return(0)); +} + VCL_VOID v_matchproto_() vmod_director__init(VRT_CTX, @@ -1333,7 +1398,8 @@ vmod_director__init(VRT_CTX, VCL_INT keep, VCL_STRING authority, VCL_DURATION wait_timeout, - VCL_INT wait_limit) + VCL_INT wait_limit, + VCL_ENUM algorithm_arg) { struct vmod_dynamic_director *obj; @@ -1406,6 +1472,7 @@ vmod_director__init(VRT_CTX, obj->max_connections = (unsigned)max_connections; obj->proxy_header = (unsigned)proxy_header; obj->ttl_from = dynamic_ttl_parse(ttl_from_arg); + obj->algorithm = dynamic_algorithm_parse(algorithm_arg); obj->keep = (unsigned)keep; obj->wait_timeout = wait_timeout; obj->wait_limit = wait_limit; diff --git a/src/vmod_dynamic.h b/src/vmod_dynamic.h index bce0729..f409f15 100644 --- a/src/vmod_dynamic.h +++ b/src/vmod_dynamic.h @@ -76,6 +76,11 @@ enum dynamic_ttl_e { TTL_E_MAX }; +enum dynamic_algorithm_e { + RR, + LEAST +}; + struct dynamic_domain { unsigned magic; #define DYNAMIC_DOMAIN_MAGIC 0x1bfe1345 @@ -205,6 +210,7 @@ struct vmod_dynamic_director { const struct res_cb *resolver; struct VPFX(dynamic_resolver) *resolver_inst; enum dynamic_ttl_e ttl_from; + enum dynamic_algorithm_e algorithm; unsigned debug; }; diff --git a/src/vmod_dynamic.vcc b/src/vmod_dynamic.vcc index 9a63f2e..6b07936 100644 --- a/src/vmod_dynamic.vcc +++ b/src/vmod_dynamic.vcc @@ -252,7 +252,8 @@ $Object director( INT keep = 3, STRING authority = NULL, DURATION wait_timeout = -1, - INT wait_limit = 0) + INT wait_limit = 0, + ENUM { RR, LEAST } algorithm = "RR") Description Create a DNS director. @@ -366,6 +367,16 @@ Parameters: a high positive value (``UINT_MAX``, usually 4 294 967 295, see :ref:`limits.h(7POSIX)`). + - *algorithm* (default: RR) + + Load balancing algorithm to use. + + ``RR`` cycles between health backends. + + ``LEAST`` chooses the healthy backend with the most number of connections available + (i.e. the difference between ``max_connections`` and the backends current connections). + The algorithm works in a similar way if ``max_connections`` isn't set. + Parameters to set attributes of backends See varnish documentation for details From 59e8bddbb4f94c180e49c544213b33fcc5ed2ecd Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Wed, 13 Nov 2024 16:27:27 +1300 Subject: [PATCH 2/3] fix: most_connections_available check had integer overflow --- src/vmod_dynamic.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vmod_dynamic.c b/src/vmod_dynamic.c index 0b3a8b1..95a355e 100644 --- a/src/vmod_dynamic.c +++ b/src/vmod_dynamic.c @@ -256,7 +256,7 @@ static struct dynamic_ref * dom_find_leastconn(VRT_CTX, struct dynamic_domain *dom) { struct dynamic_ref *next, *best_next; - unsigned most_connections_available; + int most_connections_available; CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); CHECK_OBJ_NOTNULL(dom, DYNAMIC_DOMAIN_MAGIC); @@ -268,7 +268,7 @@ dom_find_leastconn(VRT_CTX, struct dynamic_domain *dom) next = VTAILQ_FIRST(&dom->refs); best_next = NULL; - most_connections_available = 0; + most_connections_available = INT_MIN; do { CHECK_OBJ_ORNULL(next, DYNAMIC_REF_MAGIC); @@ -280,7 +280,7 @@ dom_find_leastconn(VRT_CTX, struct dynamic_domain *dom) if (next->dir != creating && next->dir != NULL && VRT_Healthy(ctx, next->dir, NULL)) { if (VALID_OBJ((struct backend *)next->dir->priv, BACKEND_MAGIC)) { struct backend *be; - unsigned connections_available; + int connections_available; CAST_OBJ_NOTNULL(be, next->dir->priv, BACKEND_MAGIC); connections_available = be->max_connections > 0 ? be->max_connections - be->n_conn : - be->n_conn; From f7139d9f2022f9cbcd599d7b5688f0a2a0bfcd9a Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Fri, 15 Nov 2024 09:19:38 +1300 Subject: [PATCH 3/3] fix: leastconn skips first backend --- src/vmod_dynamic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vmod_dynamic.c b/src/vmod_dynamic.c index 95a355e..4156d74 100644 --- a/src/vmod_dynamic.c +++ b/src/vmod_dynamic.c @@ -272,8 +272,6 @@ dom_find_leastconn(VRT_CTX, struct dynamic_domain *dom) do { CHECK_OBJ_ORNULL(next, DYNAMIC_REF_MAGIC); - if (next != NULL) - next = VTAILQ_NEXT(next, list); if (next == NULL) break; @@ -290,6 +288,8 @@ dom_find_leastconn(VRT_CTX, struct dynamic_domain *dom) } } } + + next = VTAILQ_NEXT(next, list); } while (1); if (best_next != NULL) {