limiting connections per user/database - Mailing list pgsql-patches

From Petr Jelínek
Subject limiting connections per user/database
Date
Msg-id 42BDC668.1060900@parba.cz
Whole thread Raw
Responses Re: limiting connections per user/database
Re: limiting connections per user/database
List pgsql-patches
Hello,

patch I attached allows to set connectin limits per user or per database
(it's on TODO list).
It's proposal because I am not sure if this implementation can be
accepted (I never made new feature patch for postgres so I really dunno)
and I would like to know what you guys think about it and what I should
change.

Something about patch:
I added two new guc variables name max_db_connections and
max_user_connections which can be set by superuser which means it can be
in main config file or in user/database config.
I was thinking about 3 different aproaches - the other two was using
max_connections with set/get hooks or change in catalog tables but new
fuc variables seemed best solution to me.

Conenction limits are checked in InitPostgres function after user and
database configs are loaded.
Patch works only when stats are on because it takes number of
connections per user and database from there - I had to patch pgstat to
store user connection stats.

Also this patch relies on bugfix I sent on Thursday but I wasn't
subcribed and it still waits for moderation so I attached it to this
mail too (pgstat.c.diff) because without it database stats are broken in
current CVS.

I modified only .c sources, no documentation, I will make documentation
changes when (and if) this will be finished and accepted.

Diffs should be against latest CVS.

--
Regards
Petr Jelinek (PJMODOS)


*** pgstat.c    Sat Jun 18 01:17:26 2005
--- pgstat.c.new    Thu Jun 23 21:38:06 2005
***************
*** 2613,2619 ****
  static void
  pgstat_recv_bestart(PgStat_MsgBestart *msg, int len)
  {
!     PgStat_StatBeEntry *entry;

      /*
       * If the backend is known dead, we ignore the message -- we don't
--- 2613,2621 ----
  static void
  pgstat_recv_bestart(PgStat_MsgBestart *msg, int len)
  {
!     PgStat_StatBeEntry *beentry;
!     PgStat_StatDBEntry *dbentry;
!     bool        found;

      /*
       * If the backend is known dead, we ignore the message -- we don't
***************
*** 2623,2632 ****
      if (pgstat_add_backend(&msg->m_hdr) != 0)
          return;

!     entry = &(pgStatBeTable[msg->m_hdr.m_backendid - 1]);
!     entry->userid = msg->m_userid;
!     memcpy(&entry->clientaddr, &msg->m_clientaddr, sizeof(entry->clientaddr));
!     entry->databaseid = msg->m_databaseid;
  }


--- 2625,2675 ----
      if (pgstat_add_backend(&msg->m_hdr) != 0)
          return;

!     beentry = &(pgStatBeTable[msg->m_hdr.m_backendid - 1]);
!     beentry->userid = msg->m_userid;
!     memcpy(&beentry->clientaddr, &msg->m_clientaddr, sizeof(beentry->clientaddr));
!     beentry->databaseid = msg->m_databaseid;
!
!     /*
!      * Lookup or create the database entry for this backends DB.
!      */
!     dbentry = (PgStat_StatDBEntry *) hash_search(pgStatDBHash,
!                                            (void *) &(msg->m_databaseid),
!                                                  HASH_ENTER, &found);
!     if (dbentry == NULL)
!         ereport(ERROR,
!                 (errcode(ERRCODE_OUT_OF_MEMORY),
!              errmsg("out of memory in statistics collector --- abort")));
!
!     /*
!      * If not found, initialize the new one.
!      */
!     if (!found)
!     {
!         HASHCTL        hash_ctl;
!
!         dbentry->tables = NULL;
!         dbentry->n_xact_commit = 0;
!         dbentry->n_xact_rollback = 0;
!         dbentry->n_blocks_fetched = 0;
!         dbentry->n_blocks_hit = 0;
!         dbentry->n_backends = 0;
!         dbentry->destroy = 0;
!
!         memset(&hash_ctl, 0, sizeof(hash_ctl));
!         hash_ctl.keysize = sizeof(Oid);
!         hash_ctl.entrysize = sizeof(PgStat_StatTabEntry);
!         hash_ctl.hash = tag_hash;
!         dbentry->tables = hash_create("Per-database table",
!                                       PGSTAT_TAB_HASH_SIZE,
!                                       &hash_ctl,
!                                       HASH_ELEM | HASH_FUNCTION);
!     }
!
!     /*
!      * Count number of connects to the database
!      */
!     dbentry->n_backends++;
  }


diff -Nacr -x CVS bah2\src\backend\postmaster\pgstat.c bah\src\backend\postmaster\pgstat.c
*** bah2\src\backend\postmaster\pgstat.c    Sat Jun 25 21:57:22 2005
--- bah\src\backend\postmaster\pgstat.c    Sat Jun 25 21:56:06 2005
***************
*** 131,136 ****
--- 131,137 ----

  static TransactionId pgStatDBHashXact = InvalidTransactionId;
  static HTAB *pgStatDBHash = NULL;
+ static HTAB *pgStatUserHash = NULL;
  static HTAB *pgStatBeDead = NULL;
  static PgStat_StatBeEntry *pgStatBeTable = NULL;
  static int    pgStatNumBackends = 0;
***************
*** 163,173 ****
--- 164,177 ----
  static void pgstat_beshutdown_hook(int code, Datum arg);

  static PgStat_StatDBEntry *pgstat_get_db_entry(int databaseid);
+ static PgStat_StatUserEntry *pgstat_get_user_entry(int userid);
  static int    pgstat_add_backend(PgStat_MsgHdr *msg);
  static void pgstat_sub_backend(int procpid);
  static void pgstat_drop_database(Oid databaseid);
+ static void pgstat_drop_user(Oid userid);
  static void pgstat_write_statsfile(void);
  static void pgstat_read_statsfile(HTAB **dbhash, Oid onlydb,
+                       HTAB **userhash,
                        PgStat_StatBeEntry **betab,
                        int *numbackends);
  static void backend_read_statsfile(void);
***************
*** 181,186 ****
--- 185,191 ----
  static void pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len);
  static void pgstat_recv_tabpurge(PgStat_MsgTabpurge *msg, int len);
  static void pgstat_recv_dropdb(PgStat_MsgDropdb *msg, int len);
+ static void pgstat_recv_dropuser(PgStat_MsgDropuser *msg, int len);
  static void pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len);


***************
*** 772,782 ****
      Relation    dbrel;
      HeapScanDesc dbscan;
      HeapTuple    dbtup;
!     Oid           *dbidlist;
!     int            dbidalloc;
!     int            dbidused;
      HASH_SEQ_STATUS hstat;
      PgStat_StatDBEntry *dbentry;
      PgStat_StatTabEntry *tabentry;
      HeapTuple    reltup;
      int            nobjects = 0;
--- 777,791 ----
      Relation    dbrel;
      HeapScanDesc dbscan;
      HeapTuple    dbtup;
!     Relation    userrel;
!     HeapScanDesc userscan;
!     HeapTuple    usertup;
!     Oid           *oidlist;
!     int            oidalloc;
!     int            oidused;
      HASH_SEQ_STATUS hstat;
      PgStat_StatDBEntry *dbentry;
+     PgStat_StatUserEntry *userentry;
      PgStat_StatTabEntry *tabentry;
      HeapTuple    reltup;
      int            nobjects = 0;
***************
*** 866,886 ****
      /*
       * Read pg_database and remember the Oid's of all existing databases
       */
!     dbidalloc = 256;
!     dbidused = 0;
!     dbidlist = (Oid *) palloc(sizeof(Oid) * dbidalloc);

      dbrel = heap_open(DatabaseRelationId, AccessShareLock);
      dbscan = heap_beginscan(dbrel, SnapshotNow, 0, NULL);
      while ((dbtup = heap_getnext(dbscan, ForwardScanDirection)) != NULL)
      {
!         if (dbidused >= dbidalloc)
          {
!             dbidalloc *= 2;
!             dbidlist = (Oid *) repalloc((char *) dbidlist,
!                                         sizeof(Oid) * dbidalloc);
          }
!         dbidlist[dbidused++] = HeapTupleGetOid(dbtup);
      }
      heap_endscan(dbscan);
      heap_close(dbrel, AccessShareLock);
--- 875,895 ----
      /*
       * Read pg_database and remember the Oid's of all existing databases
       */
!     oidalloc = 256;
!     oidused = 0;
!     oidlist = (Oid *) palloc(sizeof(Oid) * oidalloc);

      dbrel = heap_open(DatabaseRelationId, AccessShareLock);
      dbscan = heap_beginscan(dbrel, SnapshotNow, 0, NULL);
      while ((dbtup = heap_getnext(dbscan, ForwardScanDirection)) != NULL)
      {
!         if (oidused >= oidalloc)
          {
!             oidalloc *= 2;
!             oidlist = (Oid *) repalloc((char *) oidlist,
!                                         sizeof(Oid) * oidalloc);
          }
!         oidlist[oidused++] = HeapTupleGetOid(dbtup);
      }
      heap_endscan(dbscan);
      heap_close(dbrel, AccessShareLock);
***************
*** 894,902 ****
      {
          Oid            dbid = dbentry->databaseid;

!         for (i = 0; i < dbidused; i++)
          {
!             if (dbidlist[i] == dbid)
              {
                  dbid = InvalidOid;
                  break;
--- 903,911 ----
      {
          Oid            dbid = dbentry->databaseid;

!         for (i = 0; i < oidused; i++)
          {
!             if (oidlist[i] == dbid)
              {
                  dbid = InvalidOid;
                  break;
***************
*** 910,919 ****
          }
      }

      /*
!      * Free the dbid list.
       */
!     pfree(dbidlist);

      /*
       * Tell the caller how many removeable objects we found
--- 919,977 ----
          }
      }

+
      /*
!      * Clear the Oid list.
       */
!     memset(oidlist, 0, sizeof(Oid) * oidalloc);
!
!     /*
!      * Read pg_shadow and remember the Oid's of all existing users
!      */
!     userrel = heap_open(ShadowRelationId, AccessShareLock);
!     userscan = heap_beginscan(userrel, SnapshotNow, 0, NULL);
!     while ((usertup = heap_getnext(userscan, ForwardScanDirection)) != NULL)
!     {
!         if (oidused >= oidalloc)
!         {
!             oidalloc *= 2;
!             oidlist = (Oid *) repalloc((char *) oidlist,
!                                         sizeof(Oid) * oidalloc);
!         }
!         oidlist[oidused++] = HeapTupleGetOid(usertup);
!     }
!     heap_endscan(userscan);
!     heap_close(userrel, AccessShareLock);
!
!     /*
!      * Search the user hash table for dead users and tell the
!      * collector to drop them as well.
!      */
!     hash_seq_init(&hstat, pgStatUserHash);
!     while ((userentry = (PgStat_StatUserEntry *) hash_seq_search(&hstat)) != NULL)
!     {
!         Oid            userid = userentry->userid;
!
!         for (i = 0; i < oidused; i++)
!         {
!             if (oidlist[i] == userid)
!             {
!                 userid = InvalidOid;
!                 break;
!             }
!         }
!
!         if (userid != InvalidOid)
!         {
!             nobjects++;
!             pgstat_drop_user(userid);
!         }
!     }
!
!     /*
!      * Free the Oid list.
!      */
!     pfree(oidlist);

      /*
       * Tell the caller how many removeable objects we found
***************
*** 946,951 ****
--- 1004,1032 ----


  /* ----------
+  * pgstat_drop_user() -
+  *
+  *    Tell the collector that we just dropped a user.
+  *    This is the only message that shouldn't get lost in space. Otherwise
+  *    the collector will keep the statistics for the dead users until his
+  *    stats file got removed while the postmaster is down.
+  * ----------
+  */
+ static void
+ pgstat_drop_user(Oid userid)
+ {
+     PgStat_MsgDropuser msg;
+
+     if (pgStatSock < 0)
+         return;
+
+     pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DROPUSER);
+     msg.m_userid = userid;
+     pgstat_send(&msg, sizeof(msg));
+ }
+
+
+ /* ----------
   * pgstat_reset_counters() -
   *
   *    Tell the statistics collector to reset counters for our database.
***************
*** 1196,1201 ****
--- 1277,1308 ----
                                                HASH_FIND, NULL);
  }

+ /* ----------
+  * pgstat_fetch_stat_userentry() -
+  *
+  *    Support function for the SQL-callable pgstat* functions. Returns
+  *    the collected statistics for one user or NULL. NULL doesn't mean
+  *    that the user doesn't exist, it is just not yet known by the
+  *    collector, so the caller is better off to report ZERO instead.
+  * ----------
+  */
+ PgStat_StatUserEntry *
+ pgstat_fetch_stat_userentry(Oid userid)
+ {
+     /*
+      * If not done for this transaction, read the statistics collector
+      * stats file into some hash tables.
+      */
+     backend_read_statsfile();
+
+     /*
+      * Lookup the requested database; return NULL if not found
+      */
+     return (PgStat_StatUserEntry *) hash_search(pgStatUserHash,
+                                               (void *) &userid,
+                                               HASH_FIND, NULL);
+ }
+

  /* ----------
   * pgstat_fetch_stat_tabentry() -
***************
*** 1490,1496 ****
       * to zero.
       */
      pgStatRunningInCollector = TRUE;
!     pgstat_read_statsfile(&pgStatDBHash, InvalidOid, NULL, NULL);

      /*
       * Create the dead backend hashtable
--- 1597,1603 ----
       * to zero.
       */
      pgStatRunningInCollector = TRUE;
!     pgstat_read_statsfile(&pgStatDBHash, InvalidOid, &pgStatUserHash, NULL, NULL);

      /*
       * Create the dead backend hashtable
***************
*** 1670,1675 ****
--- 1777,1786 ----
                      pgstat_recv_dropdb((PgStat_MsgDropdb *) &msg, nread);
                      break;

+                 case PGSTAT_MTYPE_DROPUSER:
+                     pgstat_recv_dropuser((PgStat_MsgDropuser *) &msg, nread);
+                     break;
+
                  case PGSTAT_MTYPE_RESETCOUNTER:
                      pgstat_recv_resetcounter((PgStat_MsgResetcounter *) &msg,
                                               nread);
***************
*** 2087,2092 ****
--- 2198,2228 ----
      return result;
  }

+ /*
+  * Lookup the hash table entry for the specified user. If no hash
+  * table entry exists, initialize it.
+  */
+ static PgStat_StatUserEntry *
+ pgstat_get_user_entry(int userid)
+ {
+     PgStat_StatUserEntry *result;
+     bool found;
+
+     /* Lookup or create the hash table entry for this user */
+     result = (PgStat_StatUserEntry *) hash_search(pgStatUserHash,
+                                                 &userid,
+                                                 HASH_ENTER, &found);
+
+     /* If not found, initialize the new one. */
+     if (!found)
+     {
+         result->n_backends  = 0;
+         result->destroy = 0;
+     }
+
+     return result;
+ }
+
  /* ----------
   * pgstat_sub_backend() -
   *
***************
*** 2156,2161 ****
--- 2292,2298 ----
      HASH_SEQ_STATUS hstat;
      HASH_SEQ_STATUS tstat;
      PgStat_StatDBEntry *dbentry;
+     PgStat_StatUserEntry *userentry;
      PgStat_StatTabEntry *tabentry;
      PgStat_StatBeDead *deadbe;
      FILE       *fpout;
***************
*** 2254,2259 ****
--- 2391,2412 ----
      }

      /*
+      * Walk through the user table.
+      */
+     ereport(DEBUG3, (errmsg_internal("before write 'U'")));
+     hash_seq_init(&hstat, pgStatUserHash);
+     ereport(DEBUG3, (errmsg_internal("write 'U' - before while")));
+     while ((userentry = (PgStat_StatUserEntry *) hash_seq_search(&hstat)) != NULL)
+     {
+         ereport(DEBUG3, (errmsg_internal("write 'U' - in while 1")));
+         fputc('U', fpout);
+         ereport(DEBUG3, (errmsg_internal("write 'U' - in while 2")));
+         fwrite(userentry, sizeof(PgStat_StatUserEntry), 1, fpout);
+         ereport(DEBUG3, (errmsg_internal("write 'U' - in while 3")));
+     }
+     ereport(DEBUG3, (errmsg_internal("after write 'U'")));
+
+     /*
       * Write out the known running backends to the stats file.
       */
      i = MaxBackends;
***************
*** 2327,2336 ****
--- 2480,2492 ----
   */
  static void
  pgstat_read_statsfile(HTAB **dbhash, Oid onlydb,
+                       HTAB **userhash,
                        PgStat_StatBeEntry **betab, int *numbackends)
  {
      PgStat_StatDBEntry *dbentry;
      PgStat_StatDBEntry dbbuf;
+     PgStat_StatUserEntry *userentry;
+     PgStat_StatUserEntry userbuf;
      PgStat_StatTabEntry *tabentry;
      PgStat_StatTabEntry tabbuf;
      HASHCTL        hash_ctl;
***************
*** 2371,2376 ****
--- 2527,2545 ----
                            HASH_ELEM | HASH_FUNCTION | mcxt_flags);

      /*
+      * Create users hashtable
+      */
+     ereport(DEBUG3, (errmsg_internal("before Create users hashtable")));
+     memset(&hash_ctl, 0, sizeof(hash_ctl));
+     hash_ctl.keysize = sizeof(Oid);
+     hash_ctl.entrysize = sizeof(PgStat_StatUserEntry);
+     hash_ctl.hash = oid_hash;
+     hash_ctl.hcxt = use_mcxt;
+     *userhash = hash_create("Users hash", PGSTAT_DB_HASH_SIZE, &hash_ctl,
+                           HASH_ELEM | HASH_FUNCTION | mcxt_flags);
+     ereport(DEBUG3, (errmsg_internal("after Create users hashtable")));
+
+     /*
       * Initialize the number of known backends to zero, just in case we do
       * a silent error return below.
       */
***************
*** 2501,2506 ****
--- 2670,2707 ----
                  break;

                  /*
+                  * 'U'    A PgStat_StatUserEntry struct describing an user follows.
+                  */
+             case 'U':
+                 ereport(DEBUG3, (errmsg_internal("in read 'U' start")));
+                 if (fread(&userbuf, 1, sizeof(userbuf), fpin) != sizeof(userbuf))
+                 {
+                     ereport(pgStatRunningInCollector ? LOG : WARNING,
+                             (errmsg("corrupted pgstat.stat file")));
+                     goto done;
+                 }
+
+                 /*
+                  * Add to the user hash
+                  */
+                 userentry = (PgStat_StatUserEntry *) hash_search(*userhash,
+                                               (void *) &userbuf.userid,
+                                                              HASH_ENTER,
+                                                              &found);
+                 if (found)
+                 {
+                     ereport(pgStatRunningInCollector ? LOG : WARNING,
+                             (errmsg("corrupted pgstat.stat file")));
+                     goto done;
+                 }
+
+                 memcpy(userentry, &userbuf, sizeof(PgStat_StatUserEntry));
+                 userentry->destroy = 0;
+                 userentry->n_backends = 0;
+                 ereport(DEBUG3, (errmsg_internal("in read 'U' end")));
+                 break;
+
+                 /*
                   * 'M'    The maximum number of backends to expect follows.
                   */
              case 'M':
***************
*** 2557,2562 ****
--- 2758,2772 ----
                  if (dbentry)
                      dbentry->n_backends++;

+                 /*
+                  * Count backends per user here.
+                  */
+                 userentry = (PgStat_StatUserEntry *) hash_search(*userhash,
+                            (void *) &((*betab)[havebackends].userid),
+                                                         HASH_FIND, NULL);
+                 if (userentry)
+                     userentry->n_backends++;
+
                  havebackends++;
                  if (numbackends != 0)
                      *numbackends = havebackends;
***************
*** 2598,2603 ****
--- 2808,2814 ----
      {
          Assert(!pgStatRunningInCollector);
          pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId,
+                               &pgStatUserHash,
                                &pgStatBeTable, &pgStatNumBackends);
          pgStatDBHashXact = topXid;
      }
***************
*** 2615,2620 ****
--- 2826,2832 ----
  {
      PgStat_StatBeEntry *beentry;
      PgStat_StatDBEntry *dbentry;
+     PgStat_StatUserEntry *userentry;
      bool        found;

      /*
***************
*** 2670,2675 ****
--- 2882,2913 ----
       * Count number of connects to the database
       */
      dbentry->n_backends++;
+
+
+     /*
+      * Lookup or create the user entry for this backends user.
+      */
+     userentry = (PgStat_StatUserEntry *) hash_search(pgStatUserHash,
+                                            (void *) &(msg->m_userid),
+                                                  HASH_ENTER, &found);
+     if (userentry == NULL)
+         ereport(ERROR,
+                 (errcode(ERRCODE_OUT_OF_MEMORY),
+              errmsg("out of memory in statistics collector --- abort")));
+
+     /*
+      * If not found, initialize the new one.
+      */
+     if (!found)
+     {
+         userentry->n_backends = 0;
+         userentry->destroy = 0;
+     }
+
+     /*
+      * Count number of connects of the user
+      */
+     userentry->n_backends++;
  }


***************
*** 2865,2870 ****
--- 3103,3137 ----
       * Mark the database for destruction.
       */
      dbentry->destroy = PGSTAT_DESTROY_COUNT;
+ }
+
+
+ /* ----------
+  * pgstat_recv_dropuser() -
+  *
+  *    Arrange for dead user removal
+  * ----------
+  */
+ static void
+ pgstat_recv_dropuser(PgStat_MsgDropuser *msg, int len)
+ {
+     PgStat_StatUserEntry *userentry;
+
+     /*
+      * Make sure the backend is counted for.
+      */
+     if (pgstat_add_backend(&msg->m_hdr) < 0)
+         return;
+
+     /*
+      * Lookup the user in the hashtable.
+      */
+     userentry = pgstat_get_user_entry(msg->m_userid);
+
+     /*
+      * Mark the user for destruction.
+      */
+     userentry->destroy = PGSTAT_DESTROY_COUNT;
  }


diff -Nacr -x CVS bah2\src\backend\utils\init\globals.c bah\src\backend\utils\init\globals.c
*** bah2\src\backend\utils\init\globals.c    Sat Jan 01 00:01:40 2005
--- bah\src\backend\utils\init\globals.c    Sat Jun 25 21:56:06 2005
***************
*** 92,97 ****
--- 92,99 ----
  /* Primary determinants of sizes of shared-memory structures: */
  int            NBuffers = 1000;
  int            MaxBackends = 100;
+ int            MaxDBBackends = 0;
+ int            MaxUserBackends = 0;

  int            VacuumCostPageHit = 1;        /* GUC parameters for vacuum */
  int            VacuumCostPageMiss = 10;
diff -Nacr -x CVS bah2\src\backend\utils\init\postinit.c bah\src\backend\utils\init\postinit.c
*** bah2\src\backend\utils\init\postinit.c    Fri Jun 24 19:42:44 2005
--- bah\src\backend\utils\init\postinit.c    Sat Jun 25 21:56:06 2005
***************
*** 43,48 ****
--- 43,49 ----
  #include "utils/portal.h"
  #include "utils/relcache.h"
  #include "utils/syscache.h"
+ #include "pgstat.h"


  static bool FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace);
***************
*** 50,55 ****
--- 51,57 ----
  static void InitCommunication(void);
  static void ShutdownPostgres(int code, Datum arg);
  static bool ThereIsAtLeastOneUser(void);
+ static void CheckMaxConnections(const char *dbname, const char *username);


  /*** InitPostgres support ***/
***************
*** 436,441 ****
--- 438,450 ----
      if (!bootstrap)
          ReverifyMyDatabase(dbname);

+
+     /* Now we have database specifig & user specifig configs loaded,
+      * we can check for max_db_connections and max_user_connections
+      */
+     CheckMaxConnections(dbname, username);
+
+
      /*
       * Final phase of relation cache startup: write a new cache file if
       * necessary.  This is done after ReverifyMyDatabase to avoid writing
***************
*** 548,551 ****
--- 557,599 ----
      heap_close(pg_shadow_rel, AccessExclusiveLock);

      return result;
+ }
+
+
+ /*
+  * Check if we are not over max_db_conenctions and max_user_connections limits
+  */
+ static void
+ CheckMaxConnections(const char *dbname, const char *username)
+ {
+     PgStat_StatDBEntry *dbentry;
+     PgStat_StatUserEntry *userentry;
+
+     if (MaxDBBackends > 0)
+     {
+         if ((dbentry = pgstat_fetch_stat_dbentry(MyDatabaseId)) != NULL)
+         {
+             if (dbentry->n_backends > MaxDBBackends)
+             {
+                 ereport(FATAL,
+                     (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+                  errmsg("sorry, too many clients already for database \"%s\"",
+                     dbname)));
+             }
+         }
+     }
+
+     if (MaxUserBackends > 0)
+     {
+         if ((userentry = pgstat_fetch_stat_userentry(GetUserId())) != NULL)
+         {
+             if (userentry->n_backends > MaxUserBackends)
+             {
+                 ereport(FATAL,
+                     (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+                  errmsg("sorry, too many clients already for user \"%s\"",
+                     username)));
+             }
+         }
+     }
  }
diff -Nacr -x CVS bah2\src\backend\utils\misc\guc.c bah\src\backend\utils\misc\guc.c
*** bah2\src\backend\utils\misc\guc.c    Thu Jun 23 03:57:06 2005
--- bah\src\backend\utils\misc\guc.c    Sat Jun 25 21:56:06 2005
***************
*** 981,986 ****
--- 981,1004 ----
      },

      {
+         {"max_db_connections", PGC_SUSET, CONN_AUTH_SETTINGS,
+             gettext_noop("Sets the maximum number of concurrent connections per database."),
+             NULL
+         },
+         &MaxDBBackends,
+         0, 0, INT_MAX / BLCKSZ, NULL, NULL
+     },
+
+     {
+         {"max_user_connections", PGC_SUSET, CONN_AUTH_SETTINGS,
+             gettext_noop("Sets the maximum number of concurrent connections per user."),
+             NULL
+         },
+         &MaxUserBackends,
+         0, 0, INT_MAX / BLCKSZ, NULL, NULL
+     },
+
+     {
          {"shared_buffers", PGC_POSTMASTER, RESOURCES_MEM,
              gettext_noop("Sets the number of shared memory buffers used by the server."),
              NULL
diff -Nacr -x CVS bah2\src\bin\psql\tab-complete.c bah\src\bin\psql\tab-complete.c
*** bah2\src\bin\psql\tab-complete.c    Thu Jun 23 03:57:08 2005
--- bah\src\bin\psql\tab-complete.c    Sat Jun 25 21:56:06 2005
***************
*** 576,581 ****
--- 576,583 ----
          "log_statement_stats",
          "maintenance_work_mem",
          "max_connections",
+         "max_db_connections",
+         "max_user_connections",
          "max_files_per_process",
          "max_fsm_pages",
          "max_fsm_relations",
diff -Nacr -x CVS bah2\src\include\miscadmin.h bah\src\include\miscadmin.h
*** bah2\src\include\miscadmin.h    Sat Feb 26 20:43:34 2005
--- bah\src\include\miscadmin.h    Sat Jun 25 21:56:06 2005
***************
*** 130,135 ****
--- 130,137 ----

  extern DLLIMPORT int NBuffers;
  extern int    MaxBackends;
+ extern int    MaxDBBackends;
+ extern int    MaxUserBackends;

  extern DLLIMPORT int MyProcPid;
  extern struct Port *MyProcPort;
diff -Nacr -x CVS bah2\src\include\pgstat.h bah\src\include\pgstat.h
*** bah2\src\include\pgstat.h    Wed May 11 03:41:42 2005
--- bah\src\include\pgstat.h    Sat Jun 25 21:56:06 2005
***************
*** 28,33 ****
--- 28,34 ----
  #define PGSTAT_MTYPE_TABPURGE        5
  #define PGSTAT_MTYPE_DROPDB            6
  #define PGSTAT_MTYPE_RESETCOUNTER    7
+ #define PGSTAT_MTYPE_DROPUSER        8

  /* ----------
   * The data type used for counters.
***************
*** 175,180 ****
--- 176,193 ----


  /* ----------
+  * PgStat_MsgDropuser            Sent by the backend to tell the collector
+  *                                about dropped user
+  * ----------
+  */
+ typedef struct PgStat_MsgDropuser
+ {
+     PgStat_MsgHdr m_hdr;
+     Oid            m_userid;
+ } PgStat_MsgDropuser;
+
+
+ /* ----------
   * PgStat_MsgResetcounter        Sent by the backend to tell the collector
   *                                to reset counters
   * ----------
***************
*** 224,229 ****
--- 237,253 ----
      int            destroy;
  } PgStat_StatDBEntry;

+ /* ----------
+  * PgStat_StatUserEntry            The collectors data per user
+  * ----------
+  */
+ typedef struct PgStat_StatUserEntry
+ {
+     Oid            userid;
+     int            n_backends;
+     int            destroy;
+ } PgStat_StatUserEntry;
+

  /* ----------
   * PgStat_StatBeEntry            The collectors data per backend
***************
*** 424,429 ****
--- 448,454 ----
   */
  extern PgStat_StatDBEntry *pgstat_fetch_stat_dbentry(Oid dbid);
  extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(Oid relid);
+ extern PgStat_StatUserEntry *pgstat_fetch_stat_userentry(Oid userid);
  extern PgStat_StatBeEntry *pgstat_fetch_stat_beentry(int beid);
  extern int    pgstat_fetch_stat_numbackends(void);


pgsql-patches by date:

Previous
From: Bruce Momjian
Date:
Subject: Re: Add PG version number to NLS files
Next
From: "Andrew Dunstan"
Date:
Subject: Re: COPY FROM performance improvements