Postgres LN 扩展性优化:2.9K 到 60K/秒

如果你写过 Postgres 的 LISTEN/NOTIFY,大概率听过一句忠告:这东西不能扩展,生产环境慎用。

这个标签不是凭空来的。你看到过流行的 benchmark 显示它撑死了每秒 2.9K 写入------对大部分后端场景确实不够看。

但 DBOS 团队用一套优化把这个数字推到了 60K 写入/秒(20x),延迟 15-100ms。更值钱的是 Hacker News 上 300 多分的讨论,从多个角度挖出了幕后真相。

问题是:60K/s 意味着 LISTEN/NOTIFY 真的「扩展了」吗?还是说藏着什么条件?

一、LISTEN/NOTIFY 的常见应用

sql 复制代码
-- 订阅端
LISTEN channel_name;

-- 发布端
NOTIFY channel_name, 'payload';

NOTIFY 不是在调用时立即发送,而是在 事务提交之后 才发出。事务回滚→通知不发,保证了事务一致性。典型用途:

场景 做法
缓存失效 数据变更后 NOTIFY → 订阅方清除本地缓存
实时通知 新消息写入后 NOTIFY → 在线用户收到推送
流式处理 新数据到达后 NOTIFY → 消费者批量拉取
实时协作 消息写入后 NOTIFY → 其他参与者收到更新

但问题就出在这个事务提交通知机制上。

二、全局锁:2.9K/s 的罪魁祸首

sql 复制代码
-- naive 实现:每行 insert 都 NOTIFY
CREATE FUNCTION notify_stream_chunk() RETURNS trigger AS $$
BEGIN
  PERFORM pg_notify('stream_channel', NEW.id::text);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DBOS 最初就是这样做的,然后发现无论你怎么调参数,吞吐始终卡在 2.9K/s,但 CPU、内存、IOPS 全部没有负载。

根因:NOTIFY 的事务提交需要拿全局排它锁(LWLock:NotifyQueueLock)。持有到 fsync 完成才释放。

为什么要有这个锁?因为 PG 要保证通知按事务提交顺序发送。但「提交顺序」在提交完成后才能确定。PG 的解法:用锁串行化所有含 NOTIFY 的提交。后果是 group commit 失效,每个 fsync 都变成了一场串行等待。

顺带一提,Postgres 19 即将发布的 patch 不解决这个问题------它优化的是多 channel 场景下 listener 的唤醒效率,全局锁还在。

json 复制代码
// 瓶颈速览
{
  "naive 吞吐": "2.9K 写入/秒",
  "瓶颈": "LWLock:NotifyQueueLock(全局排它锁)",
  "后果": "group commit 失效,每个提交串行 fsync",
  "PG 19 patch": "只解决多 channel 场景,不涉及全局锁"
}

三、优化方案:buffer + batch + fallback

核心洞察:NOTIFY 只是信号(ping),不是数据本身。 缓存失效场景下,客户端收到通知后会去查数据库拿最新数据。通知丢了一个,等到下次 polling 也能补上。它不需要全局有序,也不需要完全持久化。

第一步:内存 buffer

不直接 NOTIFY,把通知放到内存队列里。

第二步:批量 flush

后台线程每 50ms 把 buffer 一次性 flush 出去。全局锁只在 flush 时拿一次。

第三步:降级轮询

读者除了 LISTEN,每 30 秒 polling 兜底。防止进程 crash 导致通知丢失。

python 复制代码
# 伪代码:核心逻辑
notify_buffer = []

def flush_notify_buffer():
    if not notify_buffer:
        return
    ids = notify_buffer[:]
    notify_buffer.clear()
    db.execute("SELECT pg_notify('channel', $1)", [json.dumps(ids)])

四、60K/s 值得庆祝吗?

方案 吞吐
Naive trigger → NOTIFY 2.9K/s
Buffer + batch + polling 60K/s

但是 HN 评论区挖出了一个你需要注意的关键信息:benchmark 跑在 96 vCPU、384GB RAM 的机器上。 所以 60K/s 是巨型单机压出来的上限。有人直言:「这么大的机器才跑 60K,恰恰说明这个方案扩展能力有限。」

另外还有一些值得关注的边界:

  • 进程 crash 丢通知:30 秒 polling fallback 对某些场景不可接受
  • 单慢 reader 阻塞所有 channel 的写入------全局队列是共享的
  • 8000 bytes payload 上限:不适合传大消息
  • 代码复杂度增加:buffer + timer + polling fallback 的维护成本和加一个 Redis 差不多

有人在 HN 上说:「我在生产里用过,高负载下会丢消息。」还有人说:「看完这个优化代码,第一反应是这能调试吗?」

五、工程选择

场景 推荐度 理由
缓存失效通知 ✅ 推荐 丢了 polling 能补,最自然场景
消息队列/流式 ⚠️ 可用 buffer 方案可上,但要权衡复杂度
实时协作/编辑 ❌ 谨慎 buffer crash 丢通知不可接受
精确一次传递 ❌ 不推荐 上 Kafka / Pulsar

如果你每秒写入低于 2000,naive 实现完全够。到了瓶颈再上 buffer + batch 方案,约 50 行代码。

六、总结

LISTEN/NOTIFY 的「不可扩展」标签不是谣言------全局锁确实有硬限制。但你仔细看下来会发现,它也不是废物。当你的业务天然就是「通知可丢,polling 能补」的场景,用它比多维护一个消息中间件省心。

关键是:你技术决策别只读一个标题就下结论。你真正需要的是读懂瓶颈在哪,对号入座。那 50 行代码写得值不值,取决于你是在给 2000 写入/秒的系统加优化,还是为一个 100 写入/秒的系统提前操心。


原文:www.dbos.dev/blog/postgr...

HN:news.ycombinator.com/item?id=490...

相关推荐
李剑一1 小时前
Kimi K3太牛了!但老板说API太贵,让我本地化部署,我算完成本,他涨红脸沉默不语了!其实成本不高,三千万足矣。
aigc·ai编程
颜进强2 小时前
从零搭建私人 RAG 实战:用 Markdown 沉淀技术决策与业务决策
前端·后端·ai编程
怕浪猫3 小时前
第8章 前端交互与可视化:构建Agent的用户界面
aigc·openai·ai编程
鱼樱前端3 小时前
AI 会不会取代前端?我看了 2026 年 7 月整个市场,给你一个不吓人的答案
前端·程序员·ai编程
孤狼GPT3 小时前
ChatGPT、Codex与Pro:AI开发为什么正在从“模型选择”转向“工作流设计”?
chatgpt·ai编程·codex·chatgpt pro·工作流设计
鱼樱前端3 小时前
别再"学工具"了,先搭你的 AI 工作流
前端·ai编程·前端工程化
寒水馨5 小时前
Linux下载、安装 Codex CLI(附安装包codex-x86_64-unknown-linux-musl.tar.gz)
linux·openai·终端·ai编程·codex·智能体·codex cli
神奇霸王龙13 小时前
国产音乐视频 Prompt 三段式屠夫榜:5 个国产视频模型实测对比
人工智能·ai·prompt·音视频·agent·ai编程·agi
神奇霸王龙13 小时前
AI视频Prompt结构化实战指南:可灵/万相/豆包
人工智能·ai·prompt·aigc·音视频·ai编程