public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v55 3/3] Make archiver process an auxiliary process
5+ messages / 4 participants
[nested] [flat]

* [PATCH v55 3/3] Make archiver process an auxiliary process
@ 2021-03-11 09:01 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 5+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-11 09:01 UTC (permalink / raw)

This makes it possible to archiver process uses ipc functions and let
us monitor archiver process status.
---
 doc/src/sgml/monitoring.sgml             |   1 +
 src/backend/access/transam/xlogarchive.c |   4 +-
 src/backend/bootstrap/bootstrap.c        |  22 ++-
 src/backend/postmaster/pgarch.c          | 240 ++++++++++-------------
 src/backend/postmaster/postmaster.c      |  81 ++++----
 src/backend/storage/ipc/ipci.c           |   2 +
 src/include/miscadmin.h                  |   2 +
 src/include/postmaster/pgarch.h          |  14 +-
 src/include/storage/pmsignal.h           |   1 -
 src/include/storage/proc.h               |   8 +-
 src/tools/pgindent/typedefs.list         |   1 +
 11 files changed, 164 insertions(+), 212 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 1ba813bbb9..a96455bdb2 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -935,6 +935,7 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
        <literal>logical replication worker</literal>,
        <literal>parallel worker</literal>, <literal>background writer</literal>,
        <literal>client backend</literal>, <literal>checkpointer</literal>,
+       <literal>archiver</literal>,
        <literal>startup</literal>, <literal>walreceiver</literal>,
        <literal>walsender</literal> and <literal>walwriter</literal>.
        In addition, background workers registered by extensions may have
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 1c5a4f8b5a..26b023e754 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -25,11 +25,11 @@
 #include "common/archive.h"
 #include "miscadmin.h"
 #include "postmaster/startup.h"
+#include "postmaster/pgarch.h"
 #include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/lwlock.h"
-#include "storage/pmsignal.h"
 
 /*
  * Attempt to retrieve the specified file from off-line archival storage.
@@ -491,7 +491,7 @@ XLogArchiveNotify(const char *xlog)
 
 	/* Notify archiver that it's got something to do */
 	if (IsUnderPostmaster)
-		SendPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER);
+		PgArchWakeup();
 }
 
 /*
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 6f615e6622..41da0c5059 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -317,6 +317,9 @@ AuxiliaryProcessMain(int argc, char *argv[])
 		case StartupProcess:
 			MyBackendType = B_STARTUP;
 			break;
+		case ArchiverProcess:
+			MyBackendType = B_ARCHIVER;
+			break;
 		case BgWriterProcess:
 			MyBackendType = B_BG_WRITER;
 			break;
@@ -437,30 +440,29 @@ AuxiliaryProcessMain(int argc, char *argv[])
 			proc_exit(1);		/* should never return */
 
 		case StartupProcess:
-			/* don't set signals, startup process has its own agenda */
 			StartupProcessMain();
-			proc_exit(1);		/* should never return */
+			proc_exit(1);
+
+		case ArchiverProcess:
+			PgArchiverMain();
+			proc_exit(1);
 
 		case BgWriterProcess:
-			/* don't set signals, bgwriter has its own agenda */
 			BackgroundWriterMain();
-			proc_exit(1);		/* should never return */
+			proc_exit(1);
 
 		case CheckpointerProcess:
-			/* don't set signals, checkpointer has its own agenda */
 			CheckpointerMain();
-			proc_exit(1);		/* should never return */
+			proc_exit(1);
 
 		case WalWriterProcess:
-			/* don't set signals, walwriter has its own agenda */
 			InitXLOGAccess();
 			WalWriterMain();
-			proc_exit(1);		/* should never return */
+			proc_exit(1);
 
 		case WalReceiverProcess:
-			/* don't set signals, walreceiver has its own agenda */
 			WalReceiverMain();
-			proc_exit(1);		/* should never return */
+			proc_exit(1);
 
 		default:
 			elog(PANIC, "unrecognized process type: %d", (int) MyAuxProcType);
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index edec311f12..07ba989a37 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -38,16 +38,14 @@
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "postmaster/fork_process.h"
 #include "postmaster/interrupt.h"
 #include "postmaster/pgarch.h"
-#include "postmaster/postmaster.h"
-#include "storage/dsm.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
-#include "storage/pg_shmem.h"
 #include "storage/pmsignal.h"
+#include "storage/procsignal.h"
+#include "storage/spin.h"
 #include "utils/guc.h"
 #include "utils/ps_status.h"
 
@@ -73,153 +71,101 @@
  */
 #define NUM_ORPHAN_CLEANUP_RETRIES 3
 
+/* Shared memory area for archiver process */
+typedef struct PgArchData
+{
+	int			pgprocno;		/* pgprocno of archiver process */
+} PgArchData;
+
 
 /* ----------
  * Local data
  * ----------
  */
-static time_t last_pgarch_start_time;
 static time_t last_sigterm_time = 0;
+static PgArchData *PgArch = NULL;
 
 /*
  * Flags set by interrupt handlers for later service in the main loop.
  */
-static volatile sig_atomic_t wakened = false;
 static volatile sig_atomic_t ready_to_stop = false;
 
 /* ----------
  * Local function forward declarations
  * ----------
  */
-#ifdef EXEC_BACKEND
-static pid_t pgarch_forkexec(void);
-#endif
-
-NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn();
-static void pgarch_waken(SIGNAL_ARGS);
 static void pgarch_waken_stop(SIGNAL_ARGS);
 static void pgarch_MainLoop(void);
 static void pgarch_ArchiverCopyLoop(void);
 static bool pgarch_archiveXlog(char *xlog);
 static bool pgarch_readyXlog(char *xlog);
 static void pgarch_archiveDone(char *xlog);
+static void pgarch_die(int code, Datum arg);
 
-
-/* ------------------------------------------------------------
- * Public functions called from postmaster follow
- * ------------------------------------------------------------
- */
-
-/*
- * pgarch_start
- *
- *	Called from postmaster at startup or after an existing archiver
- *	died.  Attempt to fire up a fresh archiver process.
- *
- *	Returns PID of child process, or 0 if fail.
- *
- *	Note: if fail, we will be called again from the postmaster main loop.
- */
-int
-pgarch_start(void)
+/* Report shared memory space needed by PgArchShmemInit */
+Size
+PgArchShmemSize(void)
 {
-	time_t		curtime;
-	pid_t		pgArchPid;
+	Size		size = 0;
 
-	/*
-	 * Do nothing if no archiver needed
-	 */
-	if (!XLogArchivingActive())
-		return 0;
-
-	/*
-	 * Do nothing if too soon since last archiver start.  This is a safety
-	 * valve to protect against continuous respawn attempts if the archiver is
-	 * dying immediately at launch. Note that since we will be re-called from
-	 * the postmaster main loop, we will get another chance later.
-	 */
-	curtime = time(NULL);
-	if ((unsigned int) (curtime - last_pgarch_start_time) <
-		(unsigned int) PGARCH_RESTART_INTERVAL)
-		return 0;
-	last_pgarch_start_time = curtime;
+	size = add_size(size, sizeof(PgArchData));
 
-#ifdef EXEC_BACKEND
-	switch ((pgArchPid = pgarch_forkexec()))
-#else
-	switch ((pgArchPid = fork_process()))
-#endif
-	{
-		case -1:
-			ereport(LOG,
-					(errmsg("could not fork archiver: %m")));
-			return 0;
-
-#ifndef EXEC_BACKEND
-		case 0:
-			/* in postmaster child ... */
-			InitPostmasterChild();
-
-			/* Close the postmaster's sockets */
-			ClosePostmasterPorts(false);
+	return size;
+}
 
-			/* Drop our connection to postmaster's shared memory, as well */
-			dsm_detach_all();
-			PGSharedMemoryDetach();
+/* Allocate and initialize archiver-related shared memory */
+void
+PgArchShmemInit(void)
+{
+	bool		found;
 
-			PgArchiverMain(0, NULL);
-			break;
-#endif
+	PgArch = (PgArchData *)
+		ShmemInitStruct("Archiver Data", PgArchShmemSize(), &found);
 
-		default:
-			return (int) pgArchPid;
+	if (!found)
+	{
+		/* First time through, so initialize */
+		MemSet(PgArch, 0, PgArchShmemSize());
+		PgArch->pgprocno = INVALID_PGPROCNO;
 	}
-
-	/* shouldn't get here */
-	return 0;
 }
 
-/* ------------------------------------------------------------
- * Local functions called by archiver follow
- * ------------------------------------------------------------
- */
-
-
-#ifdef EXEC_BACKEND
-
 /*
- * pgarch_forkexec() -
+ *  PgArchIsSuppressed
  *
- * Format up the arglist for, then fork and exec, archive process
+ * Return true if archiver relaunch is suppressed.
+ *
+ * This is a safety valve to protect against continuous respawn attempts if the
+ * archiver is dying immediately at launch. Note that since we will retry to
+ * launch the archiver from the postmaster main loop, we will get another
+ * chance later.
  */
-static pid_t
-pgarch_forkexec(void)
+bool
+PgArchCanRestart(void)
 {
-	char	   *av[10];
-	int			ac = 0;
-
-	av[ac++] = "postgres";
+	static time_t last_pgarch_start_time = 0;
+	time_t		curtime = time(NULL);
 
-	av[ac++] = "--forkarch";
-
-	av[ac++] = NULL;			/* filled in by postmaster_forkexec */
+	/*
+	 * allow to launch also for the first time and when something's wrong in
+	 * the time values.
+	 */
+	if (last_pgarch_start_time == 0 ||
+		curtime < last_pgarch_start_time ||
+		(curtime - last_pgarch_start_time) >= PGARCH_RESTART_INTERVAL)
+	{
+		last_pgarch_start_time = curtime;
 
-	av[ac] = NULL;
-	Assert(ac < lengthof(av));
+		return true;
+	}
 
-	return postmaster_forkexec(ac, av);
+	return false;
 }
-#endif							/* EXEC_BACKEND */
 
 
-/*
- * PgArchiverMain
- *
- *	The argc/argv parameters are valid only in EXEC_BACKEND case.  However,
- *	since we don't use 'em, it hardly matters...
- */
-NON_EXEC_STATIC void
-PgArchiverMain(int argc, char *argv[])
+/* Main entry point for archiver process */
+void
+PgArchiverMain(void)
 {
 	/*
 	 * Ignore all signals usually bound to some action in the postmaster,
@@ -231,33 +177,51 @@ PgArchiverMain(int argc, char *argv[])
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	pqsignal(SIGALRM, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, pgarch_waken);
+	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, pgarch_waken_stop);
+
 	/* Reset some signals that are accepted by postmaster but not here */
 	pqsignal(SIGCHLD, SIG_DFL);
+
+	/* Unblock signals (they were blocked when the postmaster forked us) */
 	PG_SETMASK(&UnBlockSig);
 
-	MyBackendType = B_ARCHIVER;
-	init_ps_display(NULL);
+	/* We shouldn't be launched unnecessarily. */
+	Assert(XLogArchivingActive());
+
+	/* Arrange to clean up at archiver exit */
+	on_shmem_exit(pgarch_die, 0);
+
+	/*
+	 * Advertise our pgproc index so that backends can use our latch to wake
+	 * us up while we're sleeping.
+	 */
+	PgArch->pgprocno = MyProc->pgprocno;
 
 	pgarch_MainLoop();
 
-	exit(0);
+	proc_exit(0);
 }
 
-/* SIGUSR1 signal handler for archiver process */
-static void
-pgarch_waken(SIGNAL_ARGS)
+/*
+ * Wake up the archiver
+ */
+void
+PgArchWakeup(void)
 {
-	int			save_errno = errno;
+	int			arch_pgprocno = PgArch->pgprocno;
 
-	/* set flag that there is work to be done */
-	wakened = true;
-	SetLatch(MyLatch);
-
-	errno = save_errno;
+	/*
+	 * We don't acquire ProcArrayLock here.  It's actually fine because
+	 * procLatch isn't ever freed, so we just can potentially set the wrong
+	 * process' (or no process') latch.  Even in that case the archiver will
+	 * be relaunched shortly and will start archiving.
+	 */
+	if (arch_pgprocno != INVALID_PGPROCNO)
+		SetLatch(&ProcGlobal->allProcs[arch_pgprocno].procLatch);
 }
 
+
 /* SIGUSR2 signal handler for archiver process */
 static void
 pgarch_waken_stop(SIGNAL_ARGS)
@@ -282,14 +246,6 @@ pgarch_MainLoop(void)
 	pg_time_t	last_copy_time = 0;
 	bool		time_to_stop;
 
-	/*
-	 * We run the copy loop immediately upon entry, in case there are
-	 * unarchived files left over from a previous database run (or maybe the
-	 * archiver died unexpectedly).  After that we wait for a signal or
-	 * timeout before doing more.
-	 */
-	wakened = true;
-
 	/*
 	 * There shouldn't be anything for the archiver to do except to wait for a
 	 * signal ... however, the archiver exists to protect our data, so she
@@ -328,12 +284,8 @@ pgarch_MainLoop(void)
 		}
 
 		/* Do what we're here for */
-		if (wakened || time_to_stop)
-		{
-			wakened = false;
-			pgarch_ArchiverCopyLoop();
-			last_copy_time = time(NULL);
-		}
+		pgarch_ArchiverCopyLoop();
+		last_copy_time = time(NULL);
 
 		/*
 		 * Sleep until a signal is received, or until a poll is forced by
@@ -354,13 +306,9 @@ pgarch_MainLoop(void)
 							   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
 							   timeout * 1000L,
 							   WAIT_EVENT_ARCHIVER_MAIN);
-				if (rc & WL_TIMEOUT)
-					wakened = true;
 				if (rc & WL_POSTMASTER_DEATH)
 					time_to_stop = true;
 			}
-			else
-				wakened = true;
 		}
 
 		/*
@@ -744,3 +692,15 @@ pgarch_archiveDone(char *xlog)
 	StatusFilePath(rlogdone, xlog, ".done");
 	(void) durable_rename(rlogready, rlogdone, WARNING);
 }
+
+
+/*
+ * pgarch_die
+ *
+ * Exit-time cleanup handler
+ */
+static void
+pgarch_die(int code, Datum arg)
+{
+	PgArch->pgprocno = INVALID_PGPROCNO;
+}
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index edab95a19e..e8af05c04e 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -443,9 +443,10 @@ static void InitPostmasterDeathWatchHandle(void);
  * even during recovery.
  */
 #define PgArchStartupAllowed()	\
-	((XLogArchivingActive() && pmState == PM_RUN) ||	\
-	 (XLogArchivingAlways() &&	\
-	  (pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY)))
+	(((XLogArchivingActive() && pmState == PM_RUN) ||			\
+	  (XLogArchivingAlways() &&									  \
+	   (pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY))) && \
+	 PgArchCanRestart())
 
 #ifdef EXEC_BACKEND
 
@@ -548,6 +549,7 @@ static void ShmemBackendArrayRemove(Backend *bn);
 #endif							/* EXEC_BACKEND */
 
 #define StartupDataBase()		StartChildProcess(StartupProcess)
+#define StartArchiver()			StartChildProcess(ArchiverProcess)
 #define StartBackgroundWriter() StartChildProcess(BgWriterProcess)
 #define StartCheckpointer()		StartChildProcess(CheckpointerProcess)
 #define StartWalWriter()		StartChildProcess(WalWriterProcess)
@@ -1792,7 +1794,7 @@ ServerLoop(void)
 
 		/* If we have lost the archiver, try to start a new one. */
 		if (PgArchPID == 0 && PgArchStartupAllowed())
-			PgArchPID = pgarch_start();
+			PgArchPID = StartArchiver();
 
 		/* If we need to signal the autovacuum launcher, do so now */
 		if (avlauncher_needs_signal)
@@ -3007,7 +3009,7 @@ reaper(SIGNAL_ARGS)
 			if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0)
 				AutoVacPID = StartAutoVacLauncher();
 			if (PgArchStartupAllowed() && PgArchPID == 0)
-				PgArchPID = pgarch_start();
+				PgArchPID = StartArchiver();
 			if (PgStatPID == 0)
 				PgStatPID = pgstat_start();
 
@@ -3142,20 +3144,22 @@ reaper(SIGNAL_ARGS)
 		}
 
 		/*
-		 * Was it the archiver?  If so, just try to start a new one; no need
-		 * to force reset of the rest of the system.  (If fail, we'll try
-		 * again in future cycles of the main loop.).  Unless we were waiting
-		 * for it to shut down; don't restart it in that case, and
+		 * Was it the archiver?  If exit status is zero (normal) or one (FATAL
+		 * exit), we assume everything is all right just like normal backends
+		 * and just try to restart a new one so that we immediately retry
+		 * archiving remaining files. (If fail, we'll try again in future
+		 * cycles of the postmaster's main loop.) Unless we were waiting for
+		 * it to shut down; don't restart it in that case, and
 		 * PostmasterStateMachine() will advance to the next shutdown step.
 		 */
 		if (pid == PgArchPID)
 		{
 			PgArchPID = 0;
-			if (!EXIT_STATUS_0(exitstatus))
-				LogChildExit(LOG, _("archiver process"),
-							 pid, exitstatus);
+			if (!EXIT_STATUS_0(exitstatus) && !EXIT_STATUS_1(exitstatus))
+				HandleChildCrash(pid, exitstatus,
+								 _("archiver process"));
 			if (PgArchStartupAllowed())
-				PgArchPID = pgarch_start();
+				PgArchPID = StartArchiver();
 			continue;
 		}
 
@@ -3403,7 +3407,7 @@ CleanupBackend(int pid,
 
 /*
  * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer,
- * walwriter, autovacuum, or background worker.
+ * walwriter, autovacuum, archiver or background worker.
  *
  * The objectives here are to clean up our local state about the child
  * process, and to signal all other remaining children to quickdie.
@@ -3609,19 +3613,16 @@ HandleChildCrash(int pid, int exitstatus, const char *procname)
 		signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT));
 	}
 
-	/*
-	 * Force a power-cycle of the pgarch process too.  (This isn't absolutely
-	 * necessary, but it seems like a good idea for robustness, and it
-	 * simplifies the state-machine logic in the case where a shutdown request
-	 * arrives during crash processing.)
-	 */
-	if (PgArchPID != 0 && take_action)
+	/* Take care of the archiver too */
+	if (pid == PgArchPID)
+		PgArchPID = 0;
+	else if (PgArchPID != 0 && take_action)
 	{
 		ereport(DEBUG2,
 				(errmsg_internal("sending %s to process %d",
-								 "SIGQUIT",
+								 (SendStop ? "SIGSTOP" : "SIGQUIT"),
 								 (int) PgArchPID)));
-		signal_child(PgArchPID, SIGQUIT);
+		signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT));
 	}
 
 	/*
@@ -3804,12 +3805,11 @@ PostmasterStateMachine(void)
 		 * (including autovac workers), no bgworkers (including unconnected
 		 * ones), and no walwriter, autovac launcher or bgwriter.  If we are
 		 * doing crash recovery or an immediate shutdown then we expect the
-		 * checkpointer to exit as well, otherwise not. The archiver, stats,
-		 * and syslogger processes are disregarded since they are not
-		 * connected to shared memory; we also disregard dead_end children
-		 * here. Walsenders are also disregarded, they will be terminated
-		 * later after writing the checkpoint record, like the archiver
-		 * process.
+		 * checkpointer to exit as well, otherwise not. The stats and
+		 * syslogger processes are disregarded since they are not connected to
+		 * shared memory; we also disregard dead_end children here. Walsenders
+		 * and archiver are also disregarded, they will be terminated later
+		 * after writing the checkpoint record.
 		 */
 		if (CountChildren(BACKEND_TYPE_ALL - BACKEND_TYPE_WALSND) == 0 &&
 			StartupPID == 0 &&
@@ -3912,6 +3912,7 @@ PostmasterStateMachine(void)
 			Assert(CheckpointerPID == 0);
 			Assert(WalWriterPID == 0);
 			Assert(AutoVacPID == 0);
+			Assert(PgArchPID == 0);
 			/* syslogger is not considered here */
 			pmState = PM_NO_CHILDREN;
 		}
@@ -5037,12 +5038,6 @@ SubPostmasterMain(int argc, char *argv[])
 
 		StartBackgroundWorker();
 	}
-	if (strcmp(argv[1], "--forkarch") == 0)
-	{
-		/* Do not want to attach to shared memory */
-
-		PgArchiverMain(argc, argv); /* does not return */
-	}
 	if (strcmp(argv[1], "--forkcol") == 0)
 	{
 		/* Do not want to attach to shared memory */
@@ -5140,7 +5135,7 @@ sigusr1_handler(SIGNAL_ARGS)
 		 */
 		Assert(PgArchPID == 0);
 		if (XLogArchivingAlways())
-			PgArchPID = pgarch_start();
+			PgArchPID = StartArchiver();
 
 		/*
 		 * If we aren't planning to enter hot standby mode later, treat
@@ -5194,16 +5189,6 @@ sigusr1_handler(SIGNAL_ARGS)
 	if (StartWorkerNeeded || HaveCrashedWorker)
 		maybe_start_bgworkers();
 
-	if (CheckPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER) &&
-		PgArchPID != 0)
-	{
-		/*
-		 * Send SIGUSR1 to archiver process, to wake it up and begin archiving
-		 * next WAL file.
-		 */
-		signal_child(PgArchPID, SIGUSR1);
-	}
-
 	/* Tell syslogger to rotate logfile if requested */
 	if (SysLoggerPID != 0)
 	{
@@ -5445,6 +5430,10 @@ StartChildProcess(AuxProcType type)
 				ereport(LOG,
 						(errmsg("could not fork startup process: %m")));
 				break;
+			case ArchiverProcess:
+				ereport(LOG,
+						(errmsg("could not fork archiver process: %m")));
+				break;
 			case BgWriterProcess:
 				ereport(LOG,
 						(errmsg("could not fork background writer process: %m")));
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index f9bbe97b50..3e4ec53a97 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -144,6 +144,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, ReplicationOriginShmemSize());
 		size = add_size(size, WalSndShmemSize());
 		size = add_size(size, WalRcvShmemSize());
+		size = add_size(size, PgArchShmemSize());
 		size = add_size(size, ApplyLauncherShmemSize());
 		size = add_size(size, SnapMgrShmemSize());
 		size = add_size(size, BTreeShmemSize());
@@ -258,6 +259,7 @@ CreateSharedMemoryAndSemaphores(void)
 	ReplicationOriginShmemInit();
 	WalSndShmemInit();
 	WalRcvShmemInit();
+	PgArchShmemInit();
 	ApplyLauncherShmemInit();
 
 	/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 54693e047a..013850ac28 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -417,6 +417,7 @@ typedef enum
 	BootstrapProcess,
 	StartupProcess,
 	BgWriterProcess,
+	ArchiverProcess,
 	CheckpointerProcess,
 	WalWriterProcess,
 	WalReceiverProcess,
@@ -429,6 +430,7 @@ extern AuxProcType MyAuxProcType;
 #define AmBootstrapProcess()		(MyAuxProcType == BootstrapProcess)
 #define AmStartupProcess()			(MyAuxProcType == StartupProcess)
 #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess)
+#define AmArchiverProcess()			(MyAuxProcType == ArchiverProcess)
 #define AmCheckpointerProcess()		(MyAuxProcType == CheckpointerProcess)
 #define AmWalWriterProcess()		(MyAuxProcType == WalWriterProcess)
 #define AmWalReceiverProcess()		(MyAuxProcType == WalReceiverProcess)
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index d102a21ab7..1e47a143e1 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -26,14 +26,10 @@
 #define MAX_XFN_CHARS	40
 #define VALID_XFN_CHARS "0123456789ABCDEF.history.backup.partial"
 
-/* ----------
- * Functions called from postmaster
- * ----------
- */
-extern int	pgarch_start(void);
-
-#ifdef EXEC_BACKEND
-extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn();
-#endif
+extern Size PgArchShmemSize(void);
+extern void PgArchShmemInit(void);
+extern bool PgArchCanRestart(void);
+extern void PgArchiverMain(void) pg_attribute_noreturn();
+extern void PgArchWakeup(void);
 
 #endif							/* _PGARCH_H */
diff --git a/src/include/storage/pmsignal.h b/src/include/storage/pmsignal.h
index dbbed18f61..8ed4d87ae6 100644
--- a/src/include/storage/pmsignal.h
+++ b/src/include/storage/pmsignal.h
@@ -34,7 +34,6 @@ typedef enum
 {
 	PMSIGNAL_RECOVERY_STARTED,	/* recovery has started */
 	PMSIGNAL_BEGIN_HOT_STANDBY, /* begin Hot Standby */
-	PMSIGNAL_WAKEN_ARCHIVER,	/* send a NOTIFY signal to xlog archiver */
 	PMSIGNAL_ROTATE_LOGFILE,	/* send SIGUSR1 to syslogger to rotate logfile */
 	PMSIGNAL_START_AUTOVAC_LAUNCHER,	/* start an autovacuum launcher */
 	PMSIGNAL_START_AUTOVAC_WORKER,	/* start an autovacuum worker */
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index a777cb64a1..2fd1ff09a7 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -370,11 +370,11 @@ extern PGPROC *PreparedXactProcs;
  * We set aside some extra PGPROC structures for auxiliary processes,
  * ie things that aren't full-fledged backends but need shmem access.
  *
- * Background writer, checkpointer and WAL writer run during normal operation.
- * Startup process and WAL receiver also consume 2 slots, but WAL writer is
- * launched only after startup has exited, so we only need 4 slots.
+ * Background writer, checkpointer, WAL writer and archiver run during normal
+ * operation.  Startup process and WAL receiver also consume 2 slots, but WAL
+ * writer is launched only after startup has exited, so we only need 5 slots.
  */
-#define NUM_AUXILIARY_PROCS		4
+#define NUM_AUXILIARY_PROCS		5
 
 /* configurable options */
 extern PGDLLIMPORT int DeadlockTimeout;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 99a34be465..78e852569e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1572,6 +1572,7 @@ PGresAttValue
 PGresParamDesc
 PGresult
 PGresult_data
+PgArchData
 PHANDLE
 PLAINTREE
 PLUID_AND_ATTRIBUTES
-- 
2.27.0


----Next_Part(Fri_Mar_12_17_24_43_2021_866)----





^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: Add tracking of backend memory allocated to pg_stat_activity
@ 2023-01-05 18:58 Reid Thompson <[email protected]>
  2023-01-05 20:13 ` Re: Add tracking of backend memory allocated to pg_stat_activity Justin Pryzby <[email protected]>
  0 siblings, 1 reply; 5+ messages in thread

From: Reid Thompson @ 2023-01-05 18:58 UTC (permalink / raw)
  To: vignesh C <[email protected]>; +Cc: [email protected]; Justin Pryzby <[email protected]>; Andres Freund <[email protected]>; [email protected]; Drouvot, Bertrand <[email protected]>; Melanie Plageman <[email protected]>

On Tue, 2023-01-03 at 16:25 +0530, vignesh C wrote:
> ...
> The patch does not apply on top of HEAD as in [1], please post a
> rebased patch:
>... 
> Regards,
> Vignesh

Per conversation in thread listed below, patches have been submitted to the "Add the ability to limit the amount of memory that can be allocated to backends" thread
https://www.postgresql.org/message-id/[email protected]

0001-Add-tracking-of-backend-memory-allocated-to-pg_stat_.patch
0002-Add-the-ability-to-limit-the-amount-of-memory-that-c.patch

On Thu, 8 Dec 2022 at 19:44, Reid Thompson
<reid(dot)thompson(at)crunchydata(dot)com> wrote:
>
> On Sun, 2022-11-27 at 09:40 -0600, Justin Pryzby wrote:
> > > ...
> > > I still wonder whether there needs to be a separate CF entry for
> > > the 0001 patch.  One issue is that there's two different lists of
> > > people involved in the threads.
> > >
>
> I'm OK with containing the conversation to one thread if everyone else
> is.  If there's no argument against, then patches after today will go
> to the "Add the ability to limit the amount of memory that can be
> allocated to backends" thread
> https://www.postgresql.org/message-id/[email protected]

-- 
Reid Thompson
Senior Software Engineer
Crunchy Data, Inc.

[email protected]
www.crunchydata.com







^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: Add tracking of backend memory allocated to pg_stat_activity
  2023-01-05 18:58 Re: Add tracking of backend memory allocated to pg_stat_activity Reid Thompson <[email protected]>
@ 2023-01-05 20:13 ` Justin Pryzby <[email protected]>
  2023-01-05 21:33   ` Re: Add tracking of backend memory allocated to pg_stat_activity Reid Thompson <[email protected]>
  0 siblings, 1 reply; 5+ messages in thread

From: Justin Pryzby @ 2023-01-05 20:13 UTC (permalink / raw)
  To: Reid Thompson <[email protected]>; +Cc: vignesh C <[email protected]>; Andres Freund <[email protected]>; [email protected]; Drouvot, Bertrand <[email protected]>; Melanie Plageman <[email protected]>

On Thu, Jan 05, 2023 at 01:58:33PM -0500, Reid Thompson wrote:
> On Tue, 2023-01-03 at 16:25 +0530, vignesh C wrote:
> > ...
> > The patch does not apply on top of HEAD as in [1], please post a
> > rebased patch:
> >... 
> > Regards,
> > Vignesh
> 
> Per conversation in thread listed below, patches have been submitted to the "Add the ability to limit the amount of memory that can be allocated to backends" thread
> https://www.postgresql.org/message-id/[email protected]

I suggest to close the associated CF entry.

(Also, the people who participated in this thread may want to be
included in the other thread going forward.)

> 0001-Add-tracking-of-backend-memory-allocated-to-pg_stat_.patch
> 0002-Add-the-ability-to-limit-the-amount-of-memory-that-c.patch
> 
> On Thu, 8 Dec 2022 at 19:44, Reid Thompson
> <reid(dot)thompson(at)crunchydata(dot)com> wrote:
> >
> > On Sun, 2022-11-27 at 09:40 -0600, Justin Pryzby wrote:
> > > > ...
> > > > I still wonder whether there needs to be a separate CF entry for
> > > > the 0001 patch.  One issue is that there's two different lists of
> > > > people involved in the threads.
> > > >
> >
> > I'm OK with containing the conversation to one thread if everyone else
> > is.  If there's no argument against, then patches after today will go
> > to the "Add the ability to limit the amount of memory that can be
> > allocated to backends" thread
> > https://www.postgresql.org/message-id/[email protected]






^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: Add tracking of backend memory allocated to pg_stat_activity
  2023-01-05 18:58 Re: Add tracking of backend memory allocated to pg_stat_activity Reid Thompson <[email protected]>
  2023-01-05 20:13 ` Re: Add tracking of backend memory allocated to pg_stat_activity Justin Pryzby <[email protected]>
@ 2023-01-05 21:33   ` Reid Thompson <[email protected]>
  2023-08-31 16:18     ` Re: Add tracking of backend memory allocated to pg_stat_activity John Morris <[email protected]>
  0 siblings, 1 reply; 5+ messages in thread

From: Reid Thompson @ 2023-01-05 21:33 UTC (permalink / raw)
  To: Justin Pryzby <[email protected]>; +Cc: [email protected]; vignesh C <[email protected]>; Andres Freund <[email protected]>; [email protected]; Drouvot, Bertrand <[email protected]>; Melanie Plageman <[email protected]>; Kyotaro Horiguchi <[email protected]>; StephenFrost <[email protected]>; Ted Yu <[email protected]>

On Thu, 2023-01-05 at 14:13 -0600, Justin Pryzby wrote:
> 
> I suggest to close the associated CF entry.

Closed with status of Withdrawn. If that is invalid, please advise.

> (Also, the people who participated in this thread may want to be
> included in the other thread going forward.)

Explicitly adding previously missed participants to Cc: - that conversation/patches are being consolidated to
the thread  "Add the ability to limit the amount of memory that can be allocated to backends"
 https://www.postgresql.org/message-id/[email protected]



Thanks,
Reid

-- 
Reid Thompson
Senior Software Engineer
Crunchy Data, Inc.

[email protected]
www.crunchydata.com







^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: Add tracking of backend memory allocated to pg_stat_activity
  2023-01-05 18:58 Re: Add tracking of backend memory allocated to pg_stat_activity Reid Thompson <[email protected]>
  2023-01-05 20:13 ` Re: Add tracking of backend memory allocated to pg_stat_activity Justin Pryzby <[email protected]>
  2023-01-05 21:33   ` Re: Add tracking of backend memory allocated to pg_stat_activity Reid Thompson <[email protected]>
@ 2023-08-31 16:18     ` John Morris <[email protected]>
  0 siblings, 0 replies; 5+ messages in thread

From: John Morris @ 2023-08-31 16:18 UTC (permalink / raw)
  To: [email protected] <[email protected]>; Justin Pryzby <[email protected]>; +Cc: vignesh C <[email protected]>; Andres Freund <[email protected]>; [email protected] <[email protected]>; Drouvot, Bertrand <[email protected]>; Melanie Plageman <[email protected]>; Kyotaro Horiguchi <[email protected]>; StephenFrost <[email protected]>; Ted Yu <[email protected]>

Here is an updated version of the earlier work.

This version:
   1) Tracks memory as requested by the backend.
   2) Includes allocations made during program startup.
   3) Optimizes the "fast path" to only update two local variables.
   4) Places a cluster wide limit on total memory allocated.

The cluster wide limit is useful for multi-hosting. One greedy cluster doesn't starve
the other clusters of memory.

Note there isn't a good way to track actual memory used by a cluster. 
Ideally, we like to get the working set size of each memory segment along with
the size of the associated kernel data structures. 
Gathering that info in a portable way is a "can of worms".
Instead, we're managing memory as requested by the application.
While not identical, the two approaches are strongly correlated.

 The memory model used is
   1) Each process is assumed to use a certain amount of memory
       simply by existing.
   2) All pg memory allocations are counted, including those before
       the process is fully initialized.
    3) Each process maintains its own local counters. These are the "truth".
     4) Periodically,
        -  local counters are added into the global, shared memory counters.
         - pgstats is updated
         - total memory is checked.

For efficiency, the global total is an approximation, not a precise number.
It can be off by as much as 1 MB per process. Memory limiting
doesn't need precision, just a consistent and reasonable approximation.

Repeating the earlier benchmark test, there is no measurable loss of performance. 
 


Attachments:

  [application/octet-stream] memtrack-v4.patch (107.8K, ../../BYAPR13MB267710BCC29F13F8BE97E643A0E5A@BYAPR13MB2677.namprd13.prod.outlook.com/2-memtrack-v4.patch)
  download | inline diff:
From 39e23b34cdfa19c0611e15bf8729086a25437714 Mon Sep 17 00:00:00 2001
From: John Morris <[email protected]>
Date: Wed, 30 Aug 2023 21:21:06 -0700
Subject: [PATCH] Rebasing memtrack changes onto recent main branch.

---
 doc/src/sgml/config.sgml                      |  30 ++
 doc/src/sgml/monitoring.sgml                  | 276 ++++++++++
 src/backend/catalog/system_views.sql          |  38 +-
 src/backend/postmaster/fork_process.c         |   7 +
 src/backend/postmaster/postmaster.c           |   6 +
 src/backend/storage/ipc/dsm.c                 |  10 +-
 src/backend/storage/ipc/dsm_impl.c            |  46 +-
 src/backend/storage/lmgr/proc.c               |  47 ++
 src/backend/utils/activity/Makefile           |   1 +
 src/backend/utils/activity/backend_status.c   |  73 ++-
 src/backend/utils/activity/memtrack.c         | 142 +++++
 src/backend/utils/activity/meson.build        |   1 +
 src/backend/utils/adt/pgstatfuncs.c           |  89 ++++
 src/backend/utils/misc/guc_tables.c           |  12 +
 src/backend/utils/misc/postgresql.conf.sample |   3 +
 src/backend/utils/mmgr/aset.c                 |  24 +-
 src/backend/utils/mmgr/generation.c           |  21 +-
 src/backend/utils/mmgr/slab.c                 |  23 +-
 src/include/catalog/pg_proc.dat               |  17 +
 src/include/storage/proc.h                    |   4 +
 src/include/utils/backend_status.h            |   8 +
 src/include/utils/memtrack.h                  | 288 ++++++++++
 src/test/modules/meson.build                  |   1 +
 src/test/modules/test_memtrack/.gitignore     |   4 +
 src/test/modules/test_memtrack/Makefile       |  25 +
 src/test/modules/test_memtrack/README         |  36 ++
 .../test_memtrack/expected/test_memtrack.out  |  80 +++
 src/test/modules/test_memtrack/meson.build    |  38 ++
 .../test_memtrack/sql/test_memtrack.sql       |  31 ++
 .../test_memtrack/test_memtrack--1.0.sql      |  20 +
 .../modules/test_memtrack/test_memtrack.c     | 496 ++++++++++++++++++
 .../modules/test_memtrack/test_memtrack.conf  |   1 +
 .../test_memtrack/test_memtrack.control       |   4 +
 src/test/modules/test_memtrack/worker_pool.c  | 416 +++++++++++++++
 src/test/modules/test_memtrack/worker_pool.h  |  69 +++
 src/test/regress/expected/rules.out           |  29 +
 src/test/regress/expected/stats.out           |  36 ++
 src/test/regress/sql/stats.sql                |  20 +
 38 files changed, 2437 insertions(+), 35 deletions(-)
 create mode 100644 src/backend/utils/activity/memtrack.c
 create mode 100644 src/include/utils/memtrack.h
 create mode 100644 src/test/modules/test_memtrack/.gitignore
 create mode 100644 src/test/modules/test_memtrack/Makefile
 create mode 100644 src/test/modules/test_memtrack/README
 create mode 100644 src/test/modules/test_memtrack/expected/test_memtrack.out
 create mode 100644 src/test/modules/test_memtrack/meson.build
 create mode 100644 src/test/modules/test_memtrack/sql/test_memtrack.sql
 create mode 100644 src/test/modules/test_memtrack/test_memtrack--1.0.sql
 create mode 100644 src/test/modules/test_memtrack/test_memtrack.c
 create mode 100644 src/test/modules/test_memtrack/test_memtrack.conf
 create mode 100644 src/test/modules/test_memtrack/test_memtrack.control
 create mode 100644 src/test/modules/test_memtrack/worker_pool.c
 create mode 100644 src/test/modules/test_memtrack/worker_pool.h

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 694d667bf9..0df7016c3f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2145,6 +2145,36 @@ include_dir 'conf.d'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-max-total-backend-memory" xreflabel="max_total_backend_memory">
+      <term><varname>max_total_backend_memory</varname> (<type>integer</type>)
+      <indexterm>
+       <primary><varname>max_total_backend_memory</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Specifies a limit to the amount of memory (MB) that may be allocated to
+        backends in total (i.e. this is not a per user or per backend limit).
+        If unset, or set to 0 it is disabled.  At databse startup
+        max_total_backend_memory is reduced by shared_memory_size_mb
+        (includes shared buffers and other memory required for initialization).
+        Each backend process is intialized with a 1MB local allowance which
+        also reduces total_bkend_mem_bytes_available. Keep this in mind when
+        setting this value. A backend request that would exhaust the limit will
+        be denied with an out of memory error causing that backend's current
+        query/transaction to fail. Further requests will not be allocated until
+        dropping below the limit.  This limit does not affect auxiliary backend
+        processes
+        <xref linkend="glossary-auxiliary-proc"/> or the postmaster process.
+        Backend memory allocations (<varname>allocated_bytes</varname>) are
+        displayed in the
+        <link linkend="monitoring-pg-stat-memory-allocation-view"><structname>pg_stat_memory_allocation</structname></link>
+        view.  Due to the dynamic nature of memory allocations, this limit is
+        not exact.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
      </sect2>
 
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 4ff415d6a0..98084a9055 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -4522,6 +4522,282 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-memory-allocation-view">
+  <title><structname>pg_stat_memory_allocation</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_memory_allocation</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_memory_allocation</structname> view will have one
+   row per server process, showing information related to the current memory
+   allocation of that process in total and by allocator type. Due to the
+   dynamic nature of memory allocations the allocated bytes values may not be
+   exact but should be sufficient for the intended purposes. Dynamic shared
+   memory allocations are included only in the value displayed for the backend
+   that created them, they are not included in the value for backends that are
+   attached to them to avoid double counting.  Use
+   <function>pg_size_pretty</function> described in
+   <xref linkend="functions-admin-dbsize"/> to make these values more easily
+   readable.
+  </para>
+
+  <table id="pg-stat-memory-allocation-view" xreflabel="pg_stat_memory_allocation">
+   <title><structname>pg_stat_memory_allocation</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>datid</structfield> <type>oid</type>
+      </para>
+      <para>
+       OID of the database this backend is connected to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>allocated_bytes</structfield> <type>bigint</type>
+      </para>
+     <para>
+      Memory currently allocated to this backend in bytes. This is the balance
+      of bytes allocated and freed by this backend.
+     </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>aset_allocated_bytes</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Memory currently allocated to this backend in bytes via the allocation
+       set allocator.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>dsm_allocated_bytes</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Memory currently allocated to this backend in bytes via the dynamic
+       shared memory allocator. Upon process exit, dsm allocations that have
+       not been freed are considered long lived and added to
+       <structfield>global_dsm_allocated_bytes</structfield> found in the
+       <link linkend="monitoring-pg-stat-global-memory-allocation-view">
+       <structname>pg_stat_global_memory_allocation</structname></link> view.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>generation_allocated_bytes</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Memory currently allocated to this backend in bytes via the generation
+       allocator.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>slab_allocated_bytes</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Memory currently allocated to this backend in bytes via the slab
+       allocator.
+      </para></entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+
+ </sect2>
+
+ <sect2 id="monitoring-pg-stat-global-memory-allocation-view">
+  <title><structname>pg_stat_global_memory_allocation</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_global-memory_allocation</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_global_memory_allocation</structname> view will
+   have one row showing information related to current shared memory
+   allocations. Due to the dynamic nature of memory allocations the allocated
+   bytes values may not be exact but should be sufficient for the intended
+   purposes. Use <function>pg_size_pretty</function> described in
+   <xref linkend="functions-admin-dbsize"/> to make the byte populated values
+   more easily readable.
+  </para>
+
+  <table id="pg-stat-global-memory-allocation-view" xreflabel="pg_stat_global_memory_allocation">
+   <title><structname>pg_stat_global_memory_allocation</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>datid</structfield> <type>oid</type>
+      </para>
+      <para>
+       OID of the database this backend is connected to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>shared_memory_size_mb</structfield> <type>integer</type>
+      </para>
+      <para>
+       Reports the size of the main shared memory area, rounded up to the
+       nearest megabyte. See <xref linkend="guc-shared-memory-size"/>.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>shared_memory_size_in_huge_pages</structfield> <type>bigint</type>
+      </para>
+     <para>
+      Reports the number of huge pages that are needed for the main shared
+      memory area based on the specified huge_page_size. If huge pages are not
+      supported, this will be -1. See
+      <xref linkend="guc-shared-memory-size-in-huge-pages"/>.
+     </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>max_total_backend_memory_bytes</structfield> <type>bigint</type>
+      </para>
+     <para>
+      Reports the user defined backend maximum allowed shared memory in bytes.
+      0 if disabled or not set. See
+      <xref linkend="guc-max-total-backend-memory"/>.
+     </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_bkend_mem_bytes_available</structfield> <type>bigint</type>
+      </para>
+     <para>
+      Tracks max_total_backend_memory (in bytes) available for allocation. At
+      database startup, total_bkend_mem_bytes_available is reduced by the
+      byte equivalent of shared_memory_size_mb. Each backend process is
+      intialized with a 1MB local allowance which also reduces
+      total_bkend_mem_bytes_available. A process's allocation requests reduce
+      it's local allowance. If a process's allocation request exceeds it's
+      remaining allowance, an attempt is made to refill the local allowance
+      from total_bkend_mem_bytes_available. If the refill request fails, then
+      the requesting process will fail with an out of memory error resulting
+      in the cancellation of that process's active query/transaction.  The
+      default refill allocation quantity is 1MB.  If a request is greater than
+      1MB, an attempt will be made to allocate the full amount. If
+      max_total_backend_memory is disabled, this will be -1.
+      <xref linkend="guc-max-total-backend-memory"/>.
+     </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>global_dsm_allocated_bytes</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Long lived dynamically allocated memory currently allocated to the
+       database. Upon process exit, dsm allocations that have not been freed
+       are considered long lived and added to
+       <structfield>global_dsm_allocated_bytes</structfield>.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_aset_allocated_bytes</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Sum total of <structfield>aset_allocated_bytes</structfield> for all
+       backend processes from
+       <link linkend="monitoring-pg-stat-memory-allocation-view">
+       <structname>pg_stat_memory_allocation</structname></link> view.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_dsm_allocated_bytes</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Sum total of <structfield>dsm_allocated_bytes</structfield> for all
+       backend processes from
+       <link linkend="monitoring-pg-stat-memory-allocation-view">
+       <structname>pg_stat_memory_allocation</structname></link> view.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_generation_allocated_bytes</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Sum total of <structfield>generation_allocated_bytes</structfield> for
+       all backend processes from
+       <link linkend="monitoring-pg-stat-memory-allocation-view">
+       <structname>pg_stat_memory_allocation</structname></link> view.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>total_slab_allocated_bytes</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Sum total of <structfield>slab_allocated_bytes</structfield> for all
+       backend processes from
+       <link linkend="monitoring-pg-stat-memory-allocation-view">
+       <structname>pg_stat_memory_allocation</structname></link> view.
+      </para></entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+
+ </sect2>
+
  <sect2 id="monitoring-stats-functions">
   <title>Statistics Functions</title>
 
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 77b06e2a7a..364ead785e 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1343,5 +1343,41 @@ CREATE VIEW pg_stat_subscription_stats AS
     FROM pg_subscription as s,
          pg_stat_get_subscription_stats(s.oid) as ss;
 
+CREATE VIEW pg_stat_memory_allocation AS
+    SELECT
+        S.datid AS datid,
+        S.pid,
+        S.allocated_bytes,
+        S.aset_allocated_bytes,
+        S.dsm_allocated_bytes,
+        S.generation_allocated_bytes,
+        S.slab_allocated_bytes
+    FROM pg_stat_get_memory_allocation(NULL) AS S
+        LEFT JOIN pg_database AS D ON (S.datid = D.oid);
+
 CREATE VIEW pg_wait_events AS
-    SELECT * FROM pg_get_wait_events();
+    SELECT * FRom pg_get_wait_events();
+
+CREATE VIEW pg_stat_global_memory_allocation AS
+WITH sums AS (
+    SELECT
+        SUM(aset_allocated_bytes) AS total_aset_allocated_bytes,
+        SUM(dsm_allocated_bytes) AS total_dsm_allocated_bytes,
+        SUM(generation_allocated_bytes) AS total_generation_allocated_bytes,
+        SUM(slab_allocated_bytes) AS total_slab_allocated_bytes
+    FROM
+        pg_stat_memory_allocation
+)
+SELECT
+        S.datid AS datid,
+        current_setting('shared_memory_size', true) as shared_memory_size,
+        (current_setting('shared_memory_size_in_huge_pages', true))::integer as shared_memory_size_in_huge_pages,
+        pg_size_bytes(current_setting('max_total_backend_memory', true)) as max_total_backend_memory_bytes,
+        S.total_bkend_mem_bytes_available,
+        S.global_dsm_allocated_bytes,
+        sums.total_aset_allocated_bytes,
+        sums.total_dsm_allocated_bytes,
+        sums.total_generation_allocated_bytes,
+        sums.total_slab_allocated_bytes
+    FROM sums, pg_stat_get_global_memory_allocation() AS S
+        LEFT JOIN pg_database AS D ON (S.datid = D.oid);
diff --git a/src/backend/postmaster/fork_process.c b/src/backend/postmaster/fork_process.c
index 6f9c2765d6..b7fbd31e4f 100644
--- a/src/backend/postmaster/fork_process.c
+++ b/src/backend/postmaster/fork_process.c
@@ -20,6 +20,7 @@
 
 #include "libpq/pqsignal.h"
 #include "postmaster/fork_process.h"
+#include "utils/memtrack.h"
 
 #ifndef WIN32
 /*
@@ -111,6 +112,12 @@ fork_process(void)
 			}
 		}
 
+		/*
+		 * Reset memory tracking to the initial state.
+		 * (They are corrrect on exec() but not after fork())
+		 */
+		init_backend_memory();
+
 		/* do post-fork initialization for random number generation */
 		pg_strong_random_init();
 	}
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index d7bfb28ff3..794bec2672 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -538,6 +538,7 @@ typedef struct
 #endif
 	char		my_exec_path[MAXPGPATH];
 	char		pkglib_path[MAXPGPATH];
+	int			max_total_bkend_mem;
 } BackendParameters;
 
 static void read_backend_variables(char *id, Port *port);
@@ -6097,6 +6098,8 @@ save_backend_variables(BackendParameters *param, Port *port,
 
 	strlcpy(param->pkglib_path, pkglib_path, MAXPGPATH);
 
+	param->max_total_bkend_mem = max_total_bkend_mem;
+
 	return true;
 }
 
@@ -6325,6 +6328,9 @@ restore_backend_variables(BackendParameters *param, Port *port)
 
 	strlcpy(pkglib_path, param->pkglib_path, MAXPGPATH);
 
+	max_total_bkend_mem = param->max_total_bkend_mem;
+	max_global_bkend_bytes = (uint64)max_total_bkend_mem * 1024 * 1024;
+
 	/*
 	 * We need to restore fd.c's counts of externally-opened FDs; to avoid
 	 * confusion, be sure to do this after restoring max_safe_fds.  (Note:
diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c
index 10b029bb16..8ef1a5d0c3 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -775,6 +775,14 @@ dsm_detach_all(void)
 void
 dsm_detach(dsm_segment *seg)
 {
+	/*
+	 * Retain mapped_size to pass into destroy call in cases where the detach
+	 * is the last reference. mapped_size is zeroed as part of the detach
+	 * process, but is needed later in these cases for dsm_allocated_bytes
+	 * accounting.
+	 */
+	Size		save_mapped_size = seg->mapped_size;
+
 	/*
 	 * Invoke registered callbacks.  Just in case one of those callbacks
 	 * throws a further error that brings us back here, pop the callback
@@ -855,7 +863,7 @@ dsm_detach(dsm_segment *seg)
 			 */
 			if (is_main_region_dsm_handle(seg->handle) ||
 				dsm_impl_op(DSM_OP_DESTROY, seg->handle, 0, &seg->impl_private,
-							&seg->mapped_address, &seg->mapped_size, WARNING))
+							&seg->mapped_address, &save_mapped_size, WARNING))
 			{
 				LWLockAcquire(DynamicSharedMemoryControlLock, LW_EXCLUSIVE);
 				if (is_main_region_dsm_handle(seg->handle))
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index 35fa910d6f..437a9d3d67 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -160,37 +160,67 @@ dsm_impl_op(dsm_op op, dsm_handle handle, Size request_size,
 			void **impl_private, void **mapped_address, Size *mapped_size,
 			int elevel)
 {
+	bool success;
+	int64 save_mapped_size =  *mapped_size;
+
 	Assert(op == DSM_OP_CREATE || request_size == 0);
 	Assert((op != DSM_OP_CREATE && op != DSM_OP_ATTACH) ||
 		   (*mapped_address == NULL && *mapped_size == 0));
 
+	/* Try to reserve memory for the backend if we're creating a new segment. */
+	if (op == DSM_OP_CREATE && !reserve_backend_memory(request_size, PG_ALLOC_DSM))
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+					errmsg("Unable to reserve backend memory for dynamic shared memory segment.")));
+		return false;
+	}
+
 	switch (dynamic_shared_memory_type)
 	{
 #ifdef USE_DSM_POSIX
 		case DSM_IMPL_POSIX:
-			return dsm_impl_posix(op, handle, request_size, impl_private,
-								  mapped_address, mapped_size, elevel);
+			success = dsm_impl_posix(op, handle, request_size, impl_private,
+									 mapped_address, mapped_size, elevel);
+			break;
 #endif
 #ifdef USE_DSM_SYSV
 		case DSM_IMPL_SYSV:
-			return dsm_impl_sysv(op, handle, request_size, impl_private,
-								 mapped_address, mapped_size, elevel);
+			success = dsm_impl_sysv(op, handle, request_size, impl_private,
+									mapped_address, mapped_size, elevel);
+			break;
 #endif
 #ifdef USE_DSM_WINDOWS
 		case DSM_IMPL_WINDOWS:
-			return dsm_impl_windows(op, handle, request_size, impl_private,
+			success = dsm_impl_windows(op, handle, request_size, impl_private,
 									mapped_address, mapped_size, elevel);
+			break
 #endif
 #ifdef USE_DSM_MMAP
 		case DSM_IMPL_MMAP:
-			return dsm_impl_mmap(op, handle, request_size, impl_private,
-								 mapped_address, mapped_size, elevel);
+			success = dsm_impl_mmap(op, handle, request_size, impl_private,
+									mapped_address, mapped_size, elevel);
+			break;
 #endif
 		default:
 			elog(ERROR, "unexpected dynamic shared memory type: %d",
 				 dynamic_shared_memory_type);
-			return false;
+			success = false;
 	}
+
+	/* CASE: we failed to create a new segment.  Release the reservation */
+	if ( !success && op == DSM_OP_CREATE )
+		release_backend_memory(request_size, PG_ALLOC_DSM);
+
+	/* CASE: we created a different size than expected. Record the change, but don't abort */
+	else if (success && op == DSM_OP_CREATE && *mapped_size != request_size)
+		update_local_allocation(*mapped_size - request_size, PG_ALLOC_DSM);
+
+	/* CASE: we destroyed a segment. Release the reservation */
+	else if (success && op == DSM_OP_DESTROY)
+		release_backend_memory(save_mapped_size, PG_ALLOC_DSM);
+
+	return success;
 }
 
 #ifdef USE_DSM_POSIX
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 5b663a2997..d966707792 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -51,6 +51,7 @@
 #include "storage/procsignal.h"
 #include "storage/spin.h"
 #include "storage/standby.h"
+#include "utils/guc.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 
@@ -180,6 +181,52 @@ InitProcGlobal(void)
 	ProcGlobal->checkpointerLatch = NULL;
 	pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO);
 	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO);
+    pg_atomic_init_u64(&ProcGlobal->total_bkend_mem_bytes, 0);
+	pg_atomic_init_u64(&ProcGlobal->global_dsm_allocation, 0);
+
+	/* Setup backend memory limiting if configured */
+	if (max_total_bkend_mem > 0)
+	{
+		/*
+		 * Convert max_total_bkend_mem to bytes, account for
+		 * shared_memory_size, and initialize total_bkend_mem_bytes.
+		 */
+		int			result = 0;
+
+		/* Get integer value of shared_memory_size */
+		if (parse_int(GetConfigOption("shared_memory_size", true, false), &result, 0, NULL))
+		{
+			/*
+			 * Error on startup if backend memory limit is less than shared
+			 * memory size. Warn on startup if backend memory available is
+			 * less than arbitrarily picked value of 100MB.
+			 */
+
+            elog(WARNING, "proc init: max_total=%d  result=%d\n", max_total_bkend_mem, result);
+
+			if (max_total_bkend_mem <= result)
+			{
+				ereport(ERROR,
+						errmsg("configured max_total_backend_memory %dMB is <= shared_memory_size %dMB",
+							   max_total_bkend_mem, result),
+						errhint("Disable or increase the configuration parameter \"max_total_backend_memory\"."));
+			}
+			else if (max_total_bkend_mem < result + 100)
+			{
+				ereport(WARNING,
+						errmsg("max_total_backend_memory %dMB - shared_memory_size %dMB is <= 100MB",
+							   max_total_bkend_mem, result),
+						errhint("Consider increasing the configuration parameter \"max_total_backend_memory\"."));
+			}
+
+			/*
+			 * We would like to use it as bytes rather than MB.
+			 */
+            max_total_bkend_bytes = (uint64)max_total_bkend_mem * 1024 * 1024;
+		}
+		else
+			ereport(ERROR, errmsg("max_total_backend_memory initialization is unable to parse shared_memory_size"));
+	}
 
 	/*
 	 * Create and initialize all the PGPROC structures we'll need.  There are
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index f57cf3958c..0e0051e34a 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -18,6 +18,7 @@ override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
 OBJS = \
 	backend_progress.o \
 	backend_status.o \
+	memtrack.o \
 	pgstat.o \
 	pgstat_archiver.o \
 	pgstat_bgwriter.o \
diff --git a/src/backend/utils/activity/backend_status.c b/src/backend/utils/activity/backend_status.c
index 722c5acf38..aa398f3ac0 100644
--- a/src/backend/utils/activity/backend_status.c
+++ b/src/backend/utils/activity/backend_status.c
@@ -24,6 +24,7 @@
 #include "utils/backend_status.h"
 #include "utils/guc.h"			/* for application_name */
 #include "utils/memutils.h"
+#include "utils/memtrack.h"
 
 
 /* ----------
@@ -49,7 +50,6 @@ int			pgstat_track_activity_query_size = 1024;
 /* exposed so that backend_progress.c can access it */
 PgBackendStatus *MyBEEntry = NULL;
 
-
 static PgBackendStatus *BackendStatusArray = NULL;
 static char *BackendAppnameBuffer = NULL;
 static char *BackendClientHostnameBuffer = NULL;
@@ -62,7 +62,6 @@ static PgBackendSSLStatus *BackendSslStatusBuffer = NULL;
 static PgBackendGSSStatus *BackendGssStatusBuffer = NULL;
 #endif
 
-
 /* Status for backends including auxiliary */
 static LocalPgBackendStatus *localBackendStatusTable = NULL;
 
@@ -71,7 +70,6 @@ static int	localNumBackends = 0;
 
 static MemoryContext backendStatusSnapContext;
 
-
 static void pgstat_beshutdown_hook(int code, Datum arg);
 static void pgstat_read_current_status(void);
 static void pgstat_setup_backend_status_context(void);
@@ -401,6 +399,9 @@ pgstat_bestart(void)
 	lbeentry.st_progress_command_target = InvalidOid;
 	lbeentry.st_query_id = UINT64CONST(0);
 
+	/* No allocations have been reported yet  */
+	lbeentry.st_memory = NO_BACKEND_MEMORY;
+
 	/*
 	 * we don't zero st_progress_param here to save cycles; nobody should
 	 * examine it until st_progress_command has been set to something other
@@ -471,6 +472,9 @@ pgstat_beshutdown_hook(int code, Datum arg)
 
 	PGSTAT_END_WRITE_ACTIVITY(beentry);
 
+	/* Stop reporting memory allocation changes to shared memory */
+	exit_backend_memory();
+
 	/* so that functions can check if backend_status.c is up via MyBEEntry */
 	MyBEEntry = NULL;
 }
@@ -1153,6 +1157,69 @@ pgstat_get_local_beentry_by_index(int idx)
 	return &localBackendStatusTable[idx - 1];
 }
 
+/* ----------
+ * pgstat_fetch_stat_beentry() -
+ *
+ *	Support function for the SQL-callable pgstat* functions. Returns
+ *	our local copy of the current-activity entry for one backend,
+ *	or NULL if the given beid doesn't identify any known session.
+ *
+ *	The beid argument is the BackendId of the desired session
+ *	(note that this is unlike pgstat_fetch_stat_local_beentry()).
+ *
+ *	NB: caller is responsible for a check if the user is permitted to see
+ *	this info (especially the querystring).
+ * ----------
+ */
+PgBackendStatus *
+pgstat_fetch_stat_beentry(BackendId beid)
+{
+	LocalPgBackendStatus key;
+	LocalPgBackendStatus *ret;
+
+	pgstat_read_current_status();
+
+	/*
+	 * Since the localBackendStatusTable is in order by backend_id, we can use
+	 * bsearch() to search it efficiently.
+	 */
+	key.backend_id = beid;
+	ret = (LocalPgBackendStatus *) bsearch(&key, localBackendStatusTable,
+										   localNumBackends,
+										   sizeof(LocalPgBackendStatus),
+										   cmp_lbestatus);
+	if (ret)
+		return &ret->backendStatus;
+
+	return NULL;
+}
+
+
+/* ----------
+ * pgstat_fetch_stat_local_beentry() -
+ *
+ *	Like pgstat_fetch_stat_beentry() but with locally computed additions (like
+ *	xid and xmin values of the backend)
+ *
+ *	The beid argument is a 1-based index in the localBackendStatusTable
+ *	(note that this is unlike pgstat_fetch_stat_beentry()).
+ *	Returns NULL if the argument is out of range (no current caller does that).
+ *
+ *	NB: caller is responsible for a check if the user is permitted to see
+ *	this info (especially the querystring).
+ * ----------
+ */
+LocalPgBackendStatus *
+pgstat_fetch_stat_local_beentry(int beid)
+{
+	pgstat_read_current_status();
+
+	if (beid < 1 || beid > localNumBackends)
+		return NULL;
+
+	return &localBackendStatusTable[beid - 1];
+}
+
 
 /* ----------
  * pgstat_fetch_stat_numbackends() -
diff --git a/src/backend/utils/activity/memtrack.c b/src/backend/utils/activity/memtrack.c
new file mode 100644
index 0000000000..73df1d916e
--- /dev/null
+++ b/src/backend/utils/activity/memtrack.c
@@ -0,0 +1,142 @@
+
+#include <unistd.h>
+
+#include "postgres.h"
+#include "miscadmin.h"
+#include "utils/backend_status.h"
+#include "utils/memtrack.h"
+#include "storage/proc.h"
+
+/*
+ * Max backend memory allocation allowed (MB). 0 = disabled.
+ * Max backend bytes is the same but in bytes.
+ * These default to "0", meaning don't check bounds for total memory.
+ */
+int			max_total_bkend_mem = 0;
+int64       max_total_bkend_bytes = 0;
+
+/*
+ * Local variables for tracking memory use.
+ * These values are preset so memory tracking is active on startup.
+ * After a fork(), they must be reset using 'init_backend_memory()'.
+ */
+PgBackendMemoryStatus  my_memory = INIT_BACKEND_MEMORY;
+PgBackendMemoryStatus  reported_memory = NO_BACKEND_MEMORY;
+int64      allocation_lower_bound = 0;
+int64      allocation_upper_bound = 0;
+
+/* ---------
+ * init_backend_memory() -
+ *
+ * Called immediately after a fork().
+ * Resets local memory counters to their initial startup values
+ */
+void
+init_backend_memory(void)
+{
+	debug("\n");
+	/* Start with nothing allocated. */
+	my_memory = INIT_BACKEND_MEMORY;
+	reported_memory = NO_BACKEND_MEMORY;
+
+	/* Force the next allocation to do global bounds checking. */
+	allocation_lower_bound = 0;
+	allocation_upper_bound = 0;
+}
+
+/*
+ * Clean up memory counters as backend is exiting.
+ *
+ * DSM memory is not automatically returned, so it persists in the counters.
+ * All other memory will disappear, so those counters are set to zero.
+ *
+ * Ideally, this function would be called last, but in practice there are some
+ * late memory releases that happen after it is called.
+ */
+void
+exit_backend_memory(void)
+{
+	debug("\n");
+	/*
+	 * Release non-dsm memory.
+	 * We don't release dsm shared memory since it survives process exit.
+	 */
+	for (int type = 0; type < PG_ALLOC_TYPE_MAX; type++)
+		if (type != PG_ALLOC_DSM)
+			release_backend_memory(my_memory.allocated_bytes_by_type[type], type);
+
+	/* Force the final values to be posted to shmem */
+	update_global_allocation(0, PG_ALLOC_OTHER);
+
+	/* If we get a late request, send it to the long path. */
+	allocation_lower_bound = 0;
+	allocation_upper_bound = 0;
+}
+
+
+/*
+ * Update backend memory allocation for a new request.
+ *
+ * There are two versions of this function. This one, which updates
+ * global values in shared memory, and an optimized update_local_allocation()
+ * which only updates local values.
+ *
+ * This version is the "slow path". We invoke it periodically to update
+ * global values and pgstat statistics.
+ */
+bool update_global_allocation(int64 size, pg_allocator_type type)
+{
+	int64 new_allocated_bytes;
+	int64 dsm_delta;
+
+	/* If we are still initializing, ignore the request. It should be part of initial allocation. */
+	if (ProcGlobal == NULL || MyProcPid == 0)
+		return update_local_allocation(size, type);
+
+	debug("size=%ld type=%d\n", size, type);
+
+	/* Calculate new number of bytes reflecting the reservation/release */
+	new_allocated_bytes = my_memory.allocated_bytes + size;
+
+	/* If reserving new memory and we are limited by max_total_bkend ... */
+	if (size > 0 && max_total_bkend_bytes > 0 && MyAuxProcType == NotAnAuxProcess && MyProcPid != PostmasterPid)
+	{
+		/* Update the global total memory counter subject to the upper limit. */
+		if (!atomic_add_within_bounds_i64(&ProcGlobal->total_bkend_mem_bytes,
+										  new_allocated_bytes - reported_memory.allocated_bytes,
+										  0, max_total_bkend_bytes))
+			return false;
+	}
+
+		/* Otherwise, update the global counter with no limit checking */
+	else
+		pg_atomic_add_fetch_u64(&ProcGlobal->total_bkend_mem_bytes,
+								new_allocated_bytes - reported_memory.allocated_bytes);
+
+	/* Update the local memory counters. This must happen after bounds checking */
+	update_local_allocation(size, type);
+
+	/* Update the global dsm memory counter to reflect changes since our last report */
+	dsm_delta = my_memory.allocated_bytes_by_type[PG_ALLOC_DSM] - reported_memory.allocated_bytes_by_type[PG_ALLOC_DSM];
+	pg_atomic_add_fetch_u64(&ProcGlobal->global_dsm_allocation, dsm_delta);
+
+	/* Update pgstat statistics if we are initialized as a backend process. */
+	if (MyBEEntry != NULL)
+	{
+		PGSTAT_BEGIN_WRITE_ACTIVITY(MyBEEntry);
+		MyBEEntry->st_memory = my_memory;
+		PGSTAT_END_WRITE_ACTIVITY(MyBEEntry);
+	}
+
+	/* Remember the values we just reported to pgstat */
+	reported_memory = my_memory;
+
+	/* Update bounds so they bracket our new allocation size. */
+	allocation_upper_bound = my_memory.allocated_bytes + allocation_allowance_refill_qty;
+	allocation_lower_bound = my_memory.allocated_bytes - allocation_allowance_refill_qty;
+
+	Assert((int64)pg_atomic_read_u64(&ProcGlobal->total_bkend_mem_bytes) >= 0);
+	Assert((int64)pg_atomic_read_u64(&ProcGlobal->global_dsm_allocation) >= 0);
+
+	return true;
+}
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index 46a27e7548..d3db919704 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -3,6 +3,7 @@
 backend_sources += files(
   'backend_progress.c',
   'backend_status.c',
+  'memtrack.c',
   'pgstat.c',
   'pgstat_archiver.c',
   'pgstat_bgwriter.c',
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index dd5094a2d4..1f5aac422b 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2067,3 +2067,92 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objoid));
 }
+
+/*
+ * Get the memory allocation of PG backends.
+ */
+Datum
+pg_stat_get_memory_allocation(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_MEMORY_ALLOCATION_COLS	7
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_MEMORY_ALLOCATION_COLS] = {0};
+		bool		nulls[PG_STAT_GET_MEMORY_ALLOCATION_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* Values available to all callers */
+		if (beentry->st_databaseid != InvalidOid)
+			values[0] = ObjectIdGetDatum(beentry->st_databaseid);
+		else
+			nulls[0] = true;
+
+		values[1] = Int32GetDatum(beentry->st_procpid);
+		values[2] = UInt64GetDatum(beentry->st_memory.allocated_bytes);
+		values[3] = UInt64GetDatum(beentry->st_memory.allocated_bytes_by_type[PG_ALLOC_ASET]);
+		values[4] = UInt64GetDatum(beentry->st_memory.allocated_bytes_by_type[PG_ALLOC_DSM]);
+		values[5] = UInt64GetDatum(beentry->st_memory.allocated_bytes_by_type[PG_ALLOC_GENERATION]);
+		values[6] = UInt64GetDatum(beentry->st_memory.allocated_bytes_by_type[PG_ALLOC_SLAB]);
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
+
+/*
+ * Get the global memory allocation statistics.
+ */
+Datum
+pg_stat_get_global_memory_allocation(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_GLOBAL_MEMORY_ALLOCATION_COLS	3
+	TupleDesc	tupdesc;
+	Datum		values[PG_STAT_GET_GLOBAL_MEMORY_ALLOCATION_COLS] = {0};
+	bool		nulls[PG_STAT_GET_GLOBAL_MEMORY_ALLOCATION_COLS] = {0};
+	volatile PROC_HDR *procglobal = ProcGlobal;
+
+	/* Initialise attributes information in the tuple descriptor */
+	tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_GLOBAL_MEMORY_ALLOCATION_COLS);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "datid",
+					   OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "total_bkend_mem_bytes_available",
+					   INT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "global_dsm_allocated_bytes",
+					   INT8OID, -1, 0);
+	BlessTupleDesc(tupdesc);
+
+	/* datid */
+	values[0] = ObjectIdGetDatum(MyDatabaseId);
+
+	/* Get total_bkend_mem_bytes */
+	values[1] = Int64GetDatum(pg_atomic_read_u64(&procglobal->total_bkend_mem_bytes));
+
+	/* Get global_dsm_allocated_bytes */
+	values[2] = Int64GetDatum(pg_atomic_read_u64(&procglobal->global_dsm_allocation));
+
+	/* Returns the record as Datum */
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index e565a3092f..9d2dea3948 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -77,6 +77,7 @@
 #include "utils/float.h"
 #include "utils/guc_hooks.h"
 #include "utils/guc_tables.h"
+#include "utils/memtrack.h"
 #include "utils/memutils.h"
 #include "utils/pg_locale.h"
 #include "utils/portal.h"
@@ -3507,6 +3508,17 @@ struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"max_total_backend_memory", PGC_SU_BACKEND, RESOURCES_MEM,
+			gettext_noop("Restrict total backend memory allocations to this max."),
+			gettext_noop("0 turns this feature off."),
+			GUC_UNIT_MB
+		},
+		&max_total_bkend_mem,
+		0, 0, INT_MAX,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c768af9a73..bb75e2756a 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -160,6 +160,9 @@
 #vacuum_buffer_usage_limit = 256kB	# size of vacuum and analyze buffer access strategy ring;
 					# 0 to disable vacuum buffer access strategy;
 					# range 128kB to 16GB
+#max_total_backend_memory = 0MB		# Restrict total backend memory allocations
+									# to this max (in MB). 0 turns this feature
+									# off.
 
 # - Disk -
 
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index c3affaf5a8..fe8ed8a712 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -48,6 +48,7 @@
 
 #include "port/pg_bitutils.h"
 #include "utils/memdebug.h"
+#include "utils/memtrack.h"
 #include "utils/memutils.h"
 #include "utils/memutils_memorychunk.h"
 #include "utils/memutils_internal.h"
@@ -441,7 +442,7 @@ AllocSetContextCreateInternal(MemoryContext parent,
 	 * Allocate the initial block.  Unlike other aset.c blocks, it starts with
 	 * the context header and its block header follows that.
 	 */
-	set = (AllocSet) malloc(firstBlockSize);
+	set = (AllocSet) malloc_backend(firstBlockSize, PG_ALLOC_ASET);
 	if (set == NULL)
 	{
 		if (TopMemoryContext)
@@ -539,6 +540,7 @@ AllocSetReset(MemoryContext context)
 	AllocSet	set = (AllocSet) context;
 	AllocBlock	block;
 	Size		keepersize PG_USED_FOR_ASSERTS_ONLY;
+	uint64		deallocation = 0;
 
 	Assert(AllocSetIsValid(set));
 
@@ -581,6 +583,7 @@ AllocSetReset(MemoryContext context)
 		{
 			/* Normal case, release the block */
 			context->mem_allocated -= block->endptr - ((char *) block);
+			deallocation += block->endptr - ((char *) block);
 
 #ifdef CLOBBER_FREED_MEMORY
 			wipe_mem(block, block->freeptr - ((char *) block));
@@ -591,6 +594,7 @@ AllocSetReset(MemoryContext context)
 	}
 
 	Assert(context->mem_allocated == keepersize);
+	release_backend_memory(deallocation, PG_ALLOC_ASET);
 
 	/* Reset block size allocation sequence, too */
 	set->nextBlockSize = set->initBlockSize;
@@ -609,6 +613,7 @@ AllocSetDelete(MemoryContext context)
 	AllocSet	set = (AllocSet) context;
 	AllocBlock	block = set->blocks;
 	Size		keepersize PG_USED_FOR_ASSERTS_ONLY;
+	uint64		deallocation = 0;
 
 	Assert(AllocSetIsValid(set));
 
@@ -647,11 +652,13 @@ AllocSetDelete(MemoryContext context)
 
 				freelist->first_free = (AllocSetContext *) oldset->header.nextchild;
 				freelist->num_free--;
+				deallocation += oldset->header.mem_allocated;
 
 				/* All that remains is to free the header/initial block */
 				free(oldset);
 			}
 			Assert(freelist->num_free == 0);
+			release_backend_memory(deallocation, PG_ALLOC_ASET);
 		}
 
 		/* Now add the just-deleted context to the freelist. */
@@ -668,7 +675,10 @@ AllocSetDelete(MemoryContext context)
 		AllocBlock	next = block->next;
 
 		if (!IsKeeperBlock(set, block))
+		{
 			context->mem_allocated -= block->endptr - ((char *) block);
+			deallocation += block->endptr - ((char *) block);
+		}
 
 #ifdef CLOBBER_FREED_MEMORY
 		wipe_mem(block, block->freeptr - ((char *) block));
@@ -681,6 +691,7 @@ AllocSetDelete(MemoryContext context)
 	}
 
 	Assert(context->mem_allocated == keepersize);
+	release_backend_memory(deallocation + context->mem_allocated, PG_ALLOC_ASET);
 
 	/* Finally, free the context header, including the keeper block */
 	free(set);
@@ -725,7 +736,7 @@ AllocSetAlloc(MemoryContext context, Size size)
 #endif
 
 		blksize = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
-		block = (AllocBlock) malloc(blksize);
+		block = (AllocBlock) malloc_backend(blksize, PG_ALLOC_ASET);
 		if (block == NULL)
 			return NULL;
 
@@ -925,7 +936,7 @@ AllocSetAlloc(MemoryContext context, Size size)
 			blksize <<= 1;
 
 		/* Try to allocate it */
-		block = (AllocBlock) malloc(blksize);
+		block = (AllocBlock) malloc_backend(blksize, PG_ALLOC_ASET);
 
 		/*
 		 * We could be asking for pretty big blocks here, so cope if malloc
@@ -936,7 +947,7 @@ AllocSetAlloc(MemoryContext context, Size size)
 			blksize >>= 1;
 			if (blksize < required_size)
 				break;
-			block = (AllocBlock) malloc(blksize);
+			block = (AllocBlock) malloc_backend(blksize, PG_ALLOC_ASET);
 		}
 
 		if (block == NULL)
@@ -1044,7 +1055,7 @@ AllocSetFree(void *pointer)
 #ifdef CLOBBER_FREED_MEMORY
 		wipe_mem(block, block->freeptr - ((char *) block));
 #endif
-		free(block);
+		free_backend(block, block->endptr - ((char *) block), PG_ALLOC_ASET);
 	}
 	else
 	{
@@ -1159,8 +1170,7 @@ AllocSetRealloc(void *pointer, Size size)
 		/* Do the realloc */
 		blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
 		oldblksize = block->endptr - ((char *) block);
-
-		block = (AllocBlock) realloc(block, blksize);
+		block = (AllocBlock) realloc_backend(block, blksize, oldblksize, PG_ALLOC_ASET);
 		if (block == NULL)
 		{
 			/* Disallow access to the chunk header. */
diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c
index 92401ccf73..b2bc39b472 100644
--- a/src/backend/utils/mmgr/generation.c
+++ b/src/backend/utils/mmgr/generation.c
@@ -37,6 +37,7 @@
 
 #include "lib/ilist.h"
 #include "port/pg_bitutils.h"
+#include "utils/backend_status.h"
 #include "utils/memdebug.h"
 #include "utils/memutils.h"
 #include "utils/memutils_memorychunk.h"
@@ -203,7 +204,7 @@ GenerationContextCreate(MemoryContext parent,
 	 * Allocate the initial block.  Unlike other generation.c blocks, it
 	 * starts with the context header and its block header follows that.
 	 */
-	set = (GenerationContext *) malloc(allocSize);
+	set = (GenerationContext *) malloc_backend(allocSize, PG_ALLOC_GENERATION);
 	if (set == NULL)
 	{
 		MemoryContextStats(TopMemoryContext);
@@ -279,6 +280,7 @@ GenerationReset(MemoryContext context)
 {
 	GenerationContext *set = (GenerationContext *) context;
 	dlist_mutable_iter miter;
+	uint64		deallocation = 0;
 
 	Assert(GenerationIsValid(set));
 
@@ -301,9 +303,14 @@ GenerationReset(MemoryContext context)
 		if (IsKeeperBlock(set, block))
 			GenerationBlockMarkEmpty(block);
 		else
+		{
+			deallocation += block->blksize;
 			GenerationBlockFree(set, block);
+		}
 	}
 
+	release_backend_memory(deallocation, PG_ALLOC_GENERATION);
+
 	/* set it so new allocations to make use of the keeper block */
 	set->block = KeeperBlock(set);
 
@@ -322,10 +329,12 @@ GenerationReset(MemoryContext context)
 void
 GenerationDelete(MemoryContext context)
 {
+
 	/* Reset to release all releasable GenerationBlocks */
 	GenerationReset(context);
+
 	/* And free the context header and keeper block */
-	free(context);
+	free_backend(context, MAXALIGN(sizeof(GenerationContext)) + context->mem_allocated, PG_ALLOC_GENERATION);
 }
 
 /*
@@ -365,7 +374,7 @@ GenerationAlloc(MemoryContext context, Size size)
 	{
 		Size		blksize = required_size + Generation_BLOCKHDRSZ;
 
-		block = (GenerationBlock *) malloc(blksize);
+		block = (GenerationBlock *) malloc_backend(blksize, PG_ALLOC_GENERATION);
 		if (block == NULL)
 			return NULL;
 
@@ -467,8 +476,7 @@ GenerationAlloc(MemoryContext context, Size size)
 			if (blksize < required_size)
 				blksize = pg_nextpower2_size_t(required_size);
 
-			block = (GenerationBlock *) malloc(blksize);
-
+			block = (GenerationBlock *) malloc_backend(blksize, PG_ALLOC_GENERATION);
 			if (block == NULL)
 				return NULL;
 
@@ -723,9 +731,8 @@ GenerationFree(void *pointer)
 	 * list of blocks, then return it to malloc().
 	 */
 	dlist_delete(&block->node);
-
 	set->header.mem_allocated -= block->blksize;
-	free(block);
+	free_backend(block, block->blksize, PG_ALLOC_GENERATION);
 }
 
 /*
diff --git a/src/backend/utils/mmgr/slab.c b/src/backend/utils/mmgr/slab.c
index 40c1d401c4..436be421fb 100644
--- a/src/backend/utils/mmgr/slab.c
+++ b/src/backend/utils/mmgr/slab.c
@@ -69,6 +69,7 @@
 #include "postgres.h"
 
 #include "lib/ilist.h"
+#include "utils/backend_status.h"
 #include "utils/memdebug.h"
 #include "utils/memutils.h"
 #include "utils/memutils_memorychunk.h"
@@ -359,9 +360,7 @@ SlabContextCreate(MemoryContext parent,
 		elog(ERROR, "block size %zu for slab is too small for %zu-byte chunks",
 			 blockSize, chunkSize);
 
-
-
-	slab = (SlabContext *) malloc(Slab_CONTEXT_HDRSZ(chunksPerBlock));
+	slab = (SlabContext *) malloc_backend(Slab_CONTEXT_HDRSZ(chunksPerBlock), PG_ALLOC_SLAB);
 	if (slab == NULL)
 	{
 		MemoryContextStats(TopMemoryContext);
@@ -433,6 +432,7 @@ SlabReset(MemoryContext context)
 	SlabContext *slab = (SlabContext *) context;
 	dlist_mutable_iter miter;
 	int			i;
+	uint64		deallocation = 0;
 
 	Assert(SlabIsValid(slab));
 
@@ -453,6 +453,7 @@ SlabReset(MemoryContext context)
 #endif
 		free(block);
 		context->mem_allocated -= slab->blockSize;
+		deallocation += slab->blockSize;
 	}
 
 	/* walk over blocklist and free the blocks */
@@ -469,9 +470,11 @@ SlabReset(MemoryContext context)
 #endif
 			free(block);
 			context->mem_allocated -= slab->blockSize;
+			deallocation += slab->blockSize;
 		}
 	}
 
+	release_backend_memory(deallocation, PG_ALLOC_SLAB);
 	slab->curBlocklistIndex = 0;
 
 	Assert(context->mem_allocated == 0);
@@ -486,8 +489,13 @@ SlabDelete(MemoryContext context)
 {
 	/* Reset to release all the SlabBlocks */
 	SlabReset(context);
-	/* And free the context header */
-	free(context);
+
+	/*
+	 * Until context header allocation is included in context->mem_allocated,
+	 * cast to slab and decrement the header allocation
+	 */
+	free_backend(context, Slab_CONTEXT_HDRSZ(((SlabContext *) context)->chunksPerBlock),
+				 PG_ALLOC_SLAB);
 }
 
 /*
@@ -543,8 +551,7 @@ SlabAlloc(MemoryContext context, Size size)
 		}
 		else
 		{
-			block = (SlabBlock *) malloc(slab->blockSize);
-
+			block = (SlabBlock *) malloc_backend(slab->blockSize, PG_ALLOC_SLAB);
 			if (unlikely(block == NULL))
 				return NULL;
 
@@ -742,7 +749,7 @@ SlabFree(void *pointer)
 #ifdef CLOBBER_FREED_MEMORY
 			wipe_mem(block, slab->blockSize);
 #endif
-			free(block);
+			free_backend(block, slab->blockSize, PG_ALLOC_SLAB);
 			slab->header.mem_allocated -= slab->blockSize;
 		}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..7a7f9ac1e2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5420,6 +5420,23 @@
   proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't',
   provolatile => 's', proparallel => 'r', prorettype => 'int4',
   proargtypes => '', prosrc => 'pg_stat_get_backend_idset' },
+{ oid => '9890',
+  descr => 'statistics: memory allocation information for backends',
+  proname => 'pg_stat_get_memory_allocation', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,oid,int4,int8,int8,int8,int8,int8}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{pid,datid,pid,allocated_bytes,aset_allocated_bytes,dsm_allocated_bytes,generation_allocated_bytes,slab_allocated_bytes}',
+  prosrc => 'pg_stat_get_memory_allocation' },
+{ oid => '9891',
+  descr => 'statistics: global memory allocation information',
+  proname => 'pg_stat_get_global_memory_allocation', proisstrict => 'f',
+  provolatile => 's', proparallel => 'r', prorettype => 'record',
+  proargtypes => '', proallargtypes => '{oid,int8,int8}',
+  proargmodes => '{o,o,o}',
+  proargnames => '{datid,total_bkend_mem_bytes_available,global_dsm_allocated_bytes}',
+  prosrc =>'pg_stat_get_global_memory_allocation' },
 { oid => '2022',
   descr => 'statistics: information about currently active backends',
   proname => 'pg_stat_get_activity', prorows => '100', proisstrict => 'f',
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ef74f32693..b3a2322328 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -404,6 +404,10 @@ typedef struct PROC_HDR
 	int			spins_per_delay;
 	/* Buffer id of the buffer that Startup process waits for pin on, or -1 */
 	int			startupBufferPinWaitBufId;
+	/* Global dsm allocations */
+	pg_atomic_uint64 global_dsm_allocation;
+	/* Track how much memory the backends have allocated */
+	pg_atomic_uint64 total_bkend_mem_bytes;
 } PROC_HDR;
 
 extern PGDLLIMPORT PROC_HDR *ProcGlobal;
diff --git a/src/include/utils/backend_status.h b/src/include/utils/backend_status.h
index d51c840daf..5ec310c769 100644
--- a/src/include/utils/backend_status.h
+++ b/src/include/utils/backend_status.h
@@ -10,11 +10,13 @@
 #ifndef BACKEND_STATUS_H
 #define BACKEND_STATUS_H
 
+#include "postgres.h"
 #include "datatype/timestamp.h"
 #include "libpq/pqcomm.h"
 #include "miscadmin.h"			/* for BackendType */
 #include "storage/backendid.h"
 #include "utils/backend_progress.h"
+#include "utils/memtrack.h"
 
 
 /* ----------
@@ -170,6 +172,10 @@ typedef struct PgBackendStatus
 
 	/* query identifier, optionally computed using post_parse_analyze_hook */
 	uint64		st_query_id;
+
+	/* Memory allocated to this backend, both total and subtotals by type. */
+	PgBackendMemoryStatus st_memory;
+
 } PgBackendStatus;
 
 
@@ -332,6 +338,8 @@ extern uint64 pgstat_get_my_query_id(void);
  * generate the pgstat* views.
  * ----------
  */
+extern LocalPgBackendStatus *pgstat_fetch_stat_local_beentry(int beid);
+extern PgBackendStatus *pgstat_fetch_stat_beentry(int beid);
 extern int	pgstat_fetch_stat_numbackends(void);
 extern PgBackendStatus *pgstat_get_beentry_by_backend_id(BackendId beid);
 extern LocalPgBackendStatus *pgstat_get_local_beentry_by_backend_id(BackendId beid);
diff --git a/src/include/utils/memtrack.h b/src/include/utils/memtrack.h
new file mode 100644
index 0000000000..12b56cbf58
--- /dev/null
+++ b/src/include/utils/memtrack.h
@@ -0,0 +1,288 @@
+
+#ifndef MEMTRACK_H
+#define MEMTRACK_H
+
+/* ----------
+ * Backend memory accounting functions.
+ *   Track how much memory each backend is using
+ *   and place a cluster-wide limit on the total amount of backend memory.
+ *
+ * The main functions are:
+ *     init_backend_memory()
+ *     reserve_backend_memory()
+ *     release_backend_memory()
+ *     exit_backend_memory()
+ *
+ * All local variables are properly initialized at startup, so init_backend_memory()
+ * only needs to be called after a fork() system call.
+ *
+ * The reserve/release functions implement both a "fast path" and a "slow path".
+ * The fast path is used for most allocations, and it only references
+ * local variables. The slow path is invoked periodically; it updates
+ * shared memory and checks for limits on total backend memory.
+ *
+ * The following local variables represent the "TRUTH" of this backend's memory allocations.
+ *   my_memory.allocated_bytes:               total amount of memory allocated by this backend.
+ *   my_memory.allocated_bytes_by_type[type]: subtotals by allocator type.
+ *
+ * The local values are periodically reported to pgstgt.
+ * The following variables hold the last reported values
+ *    reported_memory.allocated_bytes
+ *    reported_memory.allocated_bytes_by_type[type]:
+ *
+ * The "slow path" is invoked when my_memory.allocate_bytes exceeds these bounds.
+ * Once invokoked, it updates the reported values and sets new bounds.
+ *   allocation_upper_bound:          update when my_memory.allocated_bytes exceeds this
+ *   allocation_lower_bound:          update when my_memory.allocated_bytes drops below this
+ *   allocation_allowance_refill_qty  amount of memory to allocate or release before updating again.
+ *
+ * These counters are the values seen  by pgstat. They are a copy of reported_memory.
+ *   proc->st_memory.allocated_bytes:               last total reported to pgstat
+ *   proc->st_memory.allocated_bytes_by_type[type]: last reported subtotals reported to pgstat
+ *
+ * Limits on total backend memory. If max_total_bkend_bytes is zero, there is no limit.
+ *   ProcGlobal->total_bkend_mem_bytes:       total amount of memory reserved by all backends, including shared memory
+ *   max_total_bkend_bytes:                   maximum amount of memory allowed to be reserved by all backends.
+ *
+ * And finally,
+ *   initial_allocation_allowance:            each backend consumes this much memory simply by existing.
+ *   ProcGlobal->global_dsm_allocated_bytes:  total amount of shared memory allocated by all backends.
+ * ----------
+ */
+
+#include <unistd.h>
+#include "postgres.h"
+#include "port/atomics.h"
+
+/*
+ * Define a debug macro which becomes noop() when debug is disabled.
+ */
+#define debug(args...)  (void)0
+
+
+/* Various types of memory allocators we are tracking. */
+typedef enum pg_allocator_type
+{
+	PG_ALLOC_OTHER = 0,    /* Not tracked, but part of total memory */
+	PG_ALLOC_ASET,         /* Allocation Set           */
+	PG_ALLOC_DSM,          /* Dynamic shared memory    */
+	PG_ALLOC_GENERATION,   /* Generation Context (all freed at once) */
+	PG_ALLOC_SLAB,         /* Slab allocator 		 */
+	PG_ALLOC_TYPE_MAX,     /* (Last, for array sizing) */
+} pg_allocator_type;
+
+
+/*
+ * PgBackendMemoryStatus
+ *
+ * For each backend, track how much memory has been allocated.
+ * Note may be possible to have negative values, say if one backend
+ * creates DSM segments and another backend destroys them.
+ */
+typedef struct PgBackendMemoryStatus
+{
+	int64		allocated_bytes;
+	int64       allocated_bytes_by_type[PG_ALLOC_TYPE_MAX];
+} PgBackendMemoryStatus;
+
+
+/* These values are candidates for GUC variables.  We chose 1MV arbitrarily. */
+static const int64 initial_allocation_allowance = 1024 * 1024;  /* 1MB */
+static const int64 allocation_allowance_refill_qty = 1024 * 1024;  /* 1MB */
+
+/* Compile time initialization constants */
+#define INIT_BACKEND_MEMORY (PgBackendMemoryStatus) \
+	{initial_allocation_allowance, {initial_allocation_allowance}}
+#define NO_BACKEND_MEMORY (PgBackendMemoryStatus) \
+	{0, {0}}
+
+/* Manage memory allocation for backends. */
+extern PGDLLIMPORT PgBackendMemoryStatus my_memory;
+extern PGDLLIMPORT PgBackendMemoryStatus reported_memory;
+extern PGDLLIMPORT int64 allocation_upper_bound;
+extern PGDLLIMPORT int64 allocation_lower_bound;
+
+extern PGDLLIMPORT int64 max_total_bkend_bytes;
+extern PGDLLIMPORT int32 max_total_bkend_mem;
+
+/* These are the main entry points for backend memory accounting */
+extern void init_backend_memory(void);
+static inline bool reserve_backend_memory(int64 size, pg_allocator_type type);
+static inline void release_backend_memory(int64 size, pg_allocator_type type);
+extern void exit_backend_memory(void);
+
+/* Helper functions for backend memory accounting */
+static inline bool update_local_allocation(int64 size, pg_allocator_type type);
+extern bool update_global_allocation(int64 size, pg_allocator_type type);
+
+/* ----------
+ * reserve_backend_memory() -
+ *  Called to report a desired increase in memory for this backend.
+ *  true if successful.
+ */
+static inline bool
+reserve_backend_memory(int64 size, pg_allocator_type type)
+{
+	Assert(size >= 0);
+
+	/* quick optimization */
+	if (size == 0)
+		return true;
+
+	/* CASE: the new allocation is within bounds. Take the fast path. */
+	else if (my_memory.allocated_bytes + size <= allocation_upper_bound)
+		return update_local_allocation(size, type);
+
+	/* CASE: out of bounds. Update pgstat and check memory limits */
+	else
+		return update_global_allocation(size, type);
+}
+
+/* ----------
+ * unreserve_memory() -
+ *  Called to report decrease in memory allocated for this backend.
+ *  Note we should have already called "reserve_backend_memory"
+ *  so we should never end up with a negative total allocation.
+ */
+static inline void
+release_backend_memory(int64 size, pg_allocator_type type)
+{
+	Assert(size >= 0);
+
+	/* quick optimization */
+	if (size == 0)
+		return;
+
+	/* CASE: In bounds, take the fast path */
+	else if (my_memory.allocated_bytes - size >= allocation_lower_bound)
+		update_local_allocation(-size, type);
+
+	/* CASE: Out of bounds. Update pgstat and memory totals */
+	else
+		update_global_allocation(-size, type);
+}
+
+
+/*
+* Fast path for reserving and releasing memory.
+* This version is used for most allocations, and it
+* is stripped down to the bare minimum to reduce impact
+* on performance. It only updates local variables.
+*/
+static inline bool
+update_local_allocation(int64 size, pg_allocator_type type)
+{
+	/* Update our local memory counters. */
+	my_memory.allocated_bytes += size;
+	my_memory.allocated_bytes_by_type[type] += size;
+
+	return true;
+}
+
+
+/*--------------------------------------------
+ * Convenience functions based on malloc/free
+ *------------------------------------------*/
+
+/*
+ * Reserve memory from malloc if we can.
+ */
+static inline void *
+malloc_backend(int64 size, pg_allocator_type type)
+{
+	void *ptr;
+
+	/* reserve the memory if able to */
+	if (!reserve_backend_memory(size, type))
+		return NULL;
+
+	/* Allocate the memory, releasing the reservation if failed */
+	ptr = malloc(size);
+	if (ptr == NULL)
+		release_backend_memory(size, type);
+
+	return ptr;
+}
+
+/*
+ * Free memory which was allocated with malloc_reserved.
+ * Note: most mallocs have a non-portable method to
+ * get the size of a block of memory. Dropping the "size" parameter
+ * would greatly simplify the calling code.
+ */
+static inline void
+free_backend(void *ptr, int64 size, pg_allocator_type type)
+{
+	release_backend_memory(size, type);
+	free(ptr);
+}
+
+
+/*
+ * Realloc reserved memory.
+ */
+static inline void *
+realloc_backend(void *block, int64 new_size, int64 old_size, pg_allocator_type type)
+{
+	void *ptr;
+	bool success;
+
+	/* Update the reservation to the new size */
+	release_backend_memory(old_size, type);
+	success = reserve_backend_memory(new_size, type);
+
+	/* If unable, free the old memory and return NULL */
+	if (!success)
+	{
+		free(block);
+		return NULL;
+	}
+
+	/* Now, actually resize the memory */
+	ptr = realloc(block, new_size);
+
+	/*
+	 * If unable to resize, release the allocation.
+	 * The actual memory has already been freed.
+	 */
+	if (ptr == NULL)
+		release_backend_memory(new_size, type);
+
+	return ptr;
+}
+
+
+/* True if adding a and b would overflow */
+static inline bool addition_overflow(int64 a, int64 b)
+{
+	int64 result = a + b;
+	return (a > 0 && b > 0 && result < 0) || (a < 0 && b < 0 && result > 0);
+}
+
+/*
+ * Helper function to add to an atomic sum, as long as the result is within bounds.
+ * TODO: consider moving to atomics.h
+ */
+static inline bool
+atomic_add_within_bounds_i64(pg_atomic_uint64 *ptr, int64 add, int64 lower_bound, int64 upper_bound)
+{
+	int64 oldval;
+	int64 newval;
+
+	for (;;)
+	{
+		oldval = (int64)pg_atomic_read_u64(ptr);
+		newval = oldval + add;
+
+		/* check if we are out of bounds */
+		if (newval < lower_bound || newval > upper_bound || addition_overflow(oldval, add))
+			return false;
+
+		if (pg_atomic_compare_exchange_u64(ptr, (uint64 *)&oldval, newval))
+			return true;
+	}
+}
+
+
+
+#endif //POSTGRES_IDE_MEMTRACK_H
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 17d369e378..6bdcc6dac4 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -19,6 +19,7 @@ subdir('test_extensions')
 subdir('test_ginpostinglist')
 subdir('test_integerset')
 subdir('test_lfind')
+subdir('test_memtrack')
 subdir('test_misc')
 subdir('test_oat_hooks')
 subdir('test_parser')
diff --git a/src/test/modules/test_memtrack/.gitignore b/src/test/modules/test_memtrack/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_memtrack/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_memtrack/Makefile b/src/test/modules/test_memtrack/Makefile
new file mode 100644
index 0000000000..db44ca0575
--- /dev/null
+++ b/src/test/modules/test_memtrack/Makefile
@@ -0,0 +1,25 @@
+# src/test/modules/test_memtrack/Makefile
+
+MODULE_big = test_memtrack
+OBJS = \
+	$(WIN32RES) \
+	test_memtrack.o \
+	worker_pool.o
+PGFILEDESC = "test_memtrack - example use of shared memory message queue"
+
+EXTENSION = test_memtrack
+DATA = test_memtrack--1.0.sql
+
+REGRESS = test_memtrack
+REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/test_memtrack/test_memtrack.conf
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_memtrack
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_memtrack/README b/src/test/modules/test_memtrack/README
new file mode 100644
index 0000000000..75a6c1e84a
--- /dev/null
+++ b/src/test/modules/test_memtrack/README
@@ -0,0 +1,36 @@
+test_memtrack tests the memory tracking facilities of PostgreSQL.
+
+
+Functions
+=========
+test_allocation(# workers, type of memory, # blocks, block size)
+
+    This function controls the test. It creates a pool of workers and passes them messages
+    asking them to allocate or free memory. It verifies the memory is allocated or freed,
+    and it confirms the total memory has been freed at the end of the test.
+
+    Like test_shm_queue, this function spawns worker backends which communicate
+    through message queues.
+
+    Unlike test_shm_queue, each worker is associatied with two queues, one for requests
+    and another for responses. All messages are exchanged between the main test backend
+    and the workers. The workers do not communicate with each other.
+
+test_memtrack(# workers, type of memory, # blocks, block size)
+
+    This function is like test_allocation, but it only updates the memory tracking counters
+    and does not actually allocate or free memory.
+
+
+Messages
+========
+The message sent to the workers
+    request type (ALLOCATE or FREE)
+    type of memory
+    number of blocks to allocate or free
+    size of each block
+
+The response sent back.
+    error code (0 if no error)
+    starting memory usage totals and subtotals.
+    ending memory usage totals and subtotals.
diff --git a/src/test/modules/test_memtrack/expected/test_memtrack.out b/src/test/modules/test_memtrack/expected/test_memtrack.out
new file mode 100644
index 0000000000..23e51b4e80
--- /dev/null
+++ b/src/test/modules/test_memtrack/expected/test_memtrack.out
@@ -0,0 +1,80 @@
+CREATE EXTENSION test_memtrack;
+-- Make sure we can track memory usage of a single task
+-- Since logic is the same for all memory managers, only test one.
+SELECT test_memtrack(1, 1, 0, 1024);
+ test_memtrack 
+---------------
+ 
+(1 row)
+
+-- Make sure we can track memory usage of multiple tasks.
+--  By default we are limited to 8 tasks, so stay below the limit.
+SELECT test_memtrack(5, 2, 1024, 5*1024);
+ test_memtrack 
+---------------
+ 
+(1 row)
+
+-- Do it again. We had a bug where the second call would fail.
+SELECT test_memtrack(5, 3, 1024, 5*1024);
+ test_memtrack 
+---------------
+ 
+(1 row)
+
+-- Now we're going to actualy do memory allocations.
+-- We'll test each type of memory manager, except DSM.
+-- Verify we can create and destroy contexts.
+SELECT test_allocation(1,1,0,1024);
+ test_allocation 
+-----------------
+ 
+(1 row)
+
+SELECT test_allocation(1,2,0, 1024);
+ test_allocation 
+-----------------
+ 
+(1 row)
+
+SELECT test_allocation(1,3, 0,1024);
+ test_allocation 
+-----------------
+ 
+(1 row)
+
+SELECT test_allocation(1,4,0,1024);
+ test_allocation 
+-----------------
+ 
+(1 row)
+
+-- Create and free blocks of memory.
+SELECT test_allocation(5,1,5*1024,1024);
+ test_allocation 
+-----------------
+ 
+(1 row)
+
+SELECT test_allocation(5,2,5,1024*1024);  /* Fewer, don't exceed shmem limit */
+ test_allocation 
+-----------------
+ 
+(1 row)
+
+SELECT test_allocation(5,3,5*1024,1024);
+ test_allocation 
+-----------------
+ 
+(1 row)
+
+SELECT test_allocation(5,4,5*1024,1024);
+ test_allocation 
+-----------------
+ 
+(1 row)
+
+-- Allocate more memory than we have available.
+-- (this should fail because we configured max_backend_memory to 1024 Mb)
+SELECT test_memtrack(5, 2, 1024, 1024*1024*1024);
+ERROR:  worker ran out of memory
diff --git a/src/test/modules/test_memtrack/meson.build b/src/test/modules/test_memtrack/meson.build
new file mode 100644
index 0000000000..4b861ab62f
--- /dev/null
+++ b/src/test/modules/test_memtrack/meson.build
@@ -0,0 +1,38 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+test_memtrack_sources = files(
+  'test_memtrack.c',
+  'worker_pool.c',
+)
+
+if host_system == 'windows'
+  test_memtrack_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_memtrack',
+    '--FILEDESC', 'test_memtrack - test memory tracking',])
+endif
+
+test_memtrack = shared_module('test_memtrack',
+  test_memtrack_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += test_memtrack
+
+test_install_data += files(
+  'test_memtrack.control',
+  'test_memtrack--1.0.sql',
+)
+
+tests += {
+  'name': 'test_memtrack',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'test_memtrack',
+    ],
+    'regress_args': [
+      '--temp-config', files('test_memtrack.conf'),
+    ],
+
+  },
+}
diff --git a/src/test/modules/test_memtrack/sql/test_memtrack.sql b/src/test/modules/test_memtrack/sql/test_memtrack.sql
new file mode 100644
index 0000000000..39318276ec
--- /dev/null
+++ b/src/test/modules/test_memtrack/sql/test_memtrack.sql
@@ -0,0 +1,31 @@
+CREATE EXTENSION test_memtrack;
+
+-- Make sure we can track memory usage of a single task
+-- Since logic is the same for all memory managers, only test one.
+SELECT test_memtrack(1, 1, 0, 1024);
+
+-- Make sure we can track memory usage of multiple tasks.
+--  By default we are limited to 8 tasks, so stay below the limit.
+SELECT test_memtrack(5, 2, 1024, 5*1024);
+
+-- Do it again. We had a bug where the second call would fail.
+SELECT test_memtrack(5, 3, 1024, 5*1024);
+
+-- Now we're going to actualy do memory allocations.
+-- We'll test each type of memory manager, except DSM.
+
+-- Verify we can create and destroy contexts.
+SELECT test_allocation(1,1,0,1024);
+SELECT test_allocation(1,2,0, 1024);
+SELECT test_allocation(1,3, 0,1024);
+SELECT test_allocation(1,4,0,1024);
+
+-- Create and free blocks of memory.
+SELECT test_allocation(5,1,5*1024,1024);
+SELECT test_allocation(5,2,5,1024*1024);  /* Fewer, don't exceed shmem limit */
+SELECT test_allocation(5,3,5*1024,1024);
+SELECT test_allocation(5,4,5*1024,1024);
+
+-- Allocate more memory than we have available.
+-- (this should fail because we configured max_backend_memory to 1024 Mb)
+SELECT test_memtrack(5, 2, 1024, 1024*1024*1024);
diff --git a/src/test/modules/test_memtrack/test_memtrack--1.0.sql b/src/test/modules/test_memtrack/test_memtrack--1.0.sql
new file mode 100644
index 0000000000..9a39f24ee4
--- /dev/null
+++ b/src/test/modules/test_memtrack/test_memtrack--1.0.sql
@@ -0,0 +1,20 @@
+/* src/test/modules/test_memtrack/test_memtrack--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_memtrack" to load this file. \quit
+
+CREATE FUNCTION test_memtrack(
+                       num_workers pg_catalog.int4 default 1,
+                       type   pg_catalog.int4 default 1,
+					   num_blocks pg_catalog.int4 default 1,
+					   block_size pg_catalog.int4 default 1024)
+    RETURNS pg_catalog.void STRICT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_allocation(
+    num_workers pg_catalog.int4 default 1,
+    type   pg_catalog.int4 default 1,
+    num_blocks pg_catalog.int4 default 1,
+    block_size pg_catalog.int4 default 1024)
+    RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_memtrack/test_memtrack.c b/src/test/modules/test_memtrack/test_memtrack.c
new file mode 100644
index 0000000000..e9a8a7844f
--- /dev/null
+++ b/src/test/modules/test_memtrack/test_memtrack.c
@@ -0,0 +1,496 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_memstack.c
+ *		Test harness code
+ *
+ * Copyright (c) 2013-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_memtrack/test_memtrack.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "varatt.h"
+
+#include "storage/dsm.h"
+#include "utils/backend_status.h"
+#include "utils/memutils_internal.h"
+
+#include "worker_pool.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(test_memtrack);
+PG_FUNCTION_INFO_V1(test_allocation);
+
+typedef enum {ALLOCATE, RELEASE} Action;
+typedef struct AllocateRequest
+{
+	Action action;
+	pg_allocator_type type;
+	int32     nWorkers;
+	int32     nBlocks;
+	int64     blockSize;
+} AllocateRequest;
+
+typedef struct ResponseData
+{
+	int32		errorCode;
+	PgBackendMemoryStatus  memory;
+	PgBackendMemoryStatus  startingMemory;
+} ResponseData;
+
+/* Forward references */
+static bool reserveBlocks(pg_allocator_type type, int nBlocks, int blockSize);
+static bool releaseBlocks(pg_allocator_type type, int nBlocks, int blockSize);
+static void validateArgs(int nWorkers, pg_allocator_type type, int nBlocks, int blockSize);
+static void sendRequest(WorkerPool *pool, int worker, Action action, pg_allocator_type type, int nBlocks, int blockSize);
+static void processReply(WorkerPool *pool, int worker, Action actions, pg_allocator_type type, int nBlocks, int blockSize);
+static Datum test_nonDSM(FunctionCallInfo fcinfo, char *workerFunction);
+
+/* Test the memory tracking features standalone */
+PGDLLEXPORT Datum test_memtrack(PG_FUNCTION_ARGS);
+PGDLLEXPORT void test_memtrack_worker(Datum arg);
+
+/* Test the memory tracking features with actual allocations */
+PGDLLEXPORT Datum test_allocation(PG_FUNCTION_ARGS);
+PGDLLEXPORT void test_allocation_worker(Datum arg);
+/*
+ * Test the memory tracking features.
+ */
+Datum
+test_memtrack(PG_FUNCTION_ARGS)
+{
+	return test_nonDSM(fcinfo, "test_memtrack_worker");
+}
+
+
+Datum
+test_allocation(PG_FUNCTION_ARGS)
+{
+	return test_nonDSM(fcinfo, "test_allocation_worker");
+}
+
+
+
+/****************
+ * Parent task to test the memory tracking features.
+ * Schedules a pool of workers and verifies the results.
+ * This version tests non-shared memory allocations.
+ */
+static
+Datum test_nonDSM(FunctionCallInfo fcinfo, char *workerFunction)
+{
+	WorkerPool *pool;
+	int64 delta, starting_bkend_bytes;
+	int64 expected, fudge;
+
+	/* Get  the SQL function arguments */
+	int32 nWorkers = PG_GETARG_INT32(0);
+	pg_allocator_type type = PG_GETARG_INT32(1);
+	int32 nBlocks = PG_GETARG_INT32(2);
+	int32 blockSize = PG_GETARG_INT32(3);
+
+	debug("nWorkers=%d type=%d nBlocks=%d blockSize=%d\n", nWorkers, type, nBlocks, blockSize);
+	validateArgs(nWorkers, type, nBlocks, blockSize);
+
+	/* Our global totals may be off by an allocation allowance per worker */
+	fudge = nWorkers * allocation_allowance_refill_qty * nWorkers;
+	expected = nWorkers * nBlocks * blockSize;
+
+	/* Set up pool of background workers */
+	pool = createWorkerPool(nWorkers, sizeof(AllocateRequest) * 10, sizeof(ResponseData) * 10,
+							"test_memtrack", workerFunction);
+
+	/* Remember the total memory before we start allocations */
+	starting_bkend_bytes = pg_atomic_read_u32((void *)&ProcGlobal->total_bkend_mem_bytes);
+
+	/* Tell the workers to start their first batch of allocations */
+	for (int w = 0; w < nWorkers; w++)
+	    sendRequest(pool, w, ALLOCATE, type, nBlocks, blockSize);
+
+	/* Get the workers response and verify all is good */
+	for (int w = 0; w < nWorkers; w++)
+	    processReply(pool, w, ALLOCATE, type, nBlocks, blockSize);
+
+	/* Confirm the total backend memory is greater than what we just allocated */
+	delta = pg_atomic_read_u32((void *)&ProcGlobal->total_bkend_mem_bytes) - starting_bkend_bytes;
+	debug("starting_bkend_bytes=%zd  delta=%zd\n", starting_bkend_bytes, delta);
+	if (delta < expected - fudge || delta > expected + fudge)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					errmsg("Total allocated memory %zd doesn't match expected %zd  fudge=%zd\n", delta, expected, fudge)));
+
+	/* Tell the workers to release their memory */
+	for (int w=0; w < nWorkers; w++)
+		sendRequest(pool, w, RELEASE, type, nBlocks, blockSize);
+
+	/* Get the workers response and verify all is good */
+	for (int w = 0; w < nWorkers; w++)
+		processReply(pool, w, RELEASE, type, nBlocks, blockSize);
+
+	/* Verify the new total is reasonable */
+	delta = pg_atomic_read_u32((void *)&ProcGlobal->total_bkend_mem_bytes) - starting_bkend_bytes;
+	debug("After release: delta=%zd  expected=%d\n", delta, 0);
+	if (delta < -fudge || delta > fudge)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					errmsg("Total allocated memory %zd is less than expected %zd\n", delta, 0l)));
+
+	/* Clean up */
+	freeWorkerPool(pool);
+	debug("done\n");
+	PG_RETURN_VOID();
+}
+
+
+static
+void validateArgs(int nWorkers, pg_allocator_type type, int nBlocks, int blockSize)
+{
+	/* A negative blockcount is nonsensical. */
+	if (nBlocks < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("repeat count size must be an integer value greater than or equal to zero")));
+
+	/* a minimum of 1 worker is required. */
+	if (nWorkers <= 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("number of workers must be an integer value greater than zero")));
+
+	/* block size must be > 0 */
+	if (blockSize <= 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("block size must be an integer value greater than zero")));
+
+	/* Valid type? */
+	if (type < 0 || type >= PG_ALLOC_TYPE_MAX)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("invalid allocation type")));
+}
+
+static
+void sendRequest(WorkerPool *pool, int worker, Action action, pg_allocator_type type, int nBlocks, int blockSize)
+{
+	AllocateRequest req;
+	int result;
+    debug("worker=%d action=%d type=%d nBlocks=%d blockSize=%d\n", worker, action, type, nBlocks, blockSize);
+
+	req = (AllocateRequest) {.nBlocks = nBlocks, .action = action, .type = type, .blockSize = blockSize};
+
+	result = sendToWorker(pool, worker, &req, sizeof(req));
+	if (result != SHM_MQ_SUCCESS)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					errmsg("could not send message")));
+}
+
+
+void processReply(WorkerPool *pool, int worker, Action action, pg_allocator_type type, int nBlocks, int blockSize)
+{
+        int64 delta;
+		ResponseData *resp;
+		Size len;
+		int result;
+		debug("worker=%d action=%d type=%d nBlocks=%d blockSize=%d\n", worker, action, type, nBlocks, blockSize);
+
+		/* Receive a message. Returns a pointer to the message and a length */
+		result = recvFromWorker(pool, worker, (void *)&resp, &len);
+		if (result != SHM_MQ_SUCCESS)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						errmsg("could not receive message")));
+
+		if (resp->errorCode != 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						errmsg("worker ran out of memory")));
+
+		/* Verify the totals */
+		delta = resp->memory.allocated_bytes_by_type[type] - resp->startingMemory.allocated_bytes_by_type[type];
+		debug("delta=%ld expected=%ld\n", delta, (int64)nBlocks*blockSize);
+		if (action == ALLOCATE && delta < nBlocks * blockSize)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						errmsg("worker reported %ld bytes, expected %d", resp->memory.allocated_bytes_by_type[type], nBlocks * blockSize)));
+
+		if (action == RELEASE && delta != 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						errmsg("worker reported %ld bytes, expected 0", resp->memory.allocated_bytes_by_type[type])));
+}
+
+
+
+void test_memtrack_worker(Datum arg)
+{
+	AllocateRequest *req;
+	Size reqSize;
+
+	ResponseData resp[1];
+	shm_mq_result result;
+
+	PgBackendMemoryStatus startingMemory;
+
+	debug("");
+	workerInit(arg);
+
+	/* Now that we're running, make note of how much memory has been already allocated */
+	startingMemory = my_memory;
+
+	do
+	{
+		result = workerRecv((void *)&req, &reqSize);
+		debug("Received  result=%d  action=%d type=%d nBlocks=%d  blockSize=%ld\n", result, req->action, req->type, req->nBlocks, req->blockSize);
+		if (result != SHM_MQ_SUCCESS)
+			break;
+
+		/* Allocate or release the blocks */
+		if (req->action == ALLOCATE && reserveBlocks(req->type, req->nBlocks, req->blockSize))
+			resp->errorCode = 0;
+		else if (req->action == RELEASE && releaseBlocks(req->type, req->nBlocks, req->blockSize))
+			resp->errorCode = 0;
+		else
+			resp->errorCode = 1;
+
+		/* Get the current memory totals */
+		resp->memory = my_memory;
+		resp->startingMemory = startingMemory;
+		debug("MyBEEntry=%p  allocated_bytes=%ld  type=%d\n", MyBEEntry, my_memory.allocated_bytes_by_type[req->type], req->type);
+
+		/* Send the response */
+		result = workerSend(resp, sizeof(resp[1]));
+		debug("Send errorCode=%d  result=%d\n", resp->errorCode, result);
+
+	} while (result == SHM_MQ_SUCCESS);
+
+	workerExit(0);
+}
+
+
+bool reserveBlocks(pg_allocator_type type, int nBlocks, int blockSize)
+{
+    debug("type=%d nBlocks=%d blockSize=%d  prev=%zd\n", type, nBlocks, blockSize, my_memory.allocated_bytes_by_type[type]);
+	/* Allocate the requested number of blocks */
+	for (int i = 0; i < nBlocks; i++)
+		if (!reserve_backend_memory(blockSize, type))
+			return false;
+
+	debug("success: allocated_bytes=%zd\n", my_memory.allocated_bytes_by_type[type]);
+	return true;
+}
+
+
+bool releaseBlocks(pg_allocator_type type, int nBlocks, int blockSize)
+{
+	debug("type=%d nBlocks=%d blockSize=%d\n", type, nBlocks, blockSize);
+    for (int i = 0; i < nBlocks; i++)
+	    release_backend_memory(blockSize, type);
+
+	return true;
+}
+
+
+
+/***************
+ * Worker which allocates and frees blocks,
+ * but this one does actual allocations.
+ */
+
+/* Forward references for the allocation worker */
+static bool freeBlocks(pg_allocator_type type, int nBlocks, int blockSize);
+static MemoryContext createTestContext(pg_allocator_type type);
+static bool allocateBlocks(pg_allocator_type type, int nBlocks, int blockSize);
+static bool allocateDSMBlocks(int nBlocks, int blockSize);
+static bool freeDSMBlocks(int nBlocks, int blockSize);
+static bool allocateContextBlocks(pg_allocator_type type, int nBlocks, int blockSize);
+static bool freeContextBlocks(pg_allocator_type type, int nBlocks, int blockSize);
+
+
+/* An array of pointers to the allocated blocks */
+static void **allocations;
+static MemoryContext testContext;
+
+
+void test_allocation_worker(Datum arg)
+{
+	AllocateRequest *req;
+	Size reqSize;
+
+	ResponseData resp[1];
+	shm_mq_result result;
+
+	PgBackendMemoryStatus startingMemory;
+
+	debug("\n");
+	workerInit(arg);
+
+	/* Now that we're running, make note of how much memory has been already allocated */
+	startingMemory = my_memory;
+
+	do
+	{
+		result = workerRecv((void *)&req, &reqSize);
+		debug("Received  result=%d  action=%d type=%d nBlocks=%d  blockSize=%ld\n", result, req->action, req->type, req->nBlocks, req->blockSize);
+		if (result != SHM_MQ_SUCCESS)
+			break;
+
+		/* Allocate or release the blocks */
+		if (req->action == ALLOCATE && allocateBlocks(req->type, req->nBlocks, req->blockSize))
+			resp->errorCode = 0;
+		else if (req->action == RELEASE && freeBlocks(req->type, req->nBlocks, req->blockSize))
+			resp->errorCode = 0;
+		else
+			resp->errorCode = 1;
+
+		/* Get the current memory totals */
+		resp->memory = my_memory;
+		resp->startingMemory = startingMemory;
+		debug("MyBEEntry=%p  allocated_bytes=%ld  type=%d\n", MyBEEntry, my_memory.allocated_bytes_by_type[req->type], req->type);
+
+		/* Send the response */
+		result = workerSend(resp, sizeof(resp[1]));
+
+	} while (result == SHM_MQ_SUCCESS);
+
+	workerExit(0);
+}
+
+
+
+static bool allocateBlocks(pg_allocator_type type, int nBlocks, int blockSize)
+{
+	debug("type=%d nBlocks=%d blockSize=%d  prev=%zd\n", type, nBlocks, blockSize, my_memory.allocated_bytes_by_type[type]);
+	if (type == PG_ALLOC_DSM)
+		return allocateDSMBlocks(nBlocks, blockSize);
+	else
+		return allocateContextBlocks(type, nBlocks, blockSize);
+}
+
+static bool freeBlocks(pg_allocator_type type, int nBlocks, int blockSize)
+{
+	debug("type=%d nBlocks=%d blockSize=%d\n", type, nBlocks, blockSize);
+	if (type == PG_ALLOC_DSM)
+		return freeDSMBlocks(nBlocks, blockSize);
+	else
+		return freeContextBlocks(type, nBlocks, blockSize);
+}
+
+
+
+
+static
+MemoryContext createTestContext(pg_allocator_type type)
+{
+	switch (type)
+	{
+		case PG_ALLOC_ASET:
+			return AllocSetContextCreate(TopMemoryContext, "test_aset", 64, 1024, 16 * 1024);
+		case PG_ALLOC_SLAB:
+			return SlabContextCreate(TopMemoryContext, "test_slab", 16 * 1024, 1024);
+		case PG_ALLOC_GENERATION:
+			return GenerationContextCreate(TopMemoryContext, "test_gen", 64, 1024, 16 * 1024);
+		default: return NULL;
+
+	}
+}
+
+
+static
+bool allocateContextBlocks(pg_allocator_type type, int nBlocks, int blockSize)
+{
+	MemoryContext old;
+
+	debug("type=%d nBlocks=%d blockSize=%d  prev=%zd\n", type, nBlocks, blockSize, my_memory.allocated_bytes_by_type[type]);
+	if (type == PG_ALLOC_DSM || type == PG_ALLOC_OTHER)
+	    return false;
+
+	/* Create a memory context for the allocations */
+    testContext = createTestContext(type);
+	if (testContext == NULL)
+		return false;
+
+	/* Switch to the new context */
+	old = MemoryContextSwitchTo(testContext);
+
+	/* Create a list of block pointers - not in the context */
+	allocations = malloc(sizeof(void *) * nBlocks);
+	if (allocations == NULL)
+		return false;
+
+	/* Allocate the requested number of blocks */
+	for (int i = 0; i < nBlocks; i++)
+	{
+		allocations[i] = palloc(blockSize);
+		if (allocations[i] == NULL)
+			return false;
+	}
+
+	/* Switch back to the old context */
+	MemoryContextSwitchTo(old);
+
+	debug("success: allocated_bytes=%zd\n", my_memory.allocated_bytes_by_type[type]);
+	return true;
+	}
+
+
+static
+bool freeContextBlocks(pg_allocator_type type, int nBlocks, int blockSize)
+{
+	debug("type=%d nBlocks=%d blockSize=%d\n", type, nBlocks, blockSize);
+	for (int i = 0; i < nBlocks; i++)
+		pfree(allocations[i]);
+
+	MemoryContextDelete(testContext);
+	free(allocations);
+	allocations = NULL;
+
+	return true;
+}
+
+
+
+
+static bool allocateDSMBlocks(int nBlocks, int blockSize)
+{
+	debug("nBlocks=%d blockSize=%d\n", nBlocks, blockSize);
+
+	/* Create a list of block pointers - not in the context */
+	allocations = malloc(sizeof(void *) * nBlocks);
+	if (allocations == NULL)
+		return false;
+
+	for (int i = 0; i < nBlocks; i++)
+	{
+		allocations[i] = dsm_create(blockSize, 0);
+		if (allocations[i] == NULL)
+			return false;
+		debug("segment=%p size=%ld\n", allocations[i], dsm_segment_map_length(allocations[i]));
+	}
+
+	return true;
+}
+
+
+static bool freeDSMBlocks(int nBlocks, int blockSize)
+{
+	debug("nBlocks=%d blockSize=%d\n", nBlocks, blockSize);
+
+	for (int i = 0; i < nBlocks; i++)
+		dsm_detach(allocations[i]);
+
+	free(allocations);
+	allocations = NULL;
+
+
+	return true;
+}
diff --git a/src/test/modules/test_memtrack/test_memtrack.conf b/src/test/modules/test_memtrack/test_memtrack.conf
new file mode 100644
index 0000000000..256347e92c
--- /dev/null
+++ b/src/test/modules/test_memtrack/test_memtrack.conf
@@ -0,0 +1 @@
+max_total_backend_memory = 8192
diff --git a/src/test/modules/test_memtrack/test_memtrack.control b/src/test/modules/test_memtrack/test_memtrack.control
new file mode 100644
index 0000000000..548109e313
--- /dev/null
+++ b/src/test/modules/test_memtrack/test_memtrack.control
@@ -0,0 +1,4 @@
+comment = 'Test code for PostgreSQL memory tracking'
+default_version = '1.0'
+module_pathname = '$libdir/test_memtrack'
+relocatable = true
diff --git a/src/test/modules/test_memtrack/worker_pool.c b/src/test/modules/test_memtrack/worker_pool.c
new file mode 100644
index 0000000000..8e2810a3a4
--- /dev/null
+++ b/src/test/modules/test_memtrack/worker_pool.c
@@ -0,0 +1,416 @@
+//
+// Created by John Morris on 8/18/23.
+//
+
+
+#include "postgres.h"
+#include "storage/s_lock.h"
+#include "postmaster/bgworker.h"
+#include "storage/dsm.h"
+#include "storage/shm_toc.h"
+#include "tcop/tcopprot.h"
+#include "storage/spin.h"
+#include "storage/procarray.h"
+#include "storage/ipc.h"
+#include "storage/latch.h"
+#include "worker_pool.h"
+#include "utils/memutils.h"
+#include "miscadmin.h"
+#include "utils/memtrack.h"  /* for debug() */
+#include "utils/wait_event_types.h"
+#include "storage/barrier.h"
+
+
+#define WORKER_POOL_MAGIC 0x7843732
+
+typedef struct WorkerPoolStartup WorkerPoolStartup;
+
+
+struct WorkerPool
+{
+	dsm_segment *seg;
+	shm_toc     *toc;
+	int         nWorkers;
+	MemQue      *inQ;
+	MemQue      *outQ;
+	BackgroundWorkerHandle **handle;
+	WorkerPoolStartup *hdr;
+};
+
+
+/*
+ * This structure is stored in the dynamic shared memory segment at toc(0).
+ * We use it to assign worker numbers to each worker.
+ */
+struct WorkerPoolStartup
+{
+	uint32           nWorkers;
+	pg_atomic_uint32 nextWorker;
+	Barrier          barrier[1];
+};
+
+/* Forward references */
+static int64 estimateDsmSize(int nWorkers, int inSize, int outSize);
+static void cleanupWorkers(dsm_segment *seg, Datum arg);
+static MemQue attachToQueue(dsm_segment *seg, shm_toc *toc, int workerIdx, int queueIdx, bool isSender);
+
+/*
+ * Create a new pool of backend workers.
+ */
+WorkerPool *createWorkerPool(int nWorkers, int inSize, int outSize, char *libName, char *procName)
+{
+	WorkerPool *pool;
+	int64  dsmSize;
+	MemoryContext oldcontext;
+	BackgroundWorker worker;
+	pid_t pid;
+
+	debug("createWorkerPool: nWorkers=%d, inSize=%d, outSize=%d\n", nWorkers, inSize, outSize);
+
+	/*
+	 * We need the worker pool objects to allocated in CurTransactionContext
+	 * rather than ExprContext; otherwise, they'll be destroyed before the on_dsm_detach
+	 * hooks run.
+	 */
+	oldcontext = MemoryContextSwitchTo(CurTransactionContext);
+
+	/* Create local worker pool object and allocate arrays for the headers and queues. */
+	pool = MemoryContextAlloc(TopTransactionContext, sizeof(WorkerPool));
+	pool->nWorkers = nWorkers;
+	pool->inQ = MemoryContextAlloc(TopTransactionContext, sizeof(MemQue) * nWorkers);
+	pool->outQ = MemoryContextAlloc(TopTransactionContext, sizeof(MemQue) * nWorkers);
+	pool->handle = MemoryContextAlloc(TopTransactionContext, sizeof(BackgroundWorkerHandle *) * nWorkers);
+
+	/* Estimate the size of the shared memory and allocate shared memory */
+	dsmSize = estimateDsmSize(nWorkers, inSize, outSize);
+	pool->seg = dsm_create(dsmSize, 0);
+
+	/* Create table of contents in dsm so we can access contents */
+	pool->toc = shm_toc_create(WORKER_POOL_MAGIC, dsm_segment_address(pool->seg), dsmSize);
+
+	/* Set up the startup header as region 0 */
+	pool->hdr = shm_toc_allocate(pool->toc, sizeof(WorkerPoolStartup));
+	shm_toc_insert(pool->toc, 0, pool->hdr);
+	pool->hdr->nWorkers = nWorkers;
+	pg_atomic_init_u32(&pool->hdr->nextWorker, 0);
+	BarrierInit(pool->hdr->barrier, nWorkers+1);
+
+	/* Create memory queues for each worker */
+	for (int w = 0; w < nWorkers; w++)
+	{
+		shm_mq *mqIn, *mqOut;
+
+		/* Allocate the "In" queue */
+		mqIn = shm_toc_allocate(pool->toc, shm_mq_minimum_size + inSize);
+		shm_toc_insert(pool->toc, 1 + 2 * w, mqIn);
+		mqIn = shm_mq_create(mqIn, shm_mq_minimum_size + inSize);
+		shm_mq_set_sender(mqIn, MyProc);
+
+		/* Allocate the "Out" queue */
+		mqOut = shm_toc_allocate(pool->toc, shm_mq_minimum_size + outSize);
+		shm_toc_insert(pool->toc, 2 + 2 * w, mqOut);
+		mqOut = shm_mq_create(mqOut, shm_mq_minimum_size + outSize);
+		shm_mq_set_receiver(mqOut, MyProc);
+	}
+
+	/*
+	 * Arrange to kill all the workers if we abort before all workers are
+	 * finished hooking themselves up to the dynamic shared memory segment.
+	 *
+	 * If we die after all the workers have finished hooking themselves up to
+	 * the dynamic shared memory segment, we'll mark the two queues to which
+	 * we're directly connected as detached, and the worker(s) connected to
+	 * those queues will exit, marking any other queues to which they are
+	 * connected as detached.  This will cause any as-yet-unaware workers
+	 * connected to those queues to exit in their turn, and so on, until
+	 * everybody exits.
+	 *
+	 * But suppose the workers which are supposed to connect to the queues to
+	 * which we're directly attached exit due to some error before theynin
+	 * actually attach the queues.  The remaining workers will have no way of
+	 * knowing this.  From their perspective, they're still waiting for those
+	 * workers to start, when in fact they've already died.
+	 */
+	debug("configuring on_detach:  seg=%p  fn=%p  pool=%ld\n", pool->seg, cleanupWorkers, PointerGetDatum(pool));
+	on_dsm_detach(pool->seg, cleanupWorkers, PointerGetDatum(pool));
+
+
+	/* Configure a prototypical worker. */
+	worker = (BackgroundWorker)
+	{
+		.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_CLASS_PARALLEL,
+		.bgw_start_time = BgWorkerStart_ConsistentState,
+		.bgw_restart_time = BGW_NEVER_RESTART,
+		.bgw_notify_pid = MyProcPid,
+		.bgw_main_arg = UInt32GetDatum(dsm_segment_handle(pool->seg)),
+	};
+	strlcpy(worker.bgw_library_name, libName, sizeof(worker.bgw_library_name));
+	strlcpy(worker.bgw_function_name, procName, sizeof(worker.bgw_function_name));
+	snprintf(worker.bgw_type, sizeof(worker.bgw_type), "%s worker", libName);
+	snprintf(worker.bgw_name, sizeof(worker.bgw_name), "%s/%s worker for [%d]", libName, procName, MyProcPid);
+	debug("lib=%s proc=%s .bgw_main_arg=%d\n", libName, procName, DatumGetInt32(worker.bgw_main_arg));
+
+	/* Do for each worker */
+	for (int w = 0; w < nWorkers; w++)
+	{
+		/* Create the worker */
+		if (!RegisterDynamicBackgroundWorker(&worker, &pool->handle[w]))
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+						errmsg("could not register background process"),
+						errhint("You may need to increase max_worker_processes.")));
+
+		/* Attach the worker's memory queues */
+		pool->inQ[w] = attachToQueue(pool->seg, pool->toc, w, 0, true);
+		pool->outQ[w] = attachToQueue(pool->seg, pool->toc, w, 1, false);
+	}
+
+	/*
+	 * Wait for workers to become ready.
+	 * We could just wait on the barrier, but if a worker fails to reach
+	 * the barrier, we would end up waiting forever.
+	 */
+	for (int w = 0; w < nWorkers; w++)
+		if (WaitForBackgroundWorkerStartup(pool->handle[w], &pid) != BGWH_STARTED)
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+						errmsg("could not start background process"),
+						errhint("You may need to increase max_worker_processes.")));
+
+	/* Wait for workers to attach to the shared memory segment */
+	debug("Leader waiting on barrier\n");
+	BarrierArriveAndWait(pool->hdr->barrier, 0);
+
+	/*
+	 * Once we reach this point, all workers are ready.  We no longer need to
+	 * kill them if we die; they'll die on their own as the message queues
+	 * shut down.  For now, kill workers on detach anyway, just to be safe.
+	 */
+	//cancel_on_dsm_detach(pool->seg, cleanupWorkers, PointerGetDatum(pool));
+
+	/* Resume using the original memory context */
+	MemoryContextSwitchTo(oldcontext);
+
+	return pool;
+}
+
+
+shm_mq_result sendToWorker(WorkerPool *pool, int workerIdx, void *msg, Size len)
+{
+	shm_mq_result result;
+	debug("workerIdx=%d, len=%zd\n", workerIdx, len);
+
+	result = shm_mq_send(pool->inQ[workerIdx], len, msg, true, true);
+
+	return result;
+}
+
+shm_mq_result recvFromWorker(WorkerPool *pool, int workerIdx, void**msg, Size *len)
+{
+	shm_mq_result result;
+
+	debug("waiting workerIdx=%d\n", workerIdx);
+	result = shm_mq_receive(pool->outQ[workerIdx], len, msg, false);
+	debug("received result=%d workerIdx=%d, len=%zd\n", result, workerIdx, *len);
+
+	return result;
+}
+
+void freeWorkerPool(WorkerPool *pool)
+{
+	/* Only free the pool once. (possibly reentrant) */
+    dsm_segment *seg = pool->seg;
+	pool->seg = NULL;
+	debug("pool=%p seg=%p\n", pool, seg);
+	if (seg == NULL)
+		return;
+
+	/* Terminate the background workers */
+	cleanupWorkers(pool->seg, PointerGetDatum(pool));
+
+	/* Detach from the message queues */
+	for (int w = 0; w < pool->nWorkers; w++)
+	{
+		shm_mq_detach(pool->inQ[w]);
+		shm_mq_detach(pool->outQ[w]);
+	}
+
+    /* Detach and destroy the shared memory segment (if we haven't already) */
+	dsm_detach(seg);
+
+	/* Free the pool object */
+	pfree(pool->inQ);
+	pfree(pool->outQ);
+	pfree(pool->handle);
+	pfree(pool);
+}
+
+
+/* Wrapper to remove workers when detaching from dsm */
+static void cleanupWorkers(dsm_segment *seg, Datum arg)
+{
+	WorkerPool *pool = (WorkerPool *)DatumGetPointer(arg);
+	debug("seg=%p pool=%p\n", seg, pool);
+	for (int w = 0; w < pool->nWorkers; w++)
+		TerminateBackgroundWorker(pool->handle[w]);
+	for (int w = 0; w < pool->nWorkers; w++)
+		WaitForBackgroundWorkerShutdown(pool->handle[w]);
+}
+
+
+/*
+ * Estimate how much shared memory we need for thw pool of workers.
+ *
+ * Because the TOC machinery may choose to insert padding of oddly-sized
+ * requests, we must estimate each chunk separately.
+ *
+ * We need one key to register the location of the header, and we need
+ * nworkers * 2 keys to track the locations of the message queues.
+ */
+static
+int64 estimateDsmSize(int nWorkers, int inSize, int outSize)
+{
+	shm_toc_estimator e[1];
+
+	shm_toc_initialize_estimator(e);
+
+	shm_toc_estimate_keys(e, 1 + 2 * nWorkers);
+
+	shm_toc_estimate_chunk(e, sizeof(WorkerPoolStartup));
+
+	for (int w = 0; w < nWorkers; w++)
+	{
+		shm_toc_estimate_chunk(e, shm_mq_minimum_size + inSize);
+		shm_toc_estimate_chunk(e, shm_mq_minimum_size + outSize);
+	}
+
+	return shm_toc_estimate(e);
+}
+
+
+
+/*
+ * ----------------------------------------------------------------
+ *	Worker process code.
+ * ----------------------------------------------------------------
+ */
+
+static dsm_segment *seg;
+static shm_toc *toc;
+
+static int myWorkerNumber;
+static MemQue inQ;
+static MemQue outQ;
+static WorkerPoolStartup *hdr;
+
+shm_mq_result  workerRecv(void **msg, Size *msgSize)
+{
+	debug("MyWorkerNumber=%d\n", myWorkerNumber);
+	return shm_mq_receive(inQ, msgSize, msg, false);
+}
+
+shm_mq_result  workerSend(void *msg, Size msgSize)
+{
+	debug("MyWorkerNumber=%d msgSize=%zd\n", myWorkerNumber, msgSize);
+	return shm_mq_send(outQ, msgSize, msg, false, true);
+}
+
+
+/*
+ * This must be called from the worker process's main function.
+ */
+void workerInit(Datum arg)
+{
+	dsm_handle handle;
+
+	/* We are passed the dsm handle of the worker pool */
+	handle = DatumGetInt32(arg);
+	debug("handle=%d\n", handle);
+
+	/*
+	* Establish signal handlers.
+	*
+	* We want CHECK_FOR_INTERRUPTS() to kill off this worker process just as
+	* it would a normal user backend.  To make that happen, we use die().
+	*/
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/*
+	 * Connect to the dynamic shared memory segment.
+	 *
+	 * The backend that registered this worker passed us the ID of a shared
+	 * memory segment to which we must attach for further instructions.  Once
+	 * we've mapped the segment in our address space, attach to the table of
+	 * contents so we can locate the various data structures we'll need to
+	 * find within the segment.
+	 *
+	 * Note: at this point, we have not created any ResourceOwner in this
+	 * process.  This will result in our DSM mapping surviving until process
+	 * exit, which is fine.  If there were a ResourceOwner, it would acquire
+	 * ownership of the mapping, but we have no need for that.
+	 */
+	seg = dsm_attach(handle);
+	if (seg == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					errmsg("unable to map dynamic shared memory segment")));
+	toc = shm_toc_attach(WORKER_POOL_MAGIC, dsm_segment_address(seg));
+	if (toc == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					errmsg("bad magic number in dynamic shared memory segment")));
+
+	/* Attach to the startup header and get our worker idx */
+	hdr = shm_toc_lookup(toc, 0, false);
+	myWorkerNumber = pg_atomic_fetch_add_u32(&hdr->nextWorker, 1);
+	debug("myWorkerNumber=%d\n", myWorkerNumber);
+	if (myWorkerNumber >= hdr->nWorkers)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					errmsg("too many workers created in pool. Max=%d", hdr->nWorkers)));
+
+
+    /* Attach to the in and out message queues. */
+	inQ = attachToQueue(seg,  toc, myWorkerNumber, 0, false);
+	outQ = attachToQueue(seg, toc, myWorkerNumber, 1, true);
+
+	/* Wait for everybody else to become ready */
+	debug("Waiting on barrier\n");
+	BarrierArriveAndWait(hdr->barrier, 0);
+	debug("Barrier passed\n");
+}
+
+
+void workerExit(int code)
+{
+	debug("code=%d\n", code);
+	/*
+    * We're done.  For cleanliness, explicitly detach from the shared memory
+    * segment (that would happen anyway during process exit, though).
+    */
+	//dsm_detach(seg);
+	proc_exit(code);
+}
+
+
+static MemQue attachToQueue(dsm_segment *seg, shm_toc *toc, int workerIdx, int queueIdx, bool isSender)
+{
+	MemQue que;
+	shm_mq *mq;
+
+	/* Attach to the appropriate message queues. */
+	mq = shm_toc_lookup(toc, 1 + 2 * workerIdx + queueIdx, false);
+
+	/* Make note whether we are sending or receiving */
+	if (isSender)
+		shm_mq_set_sender(mq, MyProc);
+	else
+		shm_mq_set_receiver(mq, MyProc);
+
+	/* Attach to the queue */
+	que = shm_mq_attach(mq, seg, NULL);
+
+	debug("workerIdx=%d, queueIdx=%d  handle=%p\n", workerIdx, queueIdx, que);
+	return que;
+}
diff --git a/src/test/modules/test_memtrack/worker_pool.h b/src/test/modules/test_memtrack/worker_pool.h
new file mode 100644
index 0000000000..b4c2a84939
--- /dev/null
+++ b/src/test/modules/test_memtrack/worker_pool.h
@@ -0,0 +1,69 @@
+/*
+ * Implement a pool of workers that can be used to perform work in parallel.
+ * This version is derived from the postgres test_shm_mq module, but it is
+ * intended to be more general purpose. Besides being useful for running
+ * tests, it could eventually replace the current implementation of parallel query.
+ * For now, it is just a piece of infrastructure for running parallel tests.
+ *
+ * The worker pool is created by the owner process. It creates a shared memory
+ * segment and a set of background workers. Each background worker is provided
+ * with three message queues:
+ *    - inQ:  used to send messages to the worker
+ *    - outQ: used to reply to the owner
+ *    - errQ: used to send error exceptions back to the owner.
+ * Note: errQ is not yet implemented.
+ *
+ * The flow of control for the owner is:
+ *    pool = createWorkerPool(nWorkers, inSize, outSize, libName, procName)
+ *    repeat
+ *       sendToWorker(pool, workerIdx, msg, len)
+ *       recvFromWorker(pool, workerIdx, &msg, &len)
+ *    until done
+ *    freeWorkerPool(pool)
+ *
+ * The flow of control for the worker is:
+ *    workerInit(arg)
+ *    repeat
+ *        workerRecv(&msg, &len)
+ *        workerSend(msg, len)
+ *    until done
+ *    workerExit()
+ *
+ * If the workers don't exit on their own, they will be terminated when
+ * the owner calls freeWorkerPool.
+ *
+ * Currently, the worker entry point is passed as the name of a procedure
+ * and the name of a shared library.
+ * Future updates may allow the worker to be specified as function pointer
+ * in the main postgres executable.
+ */
+
+#ifndef WORKER_POOL_H
+#define WORKER_POOL_H
+#include <unistd.h>
+#include "postgres.h"
+#include "storage/dsm.h"
+#include "storage/shm_toc.h"
+#include "storage/shm_mq.h"
+#include "storage/s_lock.h"
+
+typedef struct WorkerPool WorkerPool;
+typedef shm_mq_handle *MemQue;
+
+/*
+ * Procedures called from the worker pool owner
+ */
+WorkerPool *createWorkerPool(int nWorkers, int inSize, int outSize, char *libName, char *procName);
+shm_mq_result sendToWorker(WorkerPool *pool, int workerIdx, void *msg, Size len);
+shm_mq_result recvFromWorker(WorkerPool *pool, int workerIdx, void **msg, Size *len);
+void freeWorkerPool(WorkerPool *pool);
+
+/*
+ * Procedures called from the worker
+ */
+void workerInit(Datum arg);
+shm_mq_result  workerRecv(void **msg, Size *msgSize);
+shm_mq_result  workerSend(void *msg, Size msgSize);
+void workerExit(int code);
+
+#endif //WORKER_POOL_H
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 5058be5411..171dffa456 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1873,6 +1873,26 @@ pg_stat_database_conflicts| SELECT oid AS datid,
     pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock,
     pg_stat_get_db_conflict_logicalslot(oid) AS confl_active_logicalslot
    FROM pg_database d;
+pg_stat_global_memory_allocation| WITH sums AS (
+         SELECT sum(pg_stat_memory_allocation.aset_allocated_bytes) AS total_aset_allocated_bytes,
+            sum(pg_stat_memory_allocation.dsm_allocated_bytes) AS total_dsm_allocated_bytes,
+            sum(pg_stat_memory_allocation.generation_allocated_bytes) AS total_generation_allocated_bytes,
+            sum(pg_stat_memory_allocation.slab_allocated_bytes) AS total_slab_allocated_bytes
+           FROM pg_stat_memory_allocation
+        )
+ SELECT s.datid,
+    current_setting('shared_memory_size'::text, true) AS shared_memory_size,
+    (current_setting('shared_memory_size_in_huge_pages'::text, true))::integer AS shared_memory_size_in_huge_pages,
+    pg_size_bytes(current_setting('max_total_backend_memory'::text, true)) AS max_total_backend_memory_bytes,
+    s.total_bkend_mem_bytes_available,
+    s.global_dsm_allocated_bytes,
+    sums.total_aset_allocated_bytes,
+    sums.total_dsm_allocated_bytes,
+    sums.total_generation_allocated_bytes,
+    sums.total_slab_allocated_bytes
+   FROM sums,
+    (pg_stat_get_global_memory_allocation() s(datid, total_bkend_mem_bytes_available, global_dsm_allocated_bytes)
+     LEFT JOIN pg_database d ON ((s.datid = d.oid)));
 pg_stat_gssapi| SELECT pid,
     gss_auth AS gss_authenticated,
     gss_princ AS principal,
@@ -1899,6 +1919,15 @@ pg_stat_io| SELECT backend_type,
     fsync_time,
     stats_reset
    FROM pg_stat_get_io() b(backend_type, object, context, reads, read_time, writes, write_time, writebacks, writeback_time, extends, extend_time, op_bytes, hits, evictions, reuses, fsyncs, fsync_time, stats_reset);
+pg_stat_memory_allocation| SELECT s.datid,
+    s.pid,
+    s.allocated_bytes,
+    s.aset_allocated_bytes,
+    s.dsm_allocated_bytes,
+    s.generation_allocated_bytes,
+    s.slab_allocated_bytes
+   FROM (pg_stat_get_memory_allocation(NULL::integer) s(datid, pid, allocated_bytes, aset_allocated_bytes, dsm_allocated_bytes, generation_allocated_bytes, slab_allocated_bytes)
+     LEFT JOIN pg_database d ON ((s.datid = d.oid)));
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 94187e59cf..8e70c836cd 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1628,4 +1628,40 @@ SELECT COUNT(*) FROM brin_hot_3 WHERE a = 2;
 
 DROP TABLE brin_hot_3;
 SET enable_seqscan = on;
+-- ensure that allocated_bytes exist for backends
+SELECT
+    allocated_bytes >= 0 AS result
+FROM
+    pg_stat_activity ps
+    JOIN pg_stat_memory_allocation pa ON (pa.pid = ps.pid)
+WHERE
+    backend_type IN ('checkpointer', 'background writer', 'walwriter', 'autovacuum launcher');
+ result 
+--------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- ensure that pg_stat_global_memory_allocation view exists
+SELECT
+    datid > 0, pg_size_bytes(shared_memory_size) >= 0, shared_memory_size_in_huge_pages >= -1, global_dsm_allocated_bytes >= 0
+FROM
+    pg_stat_global_memory_allocation;
+ ?column? | ?column? | ?column? | ?column? 
+----------+----------+----------+----------
+ t        | t        | t        | t
+(1 row)
+
+-- ensure that pg_stat_memory_allocation view exists
+SELECT
+    pid > 0, allocated_bytes >= 0, aset_allocated_bytes >= 0, dsm_allocated_bytes >= 0, generation_allocated_bytes >= 0, slab_allocated_bytes >= 0
+FROM
+    pg_stat_memory_allocation limit 1;
+ ?column? | ?column? | ?column? | ?column? | ?column? | ?column? 
+----------+----------+----------+----------+----------+----------
+ t        | t        | t        | t        | t        | t
+(1 row)
+
 -- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 1e21e55c6d..cdc8508309 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -840,4 +840,24 @@ DROP TABLE brin_hot_3;
 
 SET enable_seqscan = on;
 
+-- ensure that allocated_bytes exist for backends
+SELECT
+    allocated_bytes >= 0 AS result
+FROM
+    pg_stat_activity ps
+    JOIN pg_stat_memory_allocation pa ON (pa.pid = ps.pid)
+WHERE
+    backend_type IN ('checkpointer', 'background writer', 'walwriter', 'autovacuum launcher');
+
+-- ensure that pg_stat_global_memory_allocation view exists
+SELECT
+    datid > 0, pg_size_bytes(shared_memory_size) >= 0, shared_memory_size_in_huge_pages >= -1, global_dsm_allocated_bytes >= 0
+FROM
+    pg_stat_global_memory_allocation;
+
+-- ensure that pg_stat_memory_allocation view exists
+SELECT
+    pid > 0, allocated_bytes >= 0, aset_allocated_bytes >= 0, dsm_allocated_bytes >= 0, generation_allocated_bytes >= 0, slab_allocated_bytes >= 0
+FROM
+    pg_stat_memory_allocation limit 1;
 -- End of Stats Test
-- 
2.33.0



^ permalink  raw  reply  [nested|flat] 5+ messages in thread


end of thread, other threads:[~2023-08-31 16:18 UTC | newest]

Thread overview: 5+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2021-03-11 09:01 [PATCH v55 3/3] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]>
2023-01-05 18:58 Re: Add tracking of backend memory allocated to pg_stat_activity Reid Thompson <[email protected]>
2023-01-05 20:13 ` Re: Add tracking of backend memory allocated to pg_stat_activity Justin Pryzby <[email protected]>
2023-01-05 21:33   ` Re: Add tracking of backend memory allocated to pg_stat_activity Reid Thompson <[email protected]>
2023-08-31 16:18     ` Re: Add tracking of backend memory allocated to pg_stat_activity John Morris <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox