A background worker is a PostgreSQL process forked by the postmaster that runs arbitrary extension-supplied code. Unlike a regular backend, it does not serve a client connection – but it can connect to databases, run transactions, and coordinate with other processes through shared memory.
The background worker API lets extensions run long-lived or short-lived processes within the PostgreSQL server. Workers are forked from the postmaster just like regular backends, inherit the shared memory mapping, and can optionally establish database connections to run SQL. The API provides a complete lifecycle: registration, startup notification, status monitoring, and termination.
Background workers power some of PostgreSQL’s own subsystems (logical replication launcher, autovacuum launcher) and are essential for extensions like TimescaleDB (background schedulers), pg_cron (periodic job execution), and Citus (distributed query execution workers).
There are two registration paths. Static registration happens during _PG_init when shared_preload_libraries are loaded, before any backends start. Dynamic registration happens at runtime from a regular backend using RegisterDynamicBackgroundWorker. Static workers survive postmaster restarts; dynamic workers are tied to the session that created them (though the worker process itself runs independently).
| File | Purpose |
|---|---|
src/include/postmaster/bgworker.h |
BackgroundWorker struct, registration APIs, status queries |
src/backend/postmaster/bgworker.c |
Worker slot management, fork/signal handling |
src/backend/postmaster/postmaster.c |
Postmaster loop that starts/restarts workers |
src/backend/utils/init/miscinit.c |
BackgroundWorkerInitializeConnection |
stateDiagram-v2
[*] --> Registered: RegisterBackgroundWorker() or\nRegisterDynamicBackgroundWorker()
Registered --> Started: Postmaster forks
Started --> Running: bgw_main() begins
Running --> Running: Process work\n(can connect to DB, run SQL)
Running --> Stopped: proc_exit() or crash
Stopped --> Started: BGW_RESTART_TIME elapsed\n(auto-restart)
Stopped --> [*]: BGW_NEVER_RESTART
A background worker is described by the BackgroundWorker struct:
typedef struct BackgroundWorker
{
char bgw_name[BGW_MAXLEN]; /* human-readable name */
char bgw_type[BGW_MAXLEN]; /* worker type for ps display */
int bgw_flags; /* BGWORKER_SHMEM_ACCESS, etc. */
BgWorkerStartTime bgw_start_time; /* when to start */
int bgw_restart_time; /* seconds, or BGW_NEVER_RESTART */
char bgw_library_name[MAXPGPATH]; /* shared library name */
char bgw_function_name[BGW_MAXLEN]; /* entry point function */
Datum bgw_main_arg; /* argument to main function */
char bgw_extra[BGW_EXTRALEN]; /* extra data (128 bytes) */
pid_t bgw_notify_pid; /* send SIGUSR1 on state change */
} BackgroundWorker;
BGWORKER_SHMEM_ACCESS (0x0001) Required. Access shared memory.
BGWORKER_BACKEND_DATABASE_CONNECTION (0x0002) Can establish a DB connection.
Requires SHMEM_ACCESS.
BGWORKER_INTERRUPTIBLE (0x0004) Exit if database involved in
CREATE/ALTER/DROP DATABASE.
BGWORKER_CLASS_PARALLEL (0x0010) Internal: parallel query workers.
typedef enum {
BgWorkerStart_PostmasterStart, /* start as soon as postmaster starts */
BgWorkerStart_ConsistentState, /* start when recovery reaches consistent state */
BgWorkerStart_RecoveryFinished, /* start after recovery completes */
} BgWorkerStartTime;
Extension _PG_init()
|
| RegisterBackgroundWorker(&worker)
v
+------------------+
| Postmaster |
| (worker slots) | Stores worker in shared memory slot
+------------------+
|
| fork() when bgw_start_time condition is met
v
+------------------+
| Worker Process |
| | 1. Shared memory is already mapped (inherited from postmaster)
| bgw_main() | 2. Calls BackgroundWorkerInitializeConnection() if needed
| | 3. Runs extension logic (transactions, queries, etc.)
| | 4. Exits with code 0 (no restart) or 1 (restart after interval)
+------------------+
|
| exit(0) or exit(1) or SIGTERM
v
+------------------+
| Postmaster |
| | If exit code != 0 and bgw_restart_time != BGW_NEVER_RESTART:
| | wait bgw_restart_time seconds, then fork() again
| | If exit code == 0 or BGW_NEVER_RESTART:
| | unregister worker, free slot
+------------------+
STATIC REGISTRATION DYNAMIC REGISTRATION
(during shared_preload_libraries) (from a running backend)
_PG_init() { some_backend_function() {
BackgroundWorker worker; BackgroundWorker worker;
/* fill in worker... */ BackgroundWorkerHandle *handle;
RegisterBackgroundWorker(&worker); /* fill in worker... */
} RegisterDynamicBackgroundWorker(
&worker, &handle);
WaitForBackgroundWorkerStartup(
handle, &pid);
}
- Registered before backends exist - Registered at runtime
- Survives postmaster restart - Lost on postmaster restart
- No handle returned - Returns handle for monitoring
- Cannot wait for startup - Can wait for startup/shutdown
The postmaster resolves bgw_library_name and bgw_function_name via dlopen/dlsym and calls the function with bgw_main_arg:
/* Entry point signature */
typedef void (*bgworker_main_type)(Datum main_arg);
/* Example worker main function */
void
my_worker_main(Datum main_arg)
{
/* Set up signal handlers */
pqsignal(SIGTERM, die);
BackgroundWorkerUnblockSignals();
/* Connect to a database (optional) */
BackgroundWorkerInitializeConnection("mydb", NULL, 0);
/* Main loop */
while (!got_sigterm)
{
int rc = WaitLatch(MyLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
1000L, /* 1 second timeout */
PG_WAIT_EXTENSION);
ResetLatch(MyLatch);
if (rc & WL_LATCH_SET)
CHECK_FOR_INTERRUPTS();
/* Do work: run queries, process data, etc. */
StartTransactionCommand();
SPI_connect();
SPI_execute("SELECT process_pending_jobs()", false, 0);
SPI_finish();
CommitTransactionCommand();
}
proc_exit(0); /* Exit code 0 = don't restart */
}
Dynamic workers return a BackgroundWorkerHandle that the launching backend can use to check status:
typedef enum BgwHandleStatus
{
BGWH_STARTED, /* worker is running */
BGWH_NOT_YET_STARTED, /* worker hasn't started yet */
BGWH_STOPPED, /* worker has exited */
BGWH_POSTMASTER_DIED, /* postmaster died; status unclear */
} BgwHandleStatus;
/* Non-blocking status check */
BgwHandleStatus GetBackgroundWorkerPid(handle, &pid);
/* Blocking waits */
BgwHandleStatus WaitForBackgroundWorkerStartup(handle, &pid);
BgwHandleStatus WaitForBackgroundWorkerShutdown(handle);
/* Termination */
void TerminateBackgroundWorker(handle); /* sends SIGTERM */
Workers that need database access call one of:
/* Connect by name */
BackgroundWorkerInitializeConnection(
const char *dbname, /* database name, or NULL for no database */
const char *username, /* role name, or NULL for bootstrap superuser */
uint32 flags /* BGWORKER_BYPASS_ALLOWCONN, etc. */
);
/* Connect by OID */
BackgroundWorkerInitializeConnectionByOid(
Oid dboid,
Oid useroid,
uint32 flags
);
After this call, the worker can use SPI (Server Programming Interface) to run SQL queries, or directly call executor functions.
BackgroundWorker
+-- bgw_name[96] : display name (visible in pg_stat_activity)
+-- bgw_type[96] : type string (for ps output and grouping)
+-- bgw_flags : capability bitmask (SHMEM_ACCESS, DB_CONNECTION)
+-- bgw_start_time : PostmasterStart | ConsistentState | RecoveryFinished
+-- bgw_restart_time : seconds to wait before restart, or BGW_NEVER_RESTART
+-- bgw_library_name : shared library containing entry point
+-- bgw_function_name : name of entry point function
+-- bgw_main_arg : Datum argument passed to entry point
+-- bgw_extra[128] : opaque extra data (extension-defined use)
+-- bgw_notify_pid : PID to send SIGUSR1 on state changes
The postmaster maintains an array of worker slots in shared memory. The maximum number of background workers is controlled by max_worker_processes (default: 8). Each slot tracks:
Worker Slot (in shared memory)
+-- in_use : bool
+-- terminate : bool (SIGTERM requested)
+-- pid : pid_t (0 if not running)
+-- generation : uint64 (monotonic, prevents stale handle use)
+-- bgw : BackgroundWorker (copy of registration data)
The generation counter prevents a race where a handle refers to a slot that has been reused by a different worker.
void _PG_init(void)
{
BackgroundWorker worker;
if (!process_shared_preload_libraries_in_progress)
return;
memset(&worker, 0, sizeof(worker));
snprintf(worker.bgw_name, BGW_MAXLEN, "my periodic worker");
snprintf(worker.bgw_type, BGW_MAXLEN, "my_extension");
worker.bgw_flags = BGWORKER_SHMEM_ACCESS |
BGWORKER_BACKEND_DATABASE_CONNECTION;
worker.bgw_start_time = BgWorkerStart_RecoveryFinished;
worker.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL; /* 60 seconds */
snprintf(worker.bgw_library_name, MAXPGPATH, "my_extension");
snprintf(worker.bgw_function_name, BGW_MAXLEN, "my_worker_main");
worker.bgw_main_arg = (Datum) 0;
worker.bgw_notify_pid = 0;
RegisterBackgroundWorker(&worker);
}
void launch_helper_worker(int task_id)
{
BackgroundWorker worker;
BackgroundWorkerHandle *handle;
pid_t pid;
memset(&worker, 0, sizeof(worker));
snprintf(worker.bgw_name, BGW_MAXLEN, "helper worker %d", task_id);
worker.bgw_flags = BGWORKER_SHMEM_ACCESS |
BGWORKER_BACKEND_DATABASE_CONNECTION;
worker.bgw_start_time = BgWorkerStart_RecoveryFinished;
worker.bgw_restart_time = BGW_NEVER_RESTART;
snprintf(worker.bgw_library_name, MAXPGPATH, "my_extension");
snprintf(worker.bgw_function_name, BGW_MAXLEN, "helper_main");
worker.bgw_main_arg = Int32GetDatum(task_id);
worker.bgw_notify_pid = MyProcPid;
if (!RegisterDynamicBackgroundWorker(&worker, &handle))
ereport(ERROR, errmsg("could not register background worker"));
if (WaitForBackgroundWorkerStartup(handle, &pid) != BGWH_STARTED)
ereport(ERROR, errmsg("could not start background worker"));
/* Worker is now running with PID = pid */
}
Exit code 0 + any restart_time --> Worker removed, slot freed
Exit code 1 + BGW_NEVER_RESTART --> Worker removed, slot freed
Exit code 1 + restart_time = 60 --> Postmaster waits 60s, forks again
SIGTERM + restart_time = 60 --> Postmaster waits 60s, forks again
SIGTERM + BGW_NEVER_RESTART --> Worker removed, slot freed
Postmaster + crash restart --> Static workers re-registered
crash Dynamic workers lost
| Chapter | Connection |
|---|---|
| Chapter 0: Architecture | Background workers are postmaster-forked processes following the same model as regular backends, autovacuum workers, and WAL sender processes |
| Chapter 11: IPC | Workers coordinate with backends through shared memory, latches, and the SIGUSR1 signaling mechanism |
| Chapter 10: Memory | Workers inherit the shared memory mapping from the postmaster; shmem_request_hook lets extensions reserve additional shared memory |
| Chapter 3: Transactions | Workers with database connections can start transactions and use SPI, following the same transaction lifecycle as regular backends |
| Chapter 4: WAL | Workers that modify data generate WAL records; BgWorkerStart_ConsistentState ensures workers don’t start until recovery reaches a consistent point |