pg_background v1.9:一种更简洁、更实用的后台运行SQL的方法
摘要: pg_background 允许 PostgreSQL 在后台 worker 进程中异步执行 SQL,使调用会话不会被阻塞。它支持通过共享内存队列获取结果、独立于调用方提交的自定义事务、显式的生命周期控制(如 launch、wait、cancel、detach 和 list 操作),以及强化了安全模型(NOLOGIN 角色、特权辅助函数、无 PUBLIC 权限)。 1.9 版本添加了 worker 标签、结构化错误返回、结果元数据、批量操作,使异步 SQL 执行在日常数据库操作中更具可观测性和实用性。并兼容 PostgreSQL 14、15、16、17 和 18
有一类数据库痛点并不是突然爆发的。它来得很安静。
一个查询运行时间超过预期。一个会话被占用。某人只是为了让任务继续而打开了另一个连接。然后又一个任务出现在它后面。很快,一个完全正常的一天开始感觉像太多人试图穿过一个狭窄的门口。
这正是 pg_background 变得有用的地方。
它允许 PostgreSQL 在后台 worker 中运行 SQL,而原始会话保持空闲。工作仍然发生在数据库内部,靠近数据,但调用方不再需要坐在那里等待每个长时间运行的步骤完成。pg_background 的核心是让 PostgreSQL 拥有一种更简洁的方式来处理异步工作,而不必强迫团队离开数据库来保持会话响应。
1.9 版本对日常工作的改进
1.9 版本以实际工作中真正重要的方式改进了操作体验。
Worker 标签减少了对多个任务同时运行时的猜测。结构化错误返回使脚本和应用程序在后台工作失败时更容易智能地做出反应。结果元数据使得可以在不消费结果流的情况下检查完成状态。批量操作简化了会话启动多个 worker 时的清理工作。
综合来看,这些改进使 pg_background 更容易使用。这才是这个版本的真正价值。它使一个有用的扩展更具可观测性、更易管理,在日常操作中更实用。
真实案例:当一个会话成为瓶颈
想象一下季度末处理期间的财务平台。
一个对账任务需要运行。这并不罕见,但它涉及大量数据,可能需要时间。与此同时,平台仍在为用户提供服务,操作员仍在调查支持工单,工程师仍需要工作空间。如果这个对账查询在与触发它的同一个会话中直接运行,会话会被占用。调用方被迫等待。系统的灵活性降低。操作员失去选择。
这正是 pg_background 让人觉得不像一个功能,而更像常识的地方。
会话可以将 SQL 交给 PostgreSQL 后台 worker 处理,而不是强迫原始会话等待整个操作完成。工作继续在服务器内运行,但调用方现在可以自由地继续进行、检查进度、启动下一步,或者简单地继续服务工作流程的其余部分。
一个简单的类比有助于理解。把它想象成在餐厅点餐。服务员不会走进厨房站在炉子旁边直到饭菜做好。订单进入厨房,烹饪发生在有设备的地方,而服务员保持空闲来照顾房间里的其他人。pg_background 的工作方式大致相同。它让 SQL 运行在数据已经存在的地方,而原始会话保持可用。
这就是它的吸引力。它不是关于增加复杂性,而是关于创造更好的流程。
pg_background 是什么,以及团队为什么真正使用它
pg_background 是一个 PostgreSQL 扩展,在数据库服务器内的专用后台 worker 进程中异步执行任意 SQL 命令。与依赖独立连接的客户机端异步模式或变通方案不同,这些 worker 在 PostgreSQL 本身内部运行,并在独立事务中访问本地资源。
这很重要,有几个原因。
首先,工作保持靠近数据。其次,后台 worker 可以独立于启动它的会话提交或回滚。第三,worker 生命周期可以通过 launch、wait、cancel、detach 和 list 操作进行显式管理。
核心能力简单而实用:
- 异步 SQL 执行
- 通过共享内存队列获取结果
- 自定义事务
- 显式生命周期控制
- 经过生产验证的安全模型
这些能力解释了为什么团队在真实系统中使用 pg_background,而不是把它当作新奇事物。
同样重要的是,pg_background 不仅支持发送工作。它还支持通过共享内存队列取回结果,这意味着调用方仍然可以检查输出,而不必在 PostgreSQL 执行模型之外发明单独的返回路径。
自定义事务是团队首先使用这个扩展的最大原因之一。后台 worker 在自己的事务中运行,可以独立于启动它的会话提交或回滚。这为架构师和 DBA 提供了一个有用的设计选项。审计写入、通知或维护操作并不总是必须与调用方的事务共存亡。
该项目也很好地映射到真实的 production 用例:
- 后台维护,如 VACUUM、ANALYZE 和 REINDEX
- 异步审计日志
- 长时间运行的 ETL 管道
- 独立的通知传递
- 并行查询模式实现
这些都不是理论性的。它听起来像是团队实际需要完成的工作类型。
为什么安全模型很重要
后台执行很强大,这正是它需要护栏的原因。
没有团队希望"在后台运行 SQL"悄悄地变成"从任何地方运行任何东西"。pg_background 令人放心的地方之一是安全模型明显是为生产使用而设计的:基于 NOLOGIN 角色的模型、SECURITY DEFINER 特权辅助函数,以及无 PUBLIC 权限授予。
这对技术负责人或 DBA 来说意味着一些重要的事情。这个功能不是为了绕过操作纪律而构建的快捷方式。它是在假设后台执行应该有用而不应该变得鲁莽的情况下构建的。
pg_background 何时适用,何时不适用
当工作本质上是 SQL、数据已经驻留在 PostgreSQL 中,并且调用会话应该保持空闲而不是等待长时间运行的任务时,pg_background 是合适的选择。
当自定义事务行为有用时,它也是很强的选择,如审计日志、维护工作,或应该独立于调用方提交的非主流效果。这个常见用例与该模式很好地吻合:维护、审计日志、ETL、通知和并行工作模式。
如果真正需要的是调度器、完整的工作流编排引擎或保证的外部传递语义,它就不是正确的工具。pg_background 提供了后台 SQL 执行和生命周期控制。它不能替代日历驱动的作业调度,也不应该被视为更广泛工作流系统的完整替代品。
这个区别很重要,因为它保持了健康的期望。一个专注的工具通常比一个臃肿的工具更有用,但前提是团队用它来解决它被构建用来解决的问题。
1.9 版本的新增功能
1.9 版本添加了四个对操作友好的功能,使扩展在日常工作中更容易观测和更容易管理:
- worker 标签
- 结构化错误返回
- 结果元数据
- 批量操作
它还正式支持 PostgreSQL 14 到 18 的兼容性。
Worker 标签
后台执行系统中的一个静默烦恼是任务可能变得匿名。
一个 worker 正在运行。你知道它存在。你甚至可能知道它的 PID。但它实际在做什么?应用程序的哪部分启动了它?它是一个回填任务、审计操作,还是有人忘记记录的一些维护任务?
1.9 版本向 pg_background_launch_v2() 和 pg_background_submit_v2() 添加了可选的标签参数。这些标签出现在 pg_background_list_v2() 输出中,最多可长达 64 字节。
通俗地说,这意味着你终于可以给 worker 一个人类可以识别的名称。
对于非技术读者来说,这就是看到"一个进程正在运行"和看到"客户回填任务正在运行"之间的区别。对于工程师和 DBA 来说,它在不改变执行模型的情况下提高了可观测性。它在启动时赋予 worker 意图,当你稍后检查时,这种意图仍然可见。
一个实际用例是启动多个与不同请求或功能相关联的后台任务的 production 应用程序。诸如 prod/audit/login 或 stage/backfill/customers 这样的标签立即将 worker 检查变成可以理解的东西。
-- Launch a labeled worker
SELECT *
FROM pg_background_launch_v2(
'SELECT pg_sleep(10); SELECT now();',
65536,
'prod/api-42/request-8f3a'
) AS h;
-- Fire-and-forget with a label
SELECT *
FROM pg_background_submit_v2(
'INSERT INTO audit_log(event_type, created_at) VALUES (''login'', now())',
65536,
'prod/audit/login'
) AS h;
-- Observe labeled workers
SELECT *
FROM pg_background_list_v2()
AS (
pid int4,
cookie int8,
launched_at timestamptz,
user_id oid,
queue_size int4,
state text,
sql_preview text,
last_error text,
consumed bool
)
ORDER BY launched_at DESC;
输出:
┌─────────┬──────────────────────┬───────────────────────────────┬─────────┬────────────┬─────────┬──────────────────────────────────────────────────────────────┬────────────┬──────────┐
│ pid │ cookie │ launched_at │ user_id │ queue_size │ state │ sql_preview │ last_error │ consumed │
├─────────┼──────────────────────┼───────────────────────────────┼─────────┼────────────┼─────────┼──────────────────────────────────────────────────────────────┼────────────┼──────────┤
│ 2069536 │ -2650105714474788328 │ 2026-04-01 22:58:09.505279+00 │ 16402 │ 65536 │ stopped │ INSERT INTO audit_log(event_type, created_at) VALUES ('login…│ NULL │ f │
│ │ │ │ │ │ │…', now()) │ │ │
│ 2069534 │ -2772832329059085234 │ 2026-04-01 22:58:04.452824+00 │ 16402 │ 65536 │ stopped │ SELECT pg_sleep(10); SELECT now(); │ NULL │ f │
└─────────┴──────────────────────┴───────────────────────────────┴─────────┴────────────┴─────────┴──────────────────────────────────────────────────────────────┴────────────┴──────────┘
(2 rows)
结构化错误返回
一个好的异步系统不仅通过它在一切成功时的行为来衡量。它也通过它在某些事情失败时行为有多清晰来衡量。
1.9 版本添加了 pg_background_error_info_v2(),返回结构化错误详情,包括:
- sqlstate
- message
- detail
- hint
- context
这是一个有意义的改进,因为它将后台失败变成应用程序和操作员可以清晰检查的东西,而不是将每个失败都视为相同的模糊事件。
对于工程师来说,这尤其有用,因为失败信息变成了可编程的。工作流可以查看 sqlstate,决定错误是否可重试,记录 detail,或者以有针对性的方式显示 hint。
一个好的真实示例是异步数据纠正或批量更新。如果任务因唯一性冲突而失败,应用程序可以检查结构化错误并决定如何继续,而不是将所有内容扁平化为"worker 失败"。
-- Launch a worker that will fail
SELECT *
FROM pg_background_launch_v2(
'SELECT 1/0;',
65536,
'dev/demo/divide-by-zero'
) AS h \gset
-- Inspect the error non-destructively
SELECT *
FROM pg_background_error_info_v2(:pid, :cookie);
输出:
┌──────────┬──────────────────┬────────┬──────┬────────────────────────────────┐
│ sqlstate │ message │ detail │ hint │ context │
├──────────┼──────────────────┼────────┼──────┼────────────────────────────────┤
│ 22012 │ division by zero │ NULL │ NULL │ background worker, pid 2069753 │
└──────────┴──────────────────┴────────┴──────┴────────────────────────────────┘
不消费结果的结果元数据
通常第一个问题不是"完整结果是什么?"第一个问题要简单得多。
任务完成了吗?失败了吗?影响了多少行?运行的是什么类型的语句?
1.9 版本添加了 pg_background_result_info_v2(),返回:
- row_count
- command_tag
- completed
- has_error
重要的细节是这是非破坏性检查。你可以在不消费实际结果流的情况下检查状态。
这在应用程序、仪表板、操作脚本和轮询循环中非常有用。它允许系统在决定下一步做什么之前先询问工作是否完成。
一个实际示例可能是在后台加载数据的批量进程。调用方可能不需要每个返回的行。它可能只需要知道工作完成了,某个数量的行被处理了。
-- Launch a worker that affects rows
SELECT *
FROM pg_background_launch_v2(
'CREATE TEMP TABLE t AS SELECT generate_series(1,1000) AS id; SELECT * FROM t;',
65536,
'dev/demo/result-info'
) AS h \gset
-- Check metadata without consuming results
SELECT *
FROM pg_background_result_info_v2(:pid, :cookie);
输出:
┌───────────┬─────────────┬───────────┬───────────┐
│ row_count │ command_tag │ completed │ has_error │
├───────────┼─────────────┼───────────┼───────────┤
│ 1000 │ SELECT │ t │ f │
└───────────┴─────────────┴───────────┴───────────┘
(1 row)
批量操作
有时问题不是单个 worker。有时问题是十个。
1.9 版本添加了:
pg_background_detach_all_v2()pg_background_cancel_all_v2()
这些函数返回计数,旨在帮助更简洁地管理多个 worker。
这也是强调重要区别的正确位置:分离(detaching)不是取消(canceling)。
当你分离时,你停止跟踪 worker,但 worker 可能继续运行。
当你取消时,你请求 worker 停止。
这个区别很重要,因为它反映了两种非常不同的操作意图。有时工作仍然是有效的,你只是不再想跟踪它。有时工作本身应该停止。
-- Stop tracking all workers launched in this session
SELECT pg_background_detach_all_v2();
-- Cancel all tracked workers launched in this session
SELECT pg_background_cancel_all_v2();
使采用更容易的兼容性
1.9 版本已在 PostgreSQL 14、15、16、17 和 18 上测试。它还包括在 Ubuntu 22.04 和 24.04 上的 CI 覆盖率。
这很重要,因为一个有用的扩展在伴随真实团队已经在使用的版本时更容易采用。对于架构师来说,这意味着更少的升级障碍。对于 DBA 来说,这意味着更少的意外。对于领导者来说,这意味着扩展正在跟上平台的步伐,而不是成为版本锁定的旁路。
安装和升级
全新安装很简单:
-- Fresh install
CREATE EXTENSION pg_background;
从 1.8 升级到 1.9 很简单:
-- Upgrade from 1.8
ALTER EXTENSION pg_background UPDATE TO '1.9';
验证实际安装的内容始终是值得的:
SELECT extversion
FROM pg_extension
WHERE extname = 'pg_background';
这个小的验证步骤可以防止以后出现大量混淆。
psql 中的实践演练
理解 pg_background 最有效的方式是在 psql 中尝试一个简单的工作流。
首先,启动一个 worker 并捕获其句柄:
SELECT *
FROM pg_background_launch_v2(
'SELECT pg_sleep(5); SELECT count(*) FROM pg_class;',
65536,
'dev/demo/count-catalog'
) AS h \gset
接下来,检查当前跟踪的内容:
SELECT *
FROM pg_background_list_v2()
AS (
pid int4,
cookie int8,
launched_at timestamptz,
user_id oid,
queue_size int4,
state text,
sql_preview text,
last_error text,
consumed bool
)
ORDER BY launched_at DESC;
然后检查 worker 是否完成以及它执行了什么类型的工作:
SELECT *
FROM pg_background_result_info_v2(:pid, :cookie);
如果发生了错误,检查它:
SELECT *
FROM pg_background_error_info_v2(:pid, :cookie);
如果需要 fire-and-forget 模式来处理副作用工作,使用 submit_v2() 然后分离:
SELECT *
FROM pg_background_submit_v2(
'INSERT INTO audit_log(event_type, created_at) VALUES (''sync_complete'', now())',
65536,
'prod/audit/sync-complete'
) AS h \gset
SELECT pg_background_detach_v2(:pid, :cookie);
v2 API 是新工作的正确起点。它给你基于 cookie 的标识、更清晰的控制语义和更好的可观测性。
模式和最佳实践
当团队将功能转化为习惯时,这个版本变得更有用。
为标签使用命名约定
Worker 标签在保持一致时最有价值。诸如 feature/env/request-id 这样的格式效果很好,因为它以紧凑的方式捕获意图、环境和可追溯性。
示例包括:
- audit/prod/req-9f8d
- backfill/stage/run-20260401
- etl/prod/customer-sync
这有助于操作员、应用程序开发人员和仪表板使用相同的语言。
在消费结果之前使用 result_info 和 error_info
新的检查函数很强大,因为它们不消费结果流。这意味着你可以在决定下一步检索什么之前先询问工作是否完成或是否发生了错误。对于应用程序工作流和管理工具来说,这比强制每个检查步骤同时作为最终消费的双重角色要清晰得多。
明确区分 detach 和 cancel
这个区别值得出现在每个操作手册中。
Detach 意味着你停止跟踪 worker,但它可能继续运行。
Cancel 意味着你想让它停止。
当工作仍然是有效的、可以在没有主动监督的情况下继续时,使用 detach。当工作本身不应该继续时,使用 cancel。
不要耗尽 worker 容量
后台 worker 很有用,但不是免费的。数据库仍有物理限制。
在实践中,这意味着你应该避免在不受控循环中启动 worker,设置合理的限制,并在负载下测试行为。异步执行是有帮助的,但只有在保持在系统可以安全处理的范围内时。
将可观测性作为工作流的一部分
定期使用 pg_background_list_v2(),而不仅仅是在事故期间。将 worker 标签与请求 ID、日志和 PostgreSQL 活动视图相关联。
当后台执行工具易于看到它在做什么时,它会变得更加值得信赖。
结语
pg_background 解决了一个非常真实的 PostgreSQL 问题,而没有试图变成它不是的东西。它为团队提供了一种简洁的方式在后台运行 SQL,保持调用会话空闲,在需要时取回结果,并显式管理 worker 生命周期。
更广泛的价值很容易看到:异步执行、自定义事务、结果取回、显式控制、强化安全,以及在真实环境中出现的 production 用例。
1.9 版本在此基础上构建了使日常操作更顺畅的改进类型:标签、结构化错误、结果元数据、批量控制和更广泛的测试兼容性。
面向混合受众的五个实际好处
- pg_background 为 PostgreSQL 提供了一种简洁的方式将 SQL 卸载到后台 worker,而不保持调用会话阻塞。
- 它支持结果取回、自定义事务和显式生命周期控制,这使其不仅仅用于"稍后运行"。
- 它的安全模型是为生产使用而设计的,具有基于 NOLOGIN 角色的访问、特权辅助函数和无 PUBLIC 权限授予。
- 1.9 版本通过 worker 标签、结构化错误、结果元数据和批量操作改进了可观测性和操作体验。
- 记录在案的用例使其与维护、审计日志、ETL、通知和并行工作模式相关,而不仅仅是示例。
升级建议
如果你使用的是 1.8 版本,升级到 1.9 版本会带来有意义的操作改进。如果你正在评估 pg_background 用于新工作,请从 v2 API 开始,并以清晰的标签、显式生命周期控制以及数据库确实是工作发生正确位置的深思熟虑的用例来思考。
如果你在真实工作流中使用它,请分享反馈、提交 issues 并发送 pull requests。好的基础设施不仅在事情进展顺利时有帮助。它在工作时间长、混乱、但仍然需要无戏剧性地完成时有所帮助。