1
0
mirror of https://github.com/postgres/postgres.git synced 2025-09-02 04:21:28 +03:00

Support connection load balancing in libpq

This adds support for load balancing connections with libpq using a
connection parameter: load_balance_hosts=<string>. When setting the
param to random, hosts and addresses will be connected to in random
order. This then results in load balancing across these addresses and
hosts when multiple clients or frequent connection setups are used.

The randomization employed performs two levels of shuffling:

  1. The given hosts are randomly shuffled, before resolving them
     one-by-one.
  2. Once a host its addresses get resolved, the returned addresses
     are shuffled, before trying to connect to them one-by-one.

Author: Jelte Fennema <postgres@jeltef.nl>
Reviewed-by: Aleksander Alekseev <aleksander@timescale.com>
Reviewed-by: Michael Banck <mbanck@gmx.net>
Reviewed-by: Andrey Borodin <amborodin86@gmail.com>
Discussion: https://postgr.es/m/PR3PR83MB04768E2FF04818EEB2179949F7A69@PR3PR83MB0476.EURPRD83.prod.outlook.
This commit is contained in:
Daniel Gustafsson
2023-03-29 21:53:38 +02:00
parent 44d85ba5a3
commit 7f5b19817e
10 changed files with 431 additions and 3 deletions

View File

@@ -123,6 +123,7 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
#define DefaultChannelBinding "disable"
#endif
#define DefaultTargetSessionAttrs "any"
#define DefaultLoadBalanceHosts "disable"
#ifdef USE_SSL
#define DefaultSSLMode "prefer"
#define DefaultSSLCertMode "allow"
@@ -351,6 +352,11 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
{"load_balance_hosts", "PGLOADBALANCEHOSTS",
DefaultLoadBalanceHosts, NULL,
"Load-Balance-Hosts", "", 8, /* sizeof("disable") = 8 */
offsetof(struct pg_conn, load_balance_hosts)},
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
@@ -435,6 +441,8 @@ static void pgpassfileWarning(PGconn *conn);
static void default_threadlock(int acquire);
static bool sslVerifyProtocolVersion(const char *version);
static bool sslVerifyProtocolRange(const char *min, const char *max);
static bool parse_int_param(const char *value, int *result, PGconn *conn,
const char *context);
/* global variable because fe-auth.c needs to access it */
@@ -1020,6 +1028,31 @@ parse_comma_separated_list(char **startptr, bool *more)
return p;
}
/*
* Initializes the prng_state field of the connection. We want something
* unpredictable, so if possible, use high-quality random bits for the
* seed. Otherwise, fall back to a seed based on the connection address,
* timestamp and PID.
*/
static void
libpq_prng_init(PGconn *conn)
{
uint64 rseed;
struct timeval tval = {0};
if (pg_prng_strong_seed(&conn->prng_state))
return;
gettimeofday(&tval, NULL);
rseed = ((uint64) conn) ^
((uint64) getpid()) ^
((uint64) tval.tv_usec) ^
((uint64) tval.tv_sec);
pg_prng_seed(&conn->prng_state, rseed);
}
/*
* connectOptions2
*
@@ -1619,6 +1652,49 @@ connectOptions2(PGconn *conn)
else
conn->target_server_type = SERVER_TYPE_ANY;
/*
* validate load_balance_hosts option, and set load_balance_type
*/
if (conn->load_balance_hosts)
{
if (strcmp(conn->load_balance_hosts, "disable") == 0)
conn->load_balance_type = LOAD_BALANCE_DISABLE;
else if (strcmp(conn->load_balance_hosts, "random") == 0)
conn->load_balance_type = LOAD_BALANCE_RANDOM;
else
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
"load_balance_hosts",
conn->load_balance_hosts);
return false;
}
}
else
conn->load_balance_type = LOAD_BALANCE_DISABLE;
if (conn->load_balance_type == LOAD_BALANCE_RANDOM)
{
libpq_prng_init(conn);
/*
* This is the "inside-out" variant of the Fisher-Yates shuffle
* algorithm. Notionally, we append each new value to the array and
* then swap it with a randomly-chosen array element (possibly
* including itself, else we fail to generate permutations with the
* last integer last). The swap step can be optimized by combining it
* with the insertion.
*/
for (i = 1; i < conn->nconnhost; i++)
{
int j = pg_prng_uint64_range(&conn->prng_state, 0, i);
pg_conn_host temp = conn->connhost[j];
conn->connhost[j] = conn->connhost[i];
conn->connhost[i] = temp;
}
}
/*
* Resolve special "auto" client_encoding from the locale
*/
@@ -2626,6 +2702,32 @@ keep_going: /* We will come back to here until there is
if (ret)
goto error_return; /* message already logged */
/*
* If random load balancing is enabled we shuffle the addresses.
*/
if (conn->load_balance_type == LOAD_BALANCE_RANDOM)
{
/*
* This is the "inside-out" variant of the Fisher-Yates shuffle
* algorithm. Notionally, we append each new value to the array
* and then swap it with a randomly-chosen array element (possibly
* including itself, else we fail to generate permutations with
* the last integer last). The swap step can be optimized by
* combining it with the insertion.
*
* We don't need to initialize conn->prng_state here, because that
* already happened in connectOptions2.
*/
for (int i = 1; i < conn->naddr; i++)
{
int j = pg_prng_uint64_range(&conn->prng_state, 0, i);
AddrInfo temp = conn->addr[j];
conn->addr[j] = conn->addr[i];
conn->addr[i] = temp;
}
}
reset_connection_state_machine = true;
conn->try_next_host = false;
}
@@ -4320,6 +4422,7 @@ freePGconn(PGconn *conn)
free(conn->outBuffer);
free(conn->rowBuf);
free(conn->target_session_attrs);
free(conn->load_balance_hosts);
termPQExpBuffer(&conn->errorMessage);
termPQExpBuffer(&conn->workBuffer);