Hello hackers, at the moment it is possible to reserve some amount of connection slots for superusers and this behavior is controlled by superuser_reserved_connections configuration parameter with the default value = 3.
In case if all non-reserved connection slots are busy, replica fails to open a new connection and start streaming from the primary. Such behavior is very bad if you want to run postgresql HA clusters Initially, replication connections required superuser privileges (in 9.0) and therefore they were deliberately excluded from superuser_reserved_connections. Basically that means it has never been possible to reserve come connection slots for replication connections. Later (9.1) it became possible to create a user with REPLICATION and NOSUPERUSER options, but comment in the postinit.c still tells that superuser is required. Now I think now it is a time to go further, and we should make it possible to reserve some connection slots for replication in a manner similar to superuser connections. How should it work: 1. If we know that we got the replication connection, we just should make sure that there are at least superuser_reserved_connections free connection slots are available. 2. If we know that this is neither superuser nor replication connection, we should check that there are at least (superuser_reserved_connections + NumWalSenders() - max_wal_senders) connection slots are available. And the last question how to control the number of reserved slots for replication. There are two options: 1. We can introduce a new GUC for that: replication_reserved_connections 2. Or we can just use the value of max_wal_senders Personally, I more like the second option. Attached patch implements above described functionality. Feedback is very appretiated. Regards, -- Alexander Kukushkin
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index d60026d..13caeef 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -2273,6 +2273,10 @@ InitWalSenderSlot(void) walsnd->applyLag = -1; walsnd->state = WALSNDSTATE_STARTUP; walsnd->latch = &MyProc->procLatch; + + /* increment the number of allocated wal sender slots */ + pg_atomic_fetch_add_u32(&WalSndCtl->num_wal_senders, 1); + SpinLockRelease(&walsnd->mutex); /* don't need the lock anymore */ MyWalSnd = (WalSnd *) walsnd; @@ -2306,6 +2310,10 @@ WalSndKill(int code, Datum arg) walsnd->latch = NULL; /* Mark WalSnd struct as no longer being in use. */ walsnd->pid = 0; + + /* decrement the number of allocated wal sender slots */ + pg_atomic_fetch_sub_u32(&WalSndCtl->num_wal_senders, 1); + SpinLockRelease(&walsnd->mutex); } @@ -3022,6 +3030,7 @@ WalSndShmemInit(void) { /* First time through, so initialize */ MemSet(WalSndCtl, 0, WalSndShmemSize()); + pg_atomic_init_u32(&WalSndCtl->num_wal_senders, 0); for (i = 0; i < NUM_SYNC_REP_WAIT_MODE; i++) SHMQueueInit(&(WalSndCtl->SyncRepQueue[i])); @@ -3576,3 +3585,9 @@ LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now) Assert(time != 0); return now - time; } + +/* Return the amount of allocated wal_sender slots */ +uint32 NumWalSenders(void) +{ + return pg_atomic_read_u32(&WalSndCtl->num_wal_senders); +} diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 5ef6315..18392c1 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -789,17 +789,25 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username, } /* - * The last few connection slots are reserved for superusers. Although - * replication connections currently require superuser privileges, we - * don't allow them to consume the reserved slots, which are intended for - * interactive use. + * The last few connection slots are reserved for superusers and replication. + * Superusers always have a priority over replication connections. */ - if ((!am_superuser || am_walsender) && - ReservedBackends > 0 && - !HaveNFreeProcs(ReservedBackends)) - ereport(FATAL, - (errcode(ERRCODE_TOO_MANY_CONNECTIONS), - errmsg("remaining connection slots are reserved for non-replication superuser connections"))); + if (am_walsender) + { + if (ReservedBackends > 0 && !HaveNFreeProcs(ReservedBackends)) + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("remaining connection slots are reserved for non-replication superuser connections"))); + } + else if (!am_superuser) + { + uint32 n = ReservedBackends + max_wal_senders - NumWalSenders(); + + if (n > 0 && !HaveNFreeProcs(n)) + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("remaining connection slots are reserved for replication or superuser connections"))); + } /* Check replication permissions needed for walsender processes. */ if (am_walsender) diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h index 45b72a7..9ebcb57 100644 --- a/src/include/replication/walsender.h +++ b/src/include/replication/walsender.h @@ -37,6 +37,7 @@ extern int max_wal_senders; extern int wal_sender_timeout; extern bool log_replication_commands; +extern uint32 NumWalSenders(void); extern void InitWalSender(void); extern bool exec_replication_command(const char *query_string); extern void WalSndErrorCleanup(void); diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h index 4b90477..3ef8f4d 100644 --- a/src/include/replication/walsender_private.h +++ b/src/include/replication/walsender_private.h @@ -14,6 +14,7 @@ #include "access/xlog.h" #include "nodes/nodes.h" +#include "port/atomics.h" #include "replication/syncrep.h" #include "storage/latch.h" #include "storage/shmem.h" @@ -101,6 +102,8 @@ typedef struct */ bool sync_standbys_defined; + pg_atomic_uint32 num_wal_senders; + WalSnd walsnds[FLEXIBLE_ARRAY_MEMBER]; } WalSndCtlData;