↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3
历史版本。 PostgreSQL 12 已结束支持。 2024-11-21. 请参阅 当前版本手册.

第 47 章 后台工作进程

PostgreSQL 可以扩展为在独立进程中运行用户提供的代码。这类进程由 postgres 启动、停止并监控,因此它们的生命周期能够与服务器状态紧密关联。这些进程可以选择附着到 PostgreSQL 的共享内存区域以及在内部连接到数据库;它们还可以像普通的客户端连接服务器进程一样串行运行多个事务。此外,通过链接 libpq,它们还可以连接到服务器,并表现得像普通客户端应用程序一样。

警告

使用后台工作进程存在相当大的健壮性和安全风险,因为它们是用 C 语言编写的,对数据具有不受限制的访问权限。希望启用包含后台工作进程的模块的管理员必须极其谨慎。只应允许经过仔细审计的模块运行后台工作进程。

可以通过在 shared_preload_libraries 中包含模块名,在 PostgreSQL 启动时初始化后台工作进程。希望运行后台工作进程的模块可以在其_PG_init() 函数中调用 RegisterBackgroundWorker(BackgroundWorker *worker) 来注册它。系统启动并运行后,也可以通过调用 RegisterDynamicBackgroundWorker(BackgroundWorker *worker, BackgroundWorkerHandle **handle) 来启动后台工作进程。与只能在 postmaster 进程内调用的 RegisterBackgroundWorker 不同,RegisterDynamicBackgroundWorker 必须由普通后端或另一个后台工作进程调用。

BackgroundWorker 结构体定义如下:

typedef void (*bgworker_main_type)(Datum main_arg);
typedef struct BackgroundWorker
{
    char        bgw_name[BGW_MAXLEN];
    char        bgw_type[BGW_MAXLEN];
    int         bgw_flags;
    BgWorkerStartTime bgw_start_time;
    int         bgw_restart_time;       /* in seconds, or BGW_NEVER_RESTART */
    char        bgw_library_name[BGW_MAXLEN];
    char        bgw_function_name[BGW_MAXLEN];
    Datum       bgw_main_arg;
    char        bgw_extra[BGW_EXTRALEN];
    int         bgw_notify_pid;
} BackgroundWorker;

bgw_name 和 bgw_type 是用于日志消息、进程列表及类似场景的字符串。对于同一类型的所有后台工作进程,bgw_type 都应相同,这样例如就可以在进程列表中将这类工作进程归为一组。另一方面,bgw_name 可以包含该特定进程的额外信息。(通常,bgw_name 中的字符串会以某种方式包含该类型,但这并非严格要求。)

bgw_flags 是一个按位或组合的位掩码,用于指示模块所需的能力。可能的值有:

BGWORKER_SHMEM_ACCESS

请求共享内存访问。没有共享内存访问权的工作进程不能访问 PostgreSQL 的任何共享数据结构,如重量级锁或轻量级锁、共享缓冲区,以及工作进程自身可能希望创建和使用的任何自定义数据结构。

BGWORKER_BACKEND_DATABASE_CONNECTION

请求建立数据库连接的能力,以便之后运行事务和查询。使用 BGWORKER_BACKEND_DATABASE_CONNECTION 连接数据库的后台工作进程,还必须使用 BGWORKER_SHMEM_ACCESS 附着到共享内存,否则工作进程启动将失败。

bgw_start_time 表示 postgres 应在服务器的哪个状态下启动该进程;它可以是 BgWorkerStart_PostmasterStart(在 postgres 自身完成初始化后立即启动;请求此值的进程不能建立数据库连接)、BgWorkerStart_ConsistentState(在热备达到一致状态后立即启动,允许进程连接数据库并运行只读查询),或 BgWorkerStart_RecoveryFinished(在系统进入正常读写状态后立即启动)。请注意,在非热备服务器中,后两个值是等价的。还要注意,此设置只表示何时启动进程;当到达其他状态时,这些进程不会停止。

bgw_restart_time 是以秒计的时间间隔,表示在进程崩溃时,postgres 在重新启动该进程之前应等待多久。它可以是任意正值,也可以是 BGW_NEVER_RESTART,表示在进程崩溃时不重新启动该进程。

bgw_library_name 是一个库的名称,后台工作进程的初始入口点将在该库中查找。该库会由工作进程动态加载,而 bgw_function_name 将用于标识要调用的函数。如果加载的是核心代码中的函数,此字段必须设置为 "postgres"。

bgw_function_name 是动态加载库中的一个函数名,用作新后台工作进程的初始入口点。

bgw_main_arg 是传给后台工作进程主函数的 Datum 参数。该主函数应接受一个 Datum 类型的参数,并返回 void。bgw_main_arg 会作为该参数传入。此外,全局变量 MyBgworkerEntry 指向注册时传入的 BackgroundWorker 结构体的一份副本;检查该结构体可能会对工作进程有所帮助。

在 Windows(以及任何定义了 EXEC_BACKEND 的地方)上,或者在动态后台工作进程中,以引用方式传递 Datum 并不安全,只能按值传递。如果需要传递参数,最安全的方式是传递一个 int32 或其他较小的值,并将其用作共享内存中分配的数组的索引。如果传递的是 cstring 或 text 这样的值,那么该指针在新的后台工作进程中将无效。

bgw_extra 可以包含要传递给后台工作进程的额外数据。与 bgw_main_arg 不同,这些数据不会作为参数传递给工作进程的主函数,但可以如上所述通过 MyBgworkerEntry 访问。

bgw_notify_pid 是一个 PostgreSQL 后端进程的 PID,当后台工作进程启动或退出时,postmaster 应向该后端进程发送 SIGUSR1。对于在 postmaster 启动时注册的工作进程,或者注册该工作进程的后端不希望等待其启动时,它应为 0。否则,它应初始化为 MyProcPid。

进程一旦开始运行,就可以通过调用 BackgroundWorkerInitializeConnection(char *dbname, char *username, uint32 flags) 或 BackgroundWorkerInitializeConnectionByOid(Oid dboid, Oid useroid, uint32 flags) 连接到数据库。这使得该进程能够通过 SPI 接口运行事务和查询。如果 dbname 为 NULL,或 dboid 为 InvalidOid,则该会话不会连接到任何特定数据库,但仍可访问共享系统目录。如果 username 为 NULL,或 useroid 为 InvalidOid,则该进程将以 initdb 期间创建的超级用户身份运行。如果将 BGWORKER_BYPASS_ALLOWCONN 指定为 flags,则可以绕过对不允许用户连接的数据库的连接限制。后台工作进程只能调用这两个函数中的一个,而且只能调用一次。不能切换数据库。

当控制流到达后台工作进程的主函数时,信号起初处于阻塞状态,必须由主函数解除阻塞;这样做是为了在必要时允许该进程自定义其信号处理器。在新进程中,可以调用 BackgroundWorkerUnblockSignals 解除信号阻塞,并调用 BackgroundWorkerBlockSignals 重新阻塞信号。

如果后台工作进程的 bgw_restart_time 配置为 BGW_NEVER_RESTART,或者它以退出码 0 退出,或者被 TerminateBackgroundWorker 终止,则 postmaster 会在其退出时自动注销它。否则,它会在 bgw_restart_time 配置的时间段之后重新启动;如果 postmaster 因某个后端失败而重新初始化集簇,则会立即重新启动它。只需暂时挂起执行的后端应使用可中断的睡眠而不是退出;这可以通过调用 WaitLatch() 来实现。调用该函数时,要确保设置了 WL_POSTMASTER_DEATH 标志,并检查返回码,以便在 postgres 自身终止的紧急情况下及时退出。

当使用 RegisterDynamicBackgroundWorker 函数注册后台工作进程时,执行注册的后端可以获取有关该工作进程状态的信息。希望这样做的后端应将 BackgroundWorkerHandle * 的地址作为第二个参数传递给 RegisterDynamicBackgroundWorker。如果工作进程成功注册,该指针将初始化为一个不透明句柄,随后可以将其传递给 GetBackgroundWorkerPid(BackgroundWorkerHandle *, pid_t *) 或 TerminateBackgroundWorker(BackgroundWorkerHandle *)。GetBackgroundWorkerPid 可用于轮询工作进程的状态:返回值为 BGWH_NOT_YET_STARTED 表示该工作进程尚未由 postmaster 启动;BGWH_STOPPED 表示它已经启动但不再运行;BGWH_STARTED 表示它当前正在运行。在最后一种情况下,PID 也会通过第二个参数返回。TerminateBackgroundWorker 会使 postmaster 在该工作进程运行时向其发送 SIGTERM,并在其不再运行后尽快将其注销。

在某些情况下,注册后台工作进程的进程可能希望等待该工作进程启动。这可以通过将 bgw_notify_pid 初始化为 MyProcPid,然后把注册时获得的 BackgroundWorkerHandle * 传递给 WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *) 函数来实现。该函数会阻塞,直到 postmaster 已尝试启动该后台工作进程,或者直到 postmaster 死亡。如果后台工作进程正在运行,返回值将是 BGWH_STARTED,其 PID 会被写入所提供的地址。否则,返回值将是 BGWH_STOPPED 或 BGWH_POSTMASTER_DIED。

进程也可以通过使用 WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle) 函数,并传入注册时获得的 BackgroundWorkerHandle *,来等待后台工作进程关闭。该函数会阻塞,直到后台工作进程退出,或者 postmaster 死亡。当后台工作进程退出时,返回值为 BGWH_STOPPED;如果 postmaster 死亡,则返回 BGWH_POSTMASTER_DIED。

如果后台工作进程通过服务器编程接口(SPI)使用 NOTIFY 命令发送异步通知,那么应在提交包含该命令的事务后,显式调用 ProcessCompletedNotifies,以便送达通知。如果后台工作进程通过 SPI 使用 LISTEN 注册接收异步通知,该工作进程会将这些通知写入日志,但无法以编程方式拦截并响应这些通知。

src/test/modules/worker_spi 模块包含一个可工作的示例,展示了一些有用的技巧。

已注册后台工作进程的最大数量受 max_worker_processes 限制。

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.