Add new GUC reserved_connections.

This provides a way to reserve connection slots for non-superusers.
The slots reserved via the new GUC are available only to users who
have the new predefined role pg_use_reserved_connections.
superuser_reserved_connections remains as a final reserve in case
reserved_connections has been exhausted.

Patch by Nathan Bossart. Reviewed by Tushar Ahuja and by me.

Discussion: http://postgr.es/m/20230119194601.GA4105788@nathanxps13
This commit is contained in:
Robert Haas 2023-01-20 15:36:36 -05:00
parent fe00fec1f5
commit 6e2775e4d4
10 changed files with 115 additions and 25 deletions

View File

@ -708,6 +708,37 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
<varlistentry id="guc-reserved-connections" xreflabel="reserved_connections">
<term><varname>reserved_connections</varname> (<type>integer</type>)
<indexterm>
<primary><varname>reserved_connections</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
Determines the number of connection <quote>slots</quote> that are
reserved for connections by roles with privileges of the
<link linkend="predefined-roles-table"><literal>pg_used_reserved_connections</literal></link>
role. Whenever the number of free connection slots is greater than
<xref linkend="guc-superuser-reserved-connections"/> but less than or
equal to the sum of <varname>superuser_reserved_connections</varname>
and <varname>reserved_connections</varname>, new connections will be
accepted only for superusers and roles with privileges of
<literal>pg_use_reserved_connections</literal>. If
<varname>superuser_reserved_connections</varname> or fewer connection
slots are available, new connections will be accepted only for
superusers.
</para>
<para>
The default value is zero connections. The value must be less than
<varname>max_connections</varname> minus
<varname>superuser_reserved_connections</varname>. This parameter can
only be set at server start.
</para>
</listitem>
</varlistentry>
<varlistentry id="guc-superuser-reserved-connections"
xreflabel="superuser_reserved_connections">
<term><varname>superuser_reserved_connections</varname>
@ -725,12 +756,16 @@ include_dir 'conf.d'
number of active concurrent connections is at least
<varname>max_connections</varname> minus
<varname>superuser_reserved_connections</varname>, new
connections will be accepted only for superusers.
connections will be accepted only for superusers. The connection slots
reserved by this parameter are intended as final reserve for emergency
use after the slots reserved by
<xref linkend="guc-reserved-connections"/> have been exhausted.
</para>
<para>
The default value is three connections. The value must be less
than <varname>max_connections</varname>.
than <varname>max_connections</varname> minus
<varname>reserved_connections</varname>.
This parameter can only be set at server start.
</para>
</listitem>

View File

@ -689,6 +689,11 @@ DROP ROLE doomed_role;
and <link linkend="sql-lock"><command>LOCK TABLE</command></link> on all
relations.</entry>
</row>
<row>
<entry>pg_use_reserved_connections</entry>
<entry>Allow use of connection slots reserved via
<xref linkend="guc-reserved-connections"/>.</entry>
</row>
</tbody>
</tgroup>
</table>

View File

@ -205,14 +205,24 @@ char *ListenAddresses;
/*
* SuperuserReservedConnections is the number of backends reserved for
* superuser use. This number is taken out of the pool size given by
* MaxConnections so number of backend slots available to non-superusers is
* (MaxConnections - SuperuserReservedConnections). Note what this really
* means is "if there are <= SuperuserReservedConnections connections
* available, only superusers can make new connections" --- pre-existing
* superuser connections don't count against the limit.
* superuser use, and ReservedConnections is the number of backends reserved
* for use by roles with privileges of the pg_use_reserved_connections
* predefined role. These are taken out of the pool of MaxConnections backend
* slots, so the number of backend slots available for roles that are neither
* superuser nor have privileges of pg_use_reserved_connections is
* (MaxConnections - SuperuserReservedConnections - ReservedConnections).
*
* If the number of remaining slots is less than or equal to
* SuperuserReservedConnections, only superusers can make new connections. If
* the number of remaining slots is greater than SuperuserReservedConnections
* but less than or equal to
* (SuperuserReservedConnections + ReservedConnections), only superusers and
* roles with privileges of pg_use_reserved_connections can make new
* connections. Note that pre-existing superuser and
* pg_use_reserved_connections connections don't count against the limits.
*/
int SuperuserReservedConnections;
int ReservedConnections;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
@ -908,11 +918,12 @@ PostmasterMain(int argc, char *argv[])
/*
* Check for invalid combinations of GUC settings.
*/
if (SuperuserReservedConnections >= MaxConnections)
if (SuperuserReservedConnections + ReservedConnections >= MaxConnections)
{
write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
write_stderr("%s: superuser_reserved_connections (%d) plus reserved_connections (%d) must be less than max_connections (%d)\n",
progname,
SuperuserReservedConnections, MaxConnections);
SuperuserReservedConnections, ReservedConnections,
MaxConnections);
ExitPostmaster(1);
}
if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)

View File

@ -645,27 +645,33 @@ GetStartupBufferPinWaitBufId(void)
}
/*
* Check whether there are at least N free PGPROC objects.
* Check whether there are at least N free PGPROC objects. If false is
* returned, *nfree will be set to the number of free PGPROC objects.
* Otherwise, *nfree will be set to n.
*
* Note: this is designed on the assumption that N will generally be small.
*/
bool
HaveNFreeProcs(int n)
HaveNFreeProcs(int n, int *nfree)
{
dlist_iter iter;
Assert(n > 0);
Assert(nfree);
SpinLockAcquire(ProcStructLock);
*nfree = 0;
dlist_foreach(iter, &ProcGlobal->freeProcs)
{
n--;
if (n == 0)
(*nfree)++;
if (*nfree == n)
break;
}
SpinLockRelease(ProcStructLock);
return (n <= 0);
return (*nfree == n);
}
/*

View File

@ -719,6 +719,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
bool am_superuser;
char *fullpath;
char dbname[NAMEDATALEN];
int nfree = 0;
elog(DEBUG3, "InitPostgres");
@ -922,16 +923,30 @@ InitPostgres(const char *in_dbname, Oid dboid,
}
/*
* The last few connection slots are reserved for superusers. Replication
* connections are drawn from slots reserved with max_wal_senders and not
* limited by max_connections or superuser_reserved_connections.
* The last few connection slots are reserved for superusers and roles with
* privileges of pg_use_reserved_connections. Replication connections are
* drawn from slots reserved with max_wal_senders and are not limited by
* max_connections, superuser_reserved_connections, or
* reserved_connections.
*
* Note: At this point, the new backend has already claimed a proc struct,
* so we must check whether the number of free slots is strictly less than
* the reserved connection limits.
*/
if (!am_superuser && !am_walsender &&
SuperuserReservedConnections > 0 &&
!HaveNFreeProcs(SuperuserReservedConnections))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg("remaining connection slots are reserved for superusers")));
(SuperuserReservedConnections + ReservedConnections) > 0 &&
!HaveNFreeProcs(SuperuserReservedConnections + ReservedConnections, &nfree))
{
if (nfree < SuperuserReservedConnections)
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg("remaining connection slots are reserved for superusers")));
if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg("remaining connection slots are reserved for roles with privileges of pg_use_reserved_connections")));
}
/* Check replication permissions needed for walsender processes. */
if (am_walsender)

View File

@ -2168,6 +2168,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
{
{"reserved_connections", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
gettext_noop("Sets the number of connection slots reserved for roles "
"with privileges of pg_use_reserved_connections."),
NULL
},
&ReservedConnections,
0, 0, MAX_BACKENDS,
NULL, NULL, NULL
},
{
{"min_dynamic_shared_memory", PGC_POSTMASTER, RESOURCES_MEM,
gettext_noop("Amount of dynamic shared memory reserved at startup."),

View File

@ -63,6 +63,7 @@
# (change requires restart)
#port = 5432 # (change requires restart)
#max_connections = 100 # (change requires restart)
#reserved_connections = 0 # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart)
#unix_socket_directories = '/tmp' # comma-separated list of directories
# (change requires restart)

View File

@ -89,5 +89,10 @@
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '4550', oid_symbol => 'ROLE_PG_USE_RESERVED_CONNECTIONS',
rolname => 'pg_use_reserved_connections', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
]

View File

@ -16,6 +16,7 @@
/* GUC options */
extern PGDLLIMPORT bool EnableSSL;
extern PGDLLIMPORT int SuperuserReservedConnections;
extern PGDLLIMPORT int ReservedConnections;
extern PGDLLIMPORT int PostPortNumber;
extern PGDLLIMPORT int Unix_socket_permissions;
extern PGDLLIMPORT char *Unix_socket_group;

View File

@ -445,7 +445,7 @@ extern void InitAuxiliaryProcess(void);
extern void SetStartupBufferPinWaitBufId(int bufid);
extern int GetStartupBufferPinWaitBufId(void);
extern bool HaveNFreeProcs(int n);
extern bool HaveNFreeProcs(int n, int *nfree);
extern void ProcReleaseLocks(bool isCommit);
extern ProcWaitStatus ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable);