Add new GUC reserved_connections.
authorRobert Haas <rhaas@postgresql.org>
Fri, 20 Jan 2023 20:36:36 +0000 (15:36 -0500)
committerRobert Haas <rhaas@postgresql.org>
Fri, 20 Jan 2023 20:39:13 +0000 (15:39 -0500)
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

doc/src/sgml/config.sgml
doc/src/sgml/user-manag.sgml
src/backend/postmaster/postmaster.c
src/backend/storage/lmgr/proc.c
src/backend/utils/init/postinit.c
src/backend/utils/misc/guc_tables.c
src/backend/utils/misc/postgresql.conf.sample
src/include/catalog/pg_authid.dat
src/include/postmaster/postmaster.h
src/include/storage/proc.h

index e019a1aac9937dbf9c7053d71c98d70c5d553825..dc9b78b0b7d4cb0b5a3fde948acfdc1606001444 100644 (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>
index 71a2d8f298572d49c00ec058b0a727bd23743ec0..002c1e3affb5c60dc814ad1ebadba03b48e26d11 100644 (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>
index 3f799c4ac82b4953c80bf0d41f5b146417f401ca..aca1ef91b5782b37cc794219d104be152e8bfbcb 100644 (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)
index f8ac4edd6ffe00450603bd3c02aa6d613f82ecc4..22b4278610c03dfbbb2053f3b18582356a3482f2 100644 (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);
 }
 
 /*
index 40f145e0ab1966f6367a453a98fb386a062e8689..2f07ca7a0e1dd8694dabf8fe258950b85ff600f2 100644 (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)
index 0fa9fdd3c5866791c39b2c5a70d0fe01625bef3c..e1753a40fa9496ba3b3024509f35844105062e18 100644 (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."),
index 4cceda416222692b8230e57a63a4d863b97b9849..d06074b86f6de8fce9dedffc1207685dcf5900e8 100644 (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)
index 2a2fee7d28346e6890b4adc3a851172027d7e283..f2e5663c9fec1165d79df2363cd9ee68b62d61ff 100644 (file)
   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_' },
 
 ]
index 0e4b8ded34414811e7ea1b1a623dc207be620140..3b3889c58c049be0aec3d89c7532a7b5da914862 100644 (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;
index dd45b8ee9b7424a9b56d08fa19054352c5753f9e..4258cd92c9c0b76764e1cb000517bb8e23f1d308 100644 (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);