在 PostgreSQL 上构建实时系统:超越 ORM

Atlas Verified 团队
·
2026年2月13日
·
8 分钟阅读
在 PostgreSQL 上构建实时系统:超越 ORM

应用需要实时更新时,2025 年的默认答案是选择托管服务:Firebase Realtime Database、Supabase Realtime、Pusher、Ably 或托管 WebSocket 层。这些服务屏蔽了向连接客户端推送更新的复杂性。它们工作良好 — 直到不再如此。

Atlas Verified 最初采用包含实时订阅的托管数据库平台。系统增长后,我们遇到这一抽象的限制。连接池约束、实时可靠性问题、PostGIS 空间查询需求,以及自定义触发逻辑的愿望,都推动我们朝同一方向发展:掌控数据库层。

我们迁移至自托管 PostgreSQL,并使用其原生 LISTEN/NOTIFY 机制结合 Server-Sent Events 构建实时系统。本篇介绍决策原因、架构如何运作,以及经验。

为什么离开托管平台

托管数据库平台提供很有吸引力的价值:身份验证、行级安全、实时订阅、自动生成 API 和仪表板 — 全部开箱即用。对早期产品,这确实有价值。无需编写任何后端代码,就能构建功能完整的应用。

问题出现在应用成熟、需求偏离平台假设之后。

连接限制。 托管平台通常通过施加自身限制的连接池管理器路由数据库连接。当多个后端服务各自需要连接池 — API 服务器、后台工作系统、实时监听器 — 可用连接很快耗尽。我们发现自己在绕过连接池管理器限制,而非解决实际产品问题。

实时可靠性。 托管平台的实时系统通过 PostgreSQL 预写日志上的变更数据捕获运行。这在架构上很优雅,却引入我们无法控制的故障模式。基于 WAL 的复制存在固有延迟。实时服务偶尔落后,交付过时更新。网络中断后的重连不可靠。而调试这些问题需要访问我们不掌控的基础设施。

扩展要求。 应用处理包含地理数据的贸易文件:港口位置、运输路线、船舶位置。PostgreSQL 空间扩展 PostGIS 对这些查询必不可少。托管平台支持 PostGIS,但我们需要的特定版本和配置并非始终可用。

自定义触发逻辑。 我们需要触发应用特定事件的数据库触发器,而不只是一般“行已变化”通知。文件处理状态变化时,需要通知上传文件的具体连接客户端。托管平台实时功能在表层面,我们需要实体层面。

迁移决定不是成本或原则问题,而是务实选择:绕过平台限制花费的时间,比自行构建功能还多。

LISTEN/NOTIFY:PostgreSQL 隐藏的发布/订阅机制

PostgreSQL 自 2010 年发布的 9.0 版本起,就包含内置发布/订阅机制。虽然已有超过 15 年历史,LISTEN/NOTIFY 仍使用得出奇地少。大多数开发者不知道它存在。

机制很简单。任何数据库连接都可以对命名通道执行 LISTEN 来订阅。任何连接都可执行 NOTIFY,向该通道所有监听器发送消息。载荷是字符串,限制为 8,000 字节。

LISTEN/NOTIFY 为实时应用带来强大能力的原因:

零基础设施开销。 无需部署、监控或扩展单独的消息代理。有 PostgreSQL,就有发布/订阅。对于这一具体场景,无需 Redis、RabbitMQ 或 Kafka。

事务保证。 NOTIFY 消息仅在所在事务提交时交付。如果事务插入一行并发送通知,随后回滚,通知永远不会交付。使用外部消息代理,若无精心编排,就无法获得这一特性。

触发器集成。 PostgreSQL 触发器可在执行中调用 pg_notify。这意味着特定数据变化时,数据库可自动通知监听器,而无需写入路径中的任何应用代码。

局限确实存在,也应理解:

无持久化。 NOTIFY 触发时若无人监听,消息就丢失。没有重放、消息队列,也不保证向断开连接客户端交付。监听器必须处理重连和状态恢复。

载荷大小。 通知载荷的 8,000 字节限制,意味着不能通过 NOTIFY 发送大型对象。我们发送最少载荷 — 表名、操作类型和主键 — 必要时由客户端获取完整数据。

单数据库范围。 LISTEN/NOTIFY 在单个 PostgreSQL 实例内工作,不跨副本或集群。对所有写入都进入单个主库的架构,这并非限制。

我们的架构:触发器、SSE 与通道复用

以下说明各部分如何组合。

数据库触发器 安装在需要实时更新的表上:文件、消息、对话、通知、货运核验等。插入、更新或删除行时,触发器执行并调用 pg_notify,JSON 载荷包含表名、操作类型、主键和任何过滤列。

专用监听连接 在 API 服务器中对通知通道执行 LISTEN,并接收全部触发事件。这是长期专用 PostgreSQL 连接,与查询连接池分开,只负责监听通知。

事件总线 接收监听器通知,并根据订阅路由至连接客户端。订阅特定货运 ID 文件的客户端,仅接收该批货物文件的通知。

Server-Sent Events 向浏览器交付通知。每个连接客户端向 API 服务器建立一个 SSE 连接,指定所需通道和过滤条件。事件总线将传入通知与客户端订阅匹配,再通过相应 SSE 连接推送匹配事件。

为什么选择 SSE 而非 WebSockets

这一决策一直让我们满意。在与我们使用场景有关的每个维度,Server-Sent Events 都比 WebSockets 简单。

设计上为单向。 实时需求是服务器到客户端:数据变化时,服务器通知客户端。客户端无需向服务器发送实时消息,而是使用标准 HTTP 请求。SSE 单向模型符合实际需求。

原生 HTTP。 SSE 连接是带特定内容类型的标准 HTTP 请求。无需特殊配置即可通过所有 HTTP 代理、负载均衡器和 CDN。WebSockets 需要 HTTP Upgrade,有些代理不能正确处理,有些企业防火墙则完全阻止。

自动重连。 浏览器 EventSource API 原生处理重连。连接断开时,浏览器采用指数退避自动重连。使用 WebSockets,需自行实现重连逻辑,而且至少会犯一次错。

WebSockets 胜出的一个方面是双向通信,而我们并不需要。对于需要实时客户端到服务器消息的应用,WebSockets 是正确选择。对于服务器向客户端推送通知的应用,SSE 是正确选择。

不使用 ORM 构建查询层

离开托管平台时,我们也离开其自动生成的查询 API。API 服务器需要数据库访问层。

常见选项是 Prisma、TypeORM 或 Drizzle 等 ORM,或通过驱动使用原始 SQL。我们选择第三条路:轻量查询构建器,提供可链式调用的 API,底层生成并执行原始 SQL。

熟悉的 API。 现有代码库基于托管平台客户端库编写。兼容 API 意味着迁移时尽量少改代码。过去通过平台客户端运行的查询,可以相同语法通过我们的查询构建器运行。

没有黑箱。 ORM 生成看不见、有时无法控制的 SQL。查询缓慢时,需要阅读生成的 SQL,而它往往与产生它的 ORM 调用很不相似。我们的查询构建器生成可预测 SQL — 每个链式方法对应具体 SQL 子句,且始终可查看生成查询。

嵌套关联查询。 贸易数据具有关联性。货运有文件,文件有节点,节点有核验检查。能表达嵌套选择并返回嵌套结果,而无需每次手动编写复杂 JOIN 查询,显著改善开发体验。

结果是一个仅几百行代码的查询层,处理绝大多数数据库交互,并生成始终可阅读和优化的 SQL。

连接池预算

自托管 PostgreSQL 时,您掌控连接预算。PostgreSQL 的 max_connections 设置定义绝对上限,每个连接都消耗内存。

我们发现,将连接视为预算 — 服务之间共享的有限资源 — 能防止未规划团队常遇到的一类问题。

每个服务声明最大连接数。所有最大值之和必须远低于 PostgreSQL 上限,并为临时连接留余量 — psql 会话、迁移脚本、监控工具。这听起来明显,但大多数团队直到生产环境连接耗尽才会做。提前规划连接预算,让我们避免了这类凌晨 3 点的告警。

大规模安全迁移

从托管平台迁移至自托管 PostgreSQL,意味着移动数百个表、函数、触发器和索引。但更大的挑战是建立随着应用演进仍持续有效的迁移系统。

自动迁移系统这样运作:迁移文件以时间戳命名,放在专用目录。迁移执行器扫描目录,与记录已应用迁移的跟踪表比较,并按顺序执行待办迁移。每次迁移在事务中运行 — 若失败,则完整回滚并阻止部署,直到问题修复。

迁移执行器在每次部署、应用启动之前自动运行。这意味着模式变化作为部署流程的一部分应用,无需人工步骤。开发者创建带时间戳的 SQL 文件,推送仓库,下次部署即应用。

确保安全的关键设计:迁移使用幂等保护,部分应用后也不会失败。每次迁移必须是无需人工输入即可运行的独立 SQL 脚本。若迁移失败,事务完整回滚 — 应用不会在模式变化仅部分应用时启动。

我们获得了什么

迁移至自托管 PostgreSQL 并定制实时功能,是重大工程投入,但回报显著:

可靠性。 底层数据变化后,实时通知在数毫秒内到达。没有 WAL 复制延迟,关键路径中没有第三方服务停机。

可调试性。 出错时,我们能追踪完整路径:触发器执行、通知发送、事件总线路由、SSE 交付。每一步都在我们的代码、日志和基础设施中。

灵活性。 为新表添加实时功能,只需一次安装触发器的 SQL 迁移。无需平台配置、第三方仪表板或支持工单。

性能。 经过调优的连接池和直接 PostgreSQL 连接,性能优于通过托管平台基础设施路由的连接。迁移后,查询延迟显著降低。

代价是运营责任。我们管理数据库、处理备份、监控复制和规划容量。对熟悉 PostgreSQL 运维的团队,这是可管理的。对不熟悉的团队,托管平台的抽象可能值得接受其局限。


Atlas Verified 运行于自托管 PostgreSQL,采用定制实时基础设施,每天在全球供应链中处理数千次贸易合规核验。

喜欢这篇文章吗?

与您的人脉共享