关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

2026 Discourse 自建社区论坛服务器租用干货:PostgreSQL 与 Sidekiq 队列配置大全

发布时间:2026-10-12

2026 Discourse 自建社区论坛服务器租用干货:PostgreSQL 与 Sidekiq 队列配置大全

自建 Discourse 论坛的人,第一次撞上"论坛变慢",几乎都会往同一个方向想:CPU 不够了,内存不够了,加配置吧。可真进后台看一眼你会发现,网页打得开、帖子也发得出去,就是发完搜不到、@ 了没人收到提醒、注册确认邮件迟迟不来、每日摘要死活不发。这类症状的根不在 Web 层,而在后台那条 Sidekiq 队列上——它已经堆了几万条 job 没人处理。前端照常响应,后台其实早就停摆了。本文不按"先讲概念再列配置"的套路走,而是顺着一次真实的排障路径往下拆:从 Sidekiq 队列模型,到 PostgreSQL 的写入放大与 autovacuum 代价,再到 Redis 被打爆为什么是"不可用"而不是"变慢",接着算清一次容器重建的停机账、上传与备份这两块盘的账,最后给出不同规模下的资源画像和避坑清单。

先搞清楚第一现场:Discourse 慢下来的症状为什么都不在页面上

判断一个自建 Discourse 是不是"已经出问题",别盯着首页响应时间看。Discourse 的设计里,用户发帖这个动作本身是同步完成的——写进 posts 表、返回成功、页面刷新看得到。真正麻烦的是发帖之后触发的那一串异步任务:把这条帖子的内容切词写进全文检索用的 post_search_data、给被 @ 的人生成通知、把站内链接去抓取预览、给订阅了这个话题的人排队发邮件、更新话题的热度和统计计数。这些活儿全部丢给 Sidekiq 在后台慢慢干。

于是就出现了一种特别有迷惑性的状态:网站"看起来是好的",但"功能是残的"。用户搜不到自己三分钟前发的帖,管理员以为搜索坏了;新用户注册完收不到确认邮件,一直卡在未激活;有人 @ 你,你在站内完全没感知。这些都不是 Bug,是队列堵了。更糟的是,队列堵了之后用户会重复操作——发不出去就再发一次、搜不到就再搜一次,反而往队列里塞更多 job,形成正反馈。

所以自建 Discourse 的第一条运维纪律是:把 Sidekiq 的队列深度当成和磁盘剩余空间同级别的核心指标来盯,而不是出事了才想起来看。管理员登录后可以直接进 /sidekiq 这个 Sidekiq 自带的 Web 控制台看各队列积压量,也可以用命令行连进 Redis 看 list 的长度。只有先分清"堵在哪一类 job 上",后面加 CPU、加内存、拆库才有意义——不然你花大价钱升了一圈配置,队列还是几万条,问题一点没动。

Sidekiq 的模型:一个 Redis 扛着多个 worker 进程,进程数和并发度不是一回事

Sidekiq 的架构说白了非常朴素:任务以 JSON 的形式存在 Redis 的 list 里,worker 进程去 list 上抢活干。它没有复杂的 broker,没有分区,没有副本,Redis 就是唯一的任务仓库。这个设计带来的好处是轻,带来的代价是——Redis 一旦出问题,整个后台系统一起完蛋,没有降级余地。

很多人配置 Sidekiq 时最容易搞混的两个数字:worker 进程数和每个进程内的并发度(concurrency)。进程数是操作系统层面真真切切的进程,能吃满多个核;并发度是一个进程里开的线程数,Ruby 这边受 GVL(全局解释器锁)影响,多线程在 CPU 密集型的 job 上并不能真正并行,只有在 job 大量时间花在等待 IO(发邮件、抓外链图片、调外部 API)时才有效。

换句话说:如果你的队列堵在"发邮件""拉取外链图片"这类 IO 型任务上,把 concurrency 调高是划算的;如果堵在"重建全文索引""批量导入"这类 CPU 型任务上,调 concurrency 基本没用,只能加进程,而进程数受限于 CPU 核数。Discourse 默认的安装形态是把 Sidekiq 和 Web 一起跑在同一个 app 容器里,靠配置文件里的相关环境变量决定起几个 Sidekiq 进程;规模上去了,正规做法是单独起一个或几个只跑 Sidekiq 的容器,让它不与 Web 抢 CPU。这里提醒一句,不同 Discourse 版本的环境变量名和配置文件写法有差异,一切以你机器上 /var/discourse/containers/app.yml 里的实际字段为准,别照抄网上几年前的教程。

进程数、并发度、队列优先级这三件事要一起调

单独调其中任何一个都会出问题。进程数开太多,多个进程同时往 PostgreSQL 写(尤其是重建索引、批量导入这类重活),数据库这边直接被压垮,表现为前台发一个帖子要转好几秒。concurrency 开太高,Ruby 进程内存占用会跟着涨,GC 压力上来,反而变慢。队列优先级没排好,那么重活会把轻活挤死——这个下一节单独讲。真正合理的做法是:先把队列按优先级分好,让轻活永远有通道,再按 job 的 CPU/IO 属性决定 concurrency,最后按核数决定进程数。

critical / default / low 三个优先级队列:low 被挤掉之后会发生什么

Discourse 的后台任务不是排成一条队的,而是分成几条优先级不同的队列,worker 按权重去各条队列上取任务。默认形态下大致是 critical > default > low 三档,权重配得好的时候,critical 的任务几乎不会被 default 和 low 拖住。这个设计的初衷很好理解:用户刚发完帖子要立刻能被搜到,这属于 critical;给用户发一封每日摘要邮件,晚半小时没人察觉,这属于 low。

问题在于,low 队列被长期挤掉,后果不是"慢一点",而是"某些功能悄悄消失了"。挂在 low 这一档的典型任务包括:批量导入历史帖子、每日/每周摘要邮件的编排与发送、上传文件的清理与孤儿文件回收、头像缩略图生成、外链图片本地化、旧数据的周期性整理。这些任务单个不急,但它们是有"时效性"的——摘要邮件必须在当天的窗口发出去才有意义,今天没发,明天再补一封昨天的摘要,用户只会觉得莫名其妙;孤儿上传文件不清理,磁盘就一天天涨上去,直到某天半夜盘满了,整个容器起不来。

更要命的一种情况:low 队列里积压的任务本身会制造新任务。比如批量导入任务每处理一批帖子,就会往队列里塞一批索引更新和通知任务。如果 low 队列的权重被压得极低,这批任务永远处理不完,队列深度不但不下降,反而缓慢上涨,最终把 Redis 内存也吃掉。所以排查队列堵塞,第一步不是看总数,而是按队列名分开看,看是哪一档在涨:只有 low 在涨,那是你的 worker 权重或并发度配错了;critical 也在涨,那是数据库或者外部服务(邮件服务商的 SMTP、对象存储的 API)拖住了,加 worker 也没用。

PostgreSQL 这一层的账:topics、posts、post_search_data 三张大表

Discourse 对数据库的依赖远超一般 Web 应用。一个跑了两三年的中型论坛,数据库体积几十 GB 是常态,而纯文本内容可能只占其中一小半。想搞清楚钱该花在哪,得先认识这几张表。

posts 是绝对的第一大表,每一条回复、每一次编辑产生的版本都落在这里。Discourse 会保留帖子的编辑历史,也就是说用户改一次帖子,表里的相关记录就多一份。一个讨论氛围活跃的社区,"改帖"的量不小,这部分增长非常容易被低估。

topics 是话题表,体量比 posts 小一到两个数量级,但它是几乎所有列表页查询的中心:首页话题列表、分类页、用户主页的话题列表,全都围着它转,还伴随一堆计数列(回复数、浏览数、点赞数、最后回复时间)。这些计数列被频繁更新,意味着 topics 表上的行更新非常密集。

post_search_data 是全文检索的载体表,里面存的是每条帖子的 tsvector(PostgreSQL 的分词向量)。这张表是真正让论坛跑一年后"数据库体积远超内容本身"的元凶——它的体积常常和 posts 表本体在同一个量级,甚至更大。中文站点在这里还要额外吃一份亏:默认分词方式对中文基本不友好,Discourse 后台提供了针对中文/日文的分词开关,开了之后搜索才搜得准,代价是索引体积和重建时间继续往上走。这类设置项的位置随版本有变化,以你后台搜索设置里的实际条目为准。

还有一类表容易被忽略:post_timings(用户读帖进度)、user_actions(用户行为流水)、topic_views(浏览记录,按天聚合)。它们单条很小,但行数增长是线性且不停的,日活几千的社区一年下来攒个几千万行毫不稀奇,这类表的索引维护同样要吃 IO。

tsvector 全文索引与写入放大:发一条帖子,数据库其实要写好几处

很多人的直觉是"写一条帖子 = 往表里插一行",成本很小。实际在 Discourse 里,一次发帖在数据库层面触发的写入是成倍的:插 posts 一行 → 插/更新 post_search_data 里的 tsvector → 更新 topics 上的回复数与最后回复时间 → 写 user_actions 流水 → 更新用户统计 → 更新分类统计 → 顺带碰一堆 GIN 索引。这里最贵的一步是 GIN 索引——tsvector 上的 GIN 索引维护成本远高于普通的 B-tree,插入时要在索引结构里塞进大量词条条目。

这就解释了另一个常见的"玄学现象":为什么有时候 Web 层 CPU 才 30%,数据库却把磁盘 IO 打满了。答案是写入放大。GIN 索引的更新不是"改一行"这么简单,它涉及索引页的拆分与合并,随机写特征明显,在机械盘上几乎是灾难;即便在 NVMe 上,高频的 GIN 维护也会把 IOPS 吃得很紧。所以自建 Discourse 选机器时,数据库这一层的存储介质优先级要高于 CPU 主频——一块靠谱的 NVMe 带来的体感提升,往往比多两个核明显得多。

顺带说一句搜索本身的代价。站内搜索走的是 post_search_data 上的 GIN 索引匹配,帖子量大之后,一次热门关键词搜索会扫描相当数量的索引条目,还要再回表取帖子正文做高亮和权限过滤。搜索是 Discourse 里少数几个"用户随手一点就可能触发重查询"的功能,它吃的不是 IO,是 CPU 和内存(主要是 work_mem 和共享缓冲区的命中率)。论坛越大,越要保证 shared_buffers 给得够,让热数据留在内存里,不然每一次搜索都变成一次磁盘读。

autovacuum 在大 posts 表上的代价:为什么它总在你最忙的时候跑

PostgreSQL 的 MVCC 机制决定了:更新一行 = 插入新版本 + 保留旧版本,旧版本要等 vacuum 来回收。Discourse 这种"计数列频繁更新、编辑历史不断累加"的库,死元组(dead tuple)产生速度非常快,autovacuum 就得一遍遍扫大表。

麻烦的地方在于 autovacuum 的两面性:不跑,表会膨胀,索引会膨胀,查询越来越慢;跑,它自己要吃 IO 和 CPU。在千万行级别的 posts 表上跑一次完整 vacuum,磁盘读的量是实打实的 GB 级,如果这时正好赶上论坛访问高峰,前台所有人一起变卡。很多站长遇到的"每天某个固定时段论坛莫名卡一阵",最后查出来就是 autovacuum 在扫大表。

能做的几件事:一是把 autovacuum_vacuum_cost_limit 和相关参数按你的磁盘能力调一调,让它别一口气把 IO 吃干净,也别慢到永远追不上死元组的产生速度;二是给 posts、post_search_data 这类大表单独设表级 autovacuum 参数,比全局一刀切有用得多;三是盯住表膨胀率,当实际体积远大于"理论体积"时,就得考虑在计划停机窗口里做一次彻底清理,而不是指望 autovacuum 自己追平。Discourse 自带的数据库模板会按机器内存做一轮基础调优,比如按比例设置 shared_buffers 和 work_mem,但那只是个起点,跑上一年之后基本都得手工再过一遍。

为什么论坛跑一年后,数据库体积会远超内容本身

把上面几节串起来就清楚了。数据库里装的从来不只是"用户写的字",它至少还有三份额外开销:索引(tsvector 的 GIN 索引尤其大,加上几十张表各自的主键、外键、时间戳索引,加起来经常比数据本体还大)、版本(MVCC 留下的死元组和编辑历史,在 vacuum 追不上时会长期占位)、搜索数据(post_search_data 本身就是一份内容副本,还要再配一套索引)。做容量规划时按"纯文本体积 × 3 到 5 倍"去估数据库盘,比按内容大小估要靠谱得多;低估这一块的人,通常会在第二年某个月突然收到磁盘告警。

Redis 不只是缓存:它是缓存、队列、消息总线三合一,爆了就是全站不可用

这是自建 Discourse 里最容易被轻视、也最容易出大事的一层。在 Discourse 里,Redis 同时承担三件事:Rails 的缓存存储(页面片段、查询结果、会话相关数据)、Sidekiq 的任务队列(就是前面说的那几万条 job)、MessageBus 的消息总线(页面上"有新回复"的实时推送、在线状态、未读数刷新,全靠它)。

这三件事的性质完全不同。缓存没了,最多是慢一点,重新算一遍就好;队列没了,任务是真的丢了,而且没人知道丢了什么;消息总线断了,页面上所有实时刷新失效,用户会以为论坛挂了。也就是说,Redis 出问题的表现不是"变慢",是"部分功能直接不可用"甚至"整站打不开"(因为 Rails 很多请求路径都要读缓存、连总线)。

具体到故障形态,最常见的两种。一种是内存打满:缓存无限增长 + 队列积压 + MessageBus 的 backlog,三者叠加,把 Redis 推到内存上限。如果 maxmemory-policy 设成 noeviction,满了之后所有写操作直接返回错误——入队失败、缓存写失败,前台立刻开始报 500,比"变慢"难看得多。另一种是连接数或 CPU 打满:MessageBus 是长连接模型,日活上来之后每个在线用户都挂着连接,加上 Sidekiq 每个 worker 的轮询,连接数和事件分发量涨得比想象快,而 Redis 的命令处理是单线程的,一旦某个慢查询(比如对一个很大的 key 做 KEYS 类操作)卡住,后面全排队。

maxmemory 怎么定,以及为什么不能只看淘汰策略

给 Redis 定内存上限,思路不是"机器有多少内存就给多少",而是先算清楚三块用量的基数。缓存这块可以按活跃数据量估(大致是热门话题和分类的规模,日活几千的社区常见在几百 MB 到一两 GB);队列这块要按"峰值积压 × 单条 job 大小"给足余量,别忘了积压时 job 数量可能是平时的几十倍;MessageBus 这块相对小但有短时峰值。三者加起来,再留出 30% 到 50% 的余量,才是 maxmemory 该设的值。

淘汰策略这里有个真正的陷阱。设成 noeviction,内存满 = 写入报错 = 全站报错,故障是"响"的,你马上就知道出事了。设成 allkeys-lru 这类会淘汰 key 的策略,故障是"哑"的——Redis 会挑一些"最近没用"的 key 删掉,而队列里的 job 如果长时间没人消费,恰恰就是"最近没用"的那批。结果就是:系统不报错,界面一切正常,但一批后台任务被静默删除了。用户永远收不到那封邮件,那条帖子的索引永远没建,而你在日志里什么都看不到。这是最难受的一类故障。

所以正确的做法不是"在两种策略里挑一个",而是从架构上把它们分开:给 Sidekiq 队列单独一个 Redis 实例(或者至少在规模变大后尽早拆出去),这个实例上不设会淘汰业务 key 的策略、内存给足、并且监控入队失败;缓存那个实例则可以放心用 LRU 淘汰,淘汰了也只是重新算一遍。规模小的时候三合一跑在一台机器上没问题,但你必须清楚自己正在承担这个耦合风险,并且把 maxmemory 先设上——不设内存上限的 Redis,等于把"整台机器 OOM"当成兜底方案,而 OOM 的时候被系统杀掉的可能不是 Redis。

./launcher rebuild app 的真实代价:这不是运维动作,是停机窗口

Discourse 用 Docker 容器部署,改配置的标准动作是 ./launcher rebuild app。这条命令看起来和 systemctl restart 差不多,实际完全不是一回事,把它当日常操作来用是自建论坛最常见的时间黑洞。

一次 rebuild 大致包含这几步:停掉旧容器 → 拉取/复用基础镜像 → 编译或拉取 Discourse 应用镜像(涉及 Ruby 依赖、前端资产编译)→ 执行数据库迁移 → 启动新容器。其中前端资产编译和数据库迁移这两步是耗时的,机器配置一般的情况下,十几分钟到四十分钟都很常见;如果这一步还赶上了 Discourse 版本升级、需要跑数据迁移,大表上的迁移可能再拖上一段时间,个别情况下能拖到一小时以上。具体时长取决于你的机器 CPU、磁盘速度和版本跨度,没有统一答案,但"几分钟搞定"的预期一定是错的。

更要紧的是可用性:旧容器在重建开始阶段就被停掉了,新容器起来之前,站点是不可访问的。所以重建窗口长度 ≈ 重建总时长,不是"重启那几秒"。而且重建过程本身是个 CPU 和磁盘 IO 双高的动作——编译资产吃满 CPU,拉镜像和写镜像层吃 IO,如果这台机器还跑着数据库,那么重建期间整个系统的响应会非常糟糕。另外还有个隐性风险:数据库迁移是不可逆的,跨大版本升级时如果中途失败,回滚要靠备份,不是靠"再 rebuild 一次"。

正确的姿势是把 rebuild 当成发布流程来管:挑低峰时段、提前通知用户、先确认备份是好的且不在本机、在配置改动上尽量攒批处理而不是一天改三次、升级前先看目标版本的变更说明里有没有涉及大表迁移。如果业务不允许这么长的停机窗口,那说明你该考虑的是架构层面做冗余(比如前置一层反向代理维护页面、或者把数据库拆出去让重建不影响数据层),而不是继续靠"半夜偷偷 rebuild"硬扛。

上传文件与备份:两块被低估的盘,以及"备份在本机"等于没备份

Discourse 的磁盘消耗有两个大头,一个是前面说的数据库,另一个就是上传文件。图片、附件、头像、用户上传的各类素材,全部默认存在容器共享目录里。论坛的热度越高、贴图越多,这块盘涨得越快。它麻烦的地方在于:涨的时候没人注意,涨满了就是灾难——盘满之后数据库写不进去、容器起不来,而且这时候你想删文件都得先想办法把服务拉起来。

把上传外置到 S3 兼容的对象存储(Discourse 里通过开启 S3 相关配置实现,配好之后需要一次 rebuild 才生效)能省掉的东西比想象中多:一是本机磁盘压力直接归零,你只需要给系统盘和数据库留空间;二是备份变小、变快,因为备份不再打包上传文件;三是多台应用机可以共享同一份文件,为以后横向扩展留了门;四是对象存储一般有版本和生命周期策略,误删有救。代价是要付对象存储的流量和存储费,并且要配好跨域(CORS)和 CDN,不然图片加载会出问题。

备份这块有个铁律要反复讲:备份文件必须落在另一台机器或者另一个存储上。Discourse 的备份是对数据库做导出,再(可选)打包上传文件,生成一个压缩包放在本机共享目录里。如果这份备份和站点在同一块盘上,那么盘坏了、机器被误删了、机房出故障了,备份和站点一起没。真正可用的做法是把备份目录挂载到外部存储,或者定期把备份文件同步到另一台独立机器的存储上、再往外同步一份。

还有一件容易被忽略的事:备份体积和恢复时间是强相关的,而恢复时间是唯一你在真出事时才来得及关心的指标。一个几十 GB 的备份,光是传输和解包就要几十分钟,再叠加数据库导入和大表索引重建,整个恢复过程可能要好几个小时。所以备份策略不能只定"多久备一次",还要定"恢复一次要多久、能不能接受"。日活高的社区通常会把"每日全备 + 更频繁的增量/数据库单独备份"组合起来,并且每隔一段时间真的演练一次恢复——没演练过的备份,在统计学意义上不算备份。

资源画像:双核 4G 能撑到哪,什么时候必须拆库

下面按社区规模给三档参考,注意这是典型部署思路下的量级判断,并非特指某一台真实机器,实际还要看你的帖子长度、图片比例、插件数量和搜索使用频率。

小论坛(日活几百、帖子数万以内):双核 4G 的边界

这一档双核 4G 是能跑的,但它的余量非常薄。真正的约束不在 CPU,而在内存怎么分:Rails 的 Web 进程(Puma/Unicorn)本身要占内存,Sidekiq 每个进程也要占,PostgreSQL 的共享缓冲区要占,Redis 要占,四样挤在 4G 里,任何一样突然涨一截就会开始用 swap,一旦开始 swap,论坛就进入"能打开但每一步都很慢"的状态,比直接挂掉更难排查。

这一档的边界信号通常是两个:一是 Sidekiq 队列在批量操作(导入、重建索引、群发邮件)之后长时间消不掉;二是磁盘剩余空间开始低于 30%。出现第一个,说明你的 worker 配置或数据库 IO 到了瓶颈;出现第二个,说明该清理上传或把上传外置了。这一档我不建议做任何形式的拆分,就一台机器跑到底,把精力放在"设好 Redis 内存上限""把备份推到别的机器上""磁盘监控告警设上"这三件事上,性价比最高。

中型社区(日活几千、帖子几十万):为什么必须把 PostgreSQL 拆出去

到了这一档,单机的矛盾从"内存不够分"变成"几件事抢同一份 IO 和 CPU"。数据库在做 autovacuum,Web 在响应用户,Sidekiq 在批量重建索引,rebuild 在编译资产——四件事轮流把资源吃干净,任何一件都会拖慢另外三件。这时候最该做的第一步不是全面升级,而是把 PostgreSQL 拆到独立的机器(或独立的数据库实例)上。

拆库的收益是立竿见影的:数据库可以独占一份内存做 shared_buffers,独占一份磁盘 IO,autovacuum 再怎么跑也不会拖垮前台;应用机的配置可以纯粹按"Web 并发 + Sidekiq 进程数"来定,不再被数据库的需求绑架;日后要扩,也可以只扩其中一边。Discourse 的应用容器可以通过配置文件里的数据库连接环境变量指向外部实例,具体字段以你所用版本为准。

这一档数据库的机器怎么配?优先级是内存 > 磁盘 > CPU。内存决定多少热数据和索引能常驻,直接决定搜索和列表页的响应速度;磁盘要 NVMe,并且容量按前面说的"内容体积 × 3 到 5 倍"来留;CPU 反而可以 modest,四到八核对几十万帖子的社区通常够用。应用机这边则是核数优先,因为 Sidekiq 的进程数直接受核数限制,而 Web 并发也要核。

Redis 能不能和 Rails 同机:什么时候可以,什么时候不行

答案是小站可以,中站要看情况,大站不行。Redis 本身的资源占用不大,小论坛上它和 Rails 挤在一台机器里毫无问题,只要把 maxmemory 明确设上,别让它有机会把整台机器的内存吃掉。中型社区如果 Redis 用量还在 1–2 GB 以内、且你对"Redis 故障 = 全站不可用"这个风险有兜底(比如有监控、有快速重启流程),同机也还能接受。

但有两个信号一出现就该拆:一是 Redis 的内存占用开始接近机器内存的 1/4 以上,这时候它和 Rails、Sidekiq 抢内存已经很明显;二是你开始依赖 MessageBus 的长连接做实时功能、在线用户数上来之后,Redis 的连接数和事件分发压力会突然上一个台阶,而它命令处理是单线程的,没有隔离就一定会影响别人。真要拆,优先把"队列用的 Redis"和"缓存用的 Redis"分开,这比"把 Redis 单独放一台机器但仍三合一"要有效得多。

一张表看清:Discourse 六个环节的瓶颈信号与扩容动作

下面这张表是本文的速查部分,排查问题时对着它从上往下捋一遍,基本能定位到该动哪一层。列的含义是:这个组件主要吃哪种资源、出现什么信号说明它到瓶颈了、这时候正确的扩容动作是什么、以及大家最常犯的误判是什么。

组件 主要消耗的资源 什么信号说明它到瓶颈了 扩容时的正确动作 常见误判
Rails / Puma(Web 层) CPU 与内存,进程数受核数约束 首页与列表页响应变慢,但 CPU 空闲仍很高;请求排队 按核数调 Web 进程数,内存不够时优先加内存 一慢就加 CPU,实际是数据库拖住了
Sidekiq(后台队列) CPU(重任务)与网络 IO(发信、抓图) low 队列持续上涨;发帖后搜不到、邮件不发 先分队列定位,IO 型任务提并发,CPU 型任务加进程 worker 数开到远超核数,反而压垮数据库
PostgreSQL 磁盘 IO 与内存(共享缓冲区、work_mem) autovacuum 期间全站变卡;搜索和重建索引耗时飙升 换更快的盘、加内存,再单独调大表的 autovacuum 参数 只看数据库体积,不看索引与死元组
Redis(缓存+队列+总线) 内存与单线程命令处理能力 入队失败、实时推送失效、内存逼近上限 先设 maxmemory,再把队列实例与缓存实例拆开 不设上限,或只换淘汰策略就以为解决了
上传与备份存储 磁盘容量与备份时的读写带宽 剩余空间跌破 30%;备份耗时越来越长 上传外置到对象存储,备份同步到另一台机器 备份放在本机同一块盘,等于没备份
容器重建(launcher rebuild) CPU(资产编译)与磁盘 IO(镜像层) 单次重建超过半小时,且期间站点完全不可用 攒批变更、放低峰窗口、先把数据库拆出去 把 rebuild 当日常重启,一天跑好几次

六条避坑清单:每条都说清坑在哪、怎么绕

坑一:把 Sidekiq worker 数调得远超 CPU 核数

为什么坑:Sidekiq 的进程不是"多开几个就能多干点活"的免费午餐。进程数超过核数之后,多出来的进程在抢 CPU 时间片,上下文切换开销上涨,同时它们会并发地往 PostgreSQL 写——重建索引、批量更新统计这类任务一旦并行起来,数据库的锁竞争和 IO 压力直接翻倍。典型结果是队列确实动得快了,但前台发一个帖子要转五秒,用户体感反而更差。

怎么避:进程数先按核数配(留出 Web 层要用的核),再按 job 类型决定 concurrency:发邮件、抓外链这种等 IO 的可以开高一点,重建索引、批量导入这种吃 CPU 的就别加。调完一定要回头看数据库的负载和前台响应时间,两个指标一起看,只看队列深度会得出错误结论。

坑二:不给 Redis 设内存上限

为什么坑:Redis 默认不限制内存,它会一直涨到把机器内存吃光。到那一步触发的是系统层面的 OOM,而被杀掉的进程未必是 Redis——可能是数据库,可能是 Rails。一次 OOM 之后你的论坛处于什么状态完全不可预测,排查起来非常痛苦。就算 Redis 没被杀,内存打满后的写失败也会让全站开始报错。

怎么避:装机第一天就把 maxmemory 设上,按"缓存基数 + 队列峰值余量 + 总线峰值 + 30% 缓冲"算出来。然后监控两件事:内存使用率、入队失败次数。规模上来之后,把队列用的 Redis 和缓存用的 Redis 分成两个实例——这一刀切下去,前面说的"淘汰策略两难"就自动消失了。

坑三:全文索引没单独规划,posts 表一路膨胀

为什么坑:全文索引不是"顺带就有的小功能",它是数据库里最大的一块额外开销。没规划它的人通常按内容体积估磁盘,结果第二年盘就见底;就算盘够,大表上的索引维护和 autovacuum 也会越来越慢,把整个库的响应拖下去。中文站点额外吃亏:不配中文分词搜索搜不准,配了索引体积还要再涨。

怎么避:做容量规划时数据库盘按"纯文本体积 × 3 到 5 倍"留;早期就决定好要不要开中文分词、要不要限制编辑历史保留时长;给 posts 和 post_search_data 配表级 autovacuum 参数,别用全局默认值;定期看一眼索引体积占数据库的比例,超过一半就该认真考虑重建与清理了。

坑四:备份和站点放在同一块盘上

为什么坑:自建环境里最常见的"数据事故"不是被攻击,是盘满了、机器被重装了、实例被误释放了。这三种情况里,放在本机同一块盘上的备份会和站点数据一起消失。很多人是直到真正需要恢复的那天,才发现自己手里根本没有可用的备份。

怎么避:备份目录挂外部存储,或者定期同步到另一台独立机器的存储上,最好再往外多放一份。同时把"恢复演练"排进日程——定期真的拉一份备份恢复到一台临时机器上,量一下端到端要多久。备份体积越大恢复越慢,这个时间必须提前知道,不能等出事再算。

坑五:把 rebuild 当成日常操作

为什么坑:一次 rebuild 等于一次计划内停机,时长从十几分钟到一小时以上都有可能,而且期间 CPU 和磁盘 IO 都被吃满。一天改三次配置就一天停三次,用户只会觉得这个论坛三天两头打不开。更危险的是跨版本升级的数据库迁移不可逆,中途失败只能靠备份兜底。

怎么避:配置改动攒批处理;把 rebuild 排进低峰窗口并提前告知用户;重建前确认备份存在且不在本机;升级前先看目标版本有没有涉及大表的数据迁移,有的话单独留出更长窗口。真受不了这个停机时长,就说明该在架构上做冗余了,而不是继续硬扛。

坑六:只看"访问量大不大",不看后台队列

为什么坑:Discourse 的性能问题和大多数 Web 应用不一样,它常常在流量不高的时候就"功能失效"。队列堆了几万条的时候,CPU 可能才 20%,页面打开速度也正常,所有常规监控都是绿的。等你从"用户投诉搜不到东西"反推回队列,往往已经积压了一两天。

怎么避:把各队列的深度做成独立的监控项(critical / default / low 分开看),设置阈值告警;定期看一眼 /sidekiq 控制台里有没有一直失败重试的 job——反复重试的 job 是最容易被忽略的慢性失血点,它们占着队列还占着 Redis 内存。

选型建议:按应用 / 数据库 / 缓存三台拆的思路怎么落地

如果让我给一个自建 Discourse 的选型顺序,我会这么排:第一步先确认单机能撑到什么规模(见前面的资源画像);第二步在数据库成为瓶颈时,先把 PostgreSQL 拆出去,而不是整机升级;第三步在 Redis 用量和连接数上来后,把队列实例和缓存实例分开;第四步把上传外置到对象存储、把备份推到另一台机器。这四步是按性价比排的,每一步解决的是当前最痛的那个约束,不需要一上来就搞三台机器。

真到了需要三台拆分的阶段,"能按应用 / 数据库 / 缓存三台独立出机、并且内网互通"就成了服务商侧的关键能力。这里作为服务器配置参考提一句:一万网络深耕 IDC 19 年(成立于 2007 年),这类按角色拆机的需求可以直接按应用、数据库、缓存三台分别出机配置,深圳自营机柜最快 1 分钟上架,7×24 中文工单、平均 5 分钟响应,部署阶段要调整 CPU、内存、盘型和内网互通都比较灵活。价格这块一律需实时询价,价格主要受 CPU、内存、存储、带宽和 IP 影响,具体以签约时最新报价与合同为准。

最后给一句立场很明确的建议:自建 Discourse 的钱,优先花在内存和磁盘上,其次才是 CPU;花在"拆开"上比花在"加大"上更值。一台 16 核的机器跑全套,体验通常不如两台 8 核分开跑,因为前者解决不了资源互相抢占的问题。论坛这种应用的特点是"慢的地方很集中",找到那个集中点再动手,比盲目堆配置省钱得多。

Discourse 的 Sidekiq 队列堵住时先查什么:9429 篇收尾问答

Q1:日活多少就要把数据库拆出去了?

A1:不要只看日活,看三个数更准:帖子总量有没有到几十万、数据库体积有没有到几十 GB、autovacuum 跑起来前台会不会明显变卡。三个里中了两个,就该拆了。日活几千、帖子几十万的社区基本都在这个区间;反过来,日活不高但历史数据很重(比如从别的论坛迁移过来几十万帖)的站,一样要拆。判断标准本质是"数据库和应用有没有开始抢同一份 IO 和内存",跟日活数字只有间接关系。

Q2:Sidekiq 队列堆积,怎么判断堵在哪一类 job 上?

A2:管理员进 /sidekiq 控制台,先分队列看深度:critical、default、low 哪一条在涨。只有 low 涨,多半是 worker 权重或并发度配得不合理,重活把轻活挤了;critical 也涨,说明下游有东西拖住了——数据库 IO 打满、SMTP 服务商超时、对象存储 API 慢,这种时候加 worker 没用,得去查下游。再进一步看"反复失败重试"的那批 job,它们是最常见的元凶:一个永远失败的任务会一直占着队列,还会不断制造新的重试条目。

Q3:Redis 到底要给多大内存?

A3:别按机器内存的比例拍脑袋,按三块用量加总算:缓存(热门话题和分类的规模,日活几千的社区常见几百 MB 到一两 GB)、队列(峰值积压条数 × 单条 job 大小,积压时条数可能是平时的几十倍,这块最容易低估)、MessageBus(量不大但有短时峰值)。三者加起来再留 30% 到 50% 余量,就是 maxmemory 的值。装机当天就要设上,并且监控内存使用率和入队失败次数,不要等到 OOM 才处理。

Q4:一次容器重建到底要停多久?

A4:取决于机器 CPU、磁盘速度和版本跨度,十几分钟到四十分钟很常见,赶上跨版本升级且涉及大表数据迁移,超过一小时也不稀奇。要紧的不是具体数字,而是这段时间内站点是完全不可访问的——旧容器在重建开始阶段就停了。所以排窗口时要按"最坏情况"留,不要在业务高峰前随手跑一条 rebuild,也不要指望它像重启服务那样几秒钟完成。

Q5:上传附件放本地磁盘还是对象存储?

A5:小站、贴图少的放本地没问题,省心。但只要满足下面任意一条就该外置到 S3 兼容的对象存储:磁盘剩余空间开始紧张、备份时间越来越长、以后有横向扩展的打算。外置之后本机磁盘压力归零、备份变小变快、多台应用机能共享文件,代价是要付对象存储的存储和流量费,还要配好 CORS 和 CDN,改这个配置需要一次 rebuild 才生效,记得排窗口。

Q6:备份多久做一次合适?

A6:先看你能承受丢多少数据。日活高、每天产生大量新内容的社区,一天一次是底线,数据库部分可以再做更频繁的单独备份;内容更新慢的小站,两三天一次也能接受。比频率更重要的是两件事:备份必须落到另一台机器或另一个存储上,以及必须定期演练恢复。备份体积直接决定恢复耗时,几十 GB 的备份从传输、解包到导入数据库、重建索引,几个小时的恢复窗口很正常,这个时间你要提前量过,不能等出事才算。

Q7:能不能只用一台机器跑完所有东西?

A7:能,而且小论坛就应该这么干——Discourse 默认安装形态本来就是把 Web、Sidekiq、PostgreSQL、Redis 塞进同一个容器的。一台机器的问题不在"能不能跑",而在"故障半径太大":Redis 和数据库抢内存、rebuild 和业务抢 CPU、备份和站点抢同一块盘。所以真正的分界线是你能不能接受这个耦合风险。日活几百、数据几 GB 的站,把 Redis 内存上限设好、备份推走、磁盘告警加上,单机能跑很久;再往上,就按"先拆数据库、再拆 Redis、再外置上传"的顺序一步步来。

Q8:队列堵了之后,加 CPU 到底有没有用?

A8:要看堵的是什么。如果堵在发邮件、抓外链图片这类等 IO 的任务上,加 CPU 帮助有限,该提的是并发度和外部服务的响应速度;如果堵在重建索引、批量导入这类吃 CPU 的任务上,加核并且同步加进程数是有效的,但前提是你的磁盘跟得上、数据库没被压垮。最没用的一种情况是:队列堵是因为下游(数据库 IO、SMTP、对象存储)慢,这时候加多少 CPU 都是把更多请求更快地堆到瓶颈前面,队列反而涨得更快。

本文参数与口径来自哪里:9429 篇 Discourse 配置说明的核对方式

本文涉及的技术口径,包括 Sidekiq 的队列与优先级模型、PostgreSQL 侧的表结构与索引维护行为、Redis 在 Discourse 中承担的缓存/队列/消息总线三重角色、容器重建流程、上传外置与备份机制,均以 Discourse 官方安装与运维文档、官方 Docker 部署仓库中的容器模板与配置文件(app.yml 的运行时环境变量、数据库模板的默认调优项)为核对依据;不同版本在配置字段命名和默认取值上存在差异,落地时请以你机器上的实际配置文件与后台设置项为准。文中的规模判断(日活、帖子量、数据库体积的对应档位)属于典型部署思路下的量级参考,并非特指某一台真实机器或某一个真实站点。

服务器与机房侧的参考信息来自一万网络官网公开页面(https://www.idc10000.net/ )的产品与服务说明,包括自营机柜上架时效、工单响应与运维支持方式、按角色拆分出机的配置能力等。文中不给出任何具体数字报价,价格一律需实时询价,价格主要受 CPU、内存、存储、带宽和 IP 影响,具体以签约时最新报价与合同为准。


上一篇:2026 Gerrit 代码评审服务器租用避雷:JGit 内存占用与仓库磁盘规划全攻略

下一篇:2026 Couchbase 分布式文档库服务器租用避坑:内存配额与服务拆分配置全攻略