Hacker News 中文摘要

RSS订阅

扩展Postgres队列的规模 -- Making Postgres queues scale

文章摘要

文章探讨了如何优化PostgreSQL作为消息队列的性能,通过改进锁机制、批量处理和索引设计,使其在高并发场景下也能实现高效扩展,满足大规模任务调度需求。

文章总结

好的,这是根据您的要求,对原文主要内容进行的中文重述,已保留关键细节并删除了与主题无关的内容(如Cookie声明、导航菜单、页脚等)。


标题:Postgres 队列真的能扩展

核心观点: 传统观点认为,基于 Postgres 的队列无法应对大规模工作负载,需要依赖 RabbitMQ 或 Redis 等专用队列系统。但通过正确的优化,Postgres 完全可以胜任。本文将展示如何优化 Postgres 队列,使其在数千台服务器上实现每秒 30,000 次工作流执行。

经验一:重新发现 SKIP LOCKED

解决多工作线程(Worker)同时出队(Dequeue)同一工作流时的竞争问题是关键。在 FIFO 队列中,每个工作线程会查询并处理最旧的已入队工作流。当多个线程并发执行此操作时,它们会看到相同的旧工作流并尝试同时出队,导致大部分线程失败并重试,形成瓶颈。

Postgres 的 FOR UPDATE SKIP LOCKED 子句解决了这个问题。该查询会锁定选中的行,并跳过已被其他线程锁定的行。这样,工作线程可以无竞争地并发拉取新工作流:一个线程锁定最旧的 N 个,下一个线程锁定接下来的 N 个,以此类推。没有 SKIP LOCKED,扩展能力被限制在每秒约 100 个工作流;有了它,Postgres 可以扩展得更远。

经验二:注意事务隔离级别

当出队操作超过每秒约 1000 个工作流时,频繁出现“序列化失败”异常,导致大量重试。原因是事务隔离级别设置为 REPEATABLE READ,它虽然能支持全局队列限制(如“所有工作线程最多同时运行 N 个工作流”),但在高并发下,多个线程修改重叠行时,Postgres 会中止其中一个事务。

关键发现是,大规模队列很少使用全局流量控制,用户更倾向于使用本地限制(如“每个工作线程最多运行 10 个工作流”),这不需要跨线程协调。因此,将隔离级别改为条件判断:使用全局流量控制的队列继续使用 REPEATABLE READ,不使用的则使用 READ COMMITTED,从而完全消除序列化失败,大幅提升吞吐量。

经验三:索引并非免费

当工作流执行速度超过每秒约 8000 次时,出现了新的瓶颈:高 CPU 使用率。这源于出队查询本身和 Postgres 的自动清理(Auto-vacuum)过程,其根本原因都是低效的索引。

工作流状态表上的多个二级索引,特别是为出队查询设计的索引,虽然能快速找到所有已入队的工作流,但不会按特定顺序返回,导致 Postgres 需要额外的排序步骤,增加了 CPU 消耗。同时,维护大量索引成本高昂,每次状态更新都需要更新所有索引,后续的自动清理也会消耗大量 CPU。

解决方案是让索引更具选择性。首先,更新主出队索引,使其不仅按队列名称返回所有已入队工作流,还能按优先级和时间戳排序。其次,将其转换为部分索引(Partial Index),仅在工作流状态为“已入队”时才维护。这带来了两个好处:出队查询不再需要昂贵的排序步骤;工作流出队后,Postgres 可以直接删除其索引条目,减少了维护和自动清理成本。同样的原则也应用于大多数可观测性索引,例如,仅对有父工作流的工作流维护父工作流 ID 索引。

这些优化综合起来,大幅降低了 CPU 使用率,使队列能够扩展到每秒超过 30,000 个工作流(即每月 800 亿次)。

评论总结

根据评论内容,主要围绕Postgres作为队列系统的可行性、性能问题和实际应用展开讨论,观点存在分歧。以下是总结:

观点一:Postgres队列可扩展,且已成熟 - 支持者认为传统观点已过时,Postgres队列系统(如SolidQueue、Oban)已证明可扩展。 - 关键引用:dewey "The conventional wisdom around Postgres-backed queues is that they don't scale... That might have been the case 10 years ago.";sorentwo "They certainly do... sustained 12k/s with a p99 under ~100ms."

观点二:存在性能陷阱,尤其是死元组膨胀问题 - 批评者指出,Postgres的MVCC机制导致更新/删除操作产生死元组,影响查询规划器性能,需激进配置自动清理。 - 关键引用:atombender "A performance pitfall... is the bloat problem... dead tuples start to accumulate... affects the planner's ability to make good choices.";atombender "PgQue is explicitly designed to solve the bloat problem, by avoiding tuple deletion."

观点三:实际应用中有成功案例,但需谨慎评估规模 - 部分用户认为Postgres队列适用于中等规模(如1000并发任务),但高吞吐场景下可能面临争议。 - 关键引用:hiyer "For the scale of the question - 1000 concurrent jobs - I argued that postgres would easily scale.";rtpg "anybody here use PG for queues at a system with reasonable throughput, without regretting it?"

观点四:替代方案(如SQS)被推荐,但非绝对 - 部分场景下,面试官或团队倾向于使用专用队列服务(如SQS),而非Postgres。 - 关键引用:hiyer "the interviewer... felt it wouldn't and wanted me to go with sqs instead."

其他观察 - 评论者richwater指出此类讨论反复出现("Reading Postgres queuing posts always seem like deja vu"),表明话题热度高但观点固化。 - 评论5(rcleveng)询问发布机制,与主题无关。