How to Understand the Registration of Background Workers in Postgres Source Code

In postgres nearly 5 background activities are running all the time. We can check the activities related metadata from the postgres catalogue named pg_stat_activity. But the main feature of postgres is adding custom functionalities through the extension format or in postgres source code also. So postgres has an inbuilt background worker based on logical replication, and it is only active during the logical replication process.

You can check the current background activities in postgres like this.

 select * from pg_stat_activity where wait_event_type = 'Activity';

Result :

 datid | datname |  pid  | leader_pid | usesysid | usename  | application_name | client_addr | client_hostname | client_port |          backend_start           | xact_start | query_start | state_change | wait_event_type |     wait_event      | state | backend_xid | backend_xmin | query_id | query |         backend_type         
-------+---------+-------+------------+----------+----------+------------------+-------------+-----------------+-------------+----------------------------------+------------+-------------+--------------+-----------------+---------------------+-------+-------------+--------------+----------+-------+------------------------------
       |         | 38462 |            |       10 | postgres |                  |             |                 |             | 2026-08-17 10:39:45.181441+05:30 |            |             |              | Activity        | LogicalLauncherMain |       |             |              |          |       | logical replication launcher
       |         | 38458 |            |          |          |                  |             |                 |             | 2026-08-17 10:39:45.180519+05:30 |            |             |              | Activity        | WalWriterMain       |       |             |              |          |       | walwriter
       |         | 38447 |            |          |          |                  |             |                 |             | 2026-08-17 10:39:45.173879+05:30 |            |             |              | Activity        | BgwriterHibernate   |       |             |              |          |       | background writer
       |         | 38445 |            |          |          |                  |             |                 |             | 2026-08-17 10:39:45.173879+05:30 |            |             |              | Activity        | CheckpointerMain    |       |             |              |          |       | checkpointer
       |         | 38460 |            |          |          |                  |             |                 |             | 2026-08-17 10:39:45.180894+05:30 |            |             |              | Activity        | AutovacuumMain      |       |             |              |          |       | autovacuum launcher
(5 rows)

In postgres source code , you can see a file named postmaster.c and you can see the source code of background worker related to the logical replication.

The location of postmaster.c file in postgres source code - src/backend/postmaser/postmaster.c

	/*
* Register the apply launcher.  It's probably a good idea to call this
* before any modules had a chance to take the background worker slots.
*/
ApplyLauncherRegister();

This is the code part where it registers the background worker of logical replication in postgres.

Let’s check more details about this background worker-related code part.

/*
 * ApplyLauncherRegister
 * Register a background worker running the logical replication launcher.
 */
void
ApplyLauncherRegister(void)
{
BackgroundWorker bgw;
/*
* The logical replication launcher is disabled during binary upgrades, to
* prevent logical replication workers from running on the source cluster.
* That could cause replication origins to move forward after having been
* copied to the target cluster, potentially creating conflicts with the
* copied data files.
*/
if (max_logical_replication_workers == 0 || IsBinaryUpgrade)
return;
memset(&bgw, 0, sizeof(bgw));
bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
BGWORKER_BACKEND_DATABASE_CONNECTION;
bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ApplyLauncherMain");
snprintf(bgw.bgw_name, BGW_MAXLEN,
"logical replication launcher");
snprintf(bgw.bgw_type, BGW_MAXLEN,
"logical replication launcher");
bgw.bgw_restart_time = 5;
bgw.bgw_notify_pid = 0;
bgw.bgw_main_arg = (Datum) 0;
RegisterBackgroundWorker(&bgw);
}

In postgres, we have a structure named BackgroundWorker and we can use this structure to add the metadata of this background worker like the flags during the background worker registration, the starting time of this worker, and also the function to be executed by this background worker.

Before adding these metadata, we need to allocate memory for this structure through the memset().

Now, let’s go more detailed line by line.

	BackgroundWorker bgw;

We need to declare this background worker structure before using.

if (max_logical_replication_workers == 0 || IsBinaryUpgrade)
return;

Here, this is just a conditional statement to check that there is no logical replication workers and this is not a binary upgrade process using pg_upgrade

In postgresql, we have some global variables that store the metadata related to Postgres. So the purpose of this binary upgrade check is that, during a normal Postgres upgrade process using pg_upgrade, sometimes this process that runs postmaster several times.

So during this time, it creates the new versions postgres catalogues and its metadata only. So we need to ensure that there is no background workers should start during the postgres upgrade.

	memset(&bgw, 0, sizeof(bgw));

Allocate memory for this structure using the memset function by passing the address of this structure, the size of the structure, and we pass 0 to ensure that the 0 is written to the each byte of this allocated memory.

bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
BGWORKER_BACKEND_DATABASE_CONNECTION;

Here, we can actually include the flags related to this background worker.

There are mainly four types of flags we can use. These flags can be found in the file named bgworker.h header file.

Path of bgworker.h - src/include/postmaster/bgworker.h

The four types of flags -

/*
 * Pass this flag to have your worker be able to connect to shared memory.
 * This flag is required.
 */
#define BGWORKER_SHMEM_ACCESS 0x0001
/*
 * This flag means the bgworker requires a database connection.  The connection
 * is not established automatically; the worker must establish it later.
 * It requires that BGWORKER_SHMEM_ACCESS was passed too.
 */
#define BGWORKER_BACKEND_DATABASE_CONNECTION 0x0002
/*
 * Exit the bgworker if its database is involved in a CREATE, ALTER or DROP
 * database command.  It requires BGWORKER_SHMEM_ACCESS and
 * BGWORKER_BACKEND_DATABASE_CONNECTION.
 */
#define BGWORKER_INTERRUPTIBLE 0x0004
/*
 * This class is used internally for parallel queries, to keep track of the
 * number of active parallel workers and make sure we never launch more than
 * max_parallel_workers parallel workers at the same time.  Third party
 * background workers should not use this class.
 */
#define BGWORKER_CLASS_PARALLEL

We can specify the starting time of a background worker, and there are mainly three types of values we can choose.

bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;

Three states of starting point of background workers

BgWorkerStart_PostmasterStart

  • Here, the background worker can start with the postmaster starting time

BgWorkerStart_ConsistentState

  • This state is related to the database consistent state after the successful streaming replication. In this phase, the wal files are replayed and only after the complete replay of WAL does the database change from inconsistent state to consistent state. During this time the background worker can start.

BgWorkerStart_RecoveryFinished

  • The background worker can only start after the completion of recovery mode.

You can see the structure definitions of these starting time-related options from the file named bgworker.h

/*
 * Points in time at which a bgworker can request to be started
 */
typedef enum
{
BgWorkerStart_PostmasterStart,
BgWorkerStart_ConsistentState,
BgWorkerStart_RecoveryFinished,
} BgWorkerStartTime;

We need to specify the name of the library for this background worker. This is not a custom extension that registers a separate background worker. So we can set this to postgres. It looks like the background worker's function entry in the postgres server executable.

	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");

This is the same way we set the function name for the background worker.

snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ApplyLauncherMain");

Here, ApplyLauncherMain is the name of the function that will execute the functionality for this background worker.

snprintf(bgw.bgw_name, BGW_MAXLEN,
"logical replication launcher");

Here, we specify the name for the background worker, and this name will show in the pg_stat_activity catalogue.

snprintf(bgw.bgw_type, BGW_MAXLEN,
"logical replication launcher");

This is the type or category of this background worker.

bgw.bgw_restart_time = 5;

Here, we specify the time in seconds where the postgres should wait when the background worker crashes. If the background worker crashes, it will wait for the specified number of seconds based on bgw.bgw_restart_time and then ut creates a new background worker automatically.

bgw.bgw_notify_pid = 0;

Here, we specify which backend should notify when the background worker starts or crashes. We can pass a specific process id here.

bgw.bgw_main_arg = (Datum) 0;

This is the main argument we passes to the background worker’s function

	RegisterBackgroundWorker(&bgw);

This is the main function we are used to register our background worker by passing the address of our background worker related structure.

/*
 * Register a new static background worker.
 *
 * This can only be called directly from postmaster or in the _PG_init
 * function of a module library that's loaded by shared_preload_libraries;
 * otherwise it will have no effect.
 */
void
RegisterBackgroundWorker(BackgroundWorker *worker)
{

You can competely read the source code of the RegisterBackgroundWorker() in the file named bgworker.c

One of the biggest advantages of postgres is that actually we can actually implement the custom functionality based on our own needs through the postgres extension format. There are much more better postgres extensions available and we can use these extensions on our own terms. We can also use the custom background worker in postgres to add additional recurring functionalities also similar to autovacuum like features. Implementing a custom background should be learned and understood by every database developer or postgres developer also. By understanding this, we can implement better features of enterprise databases also.

WhatsApp