一个六十来人的研发团队把 Confluence 换成了自建的 Outline,跑在一台 4 核 8G 的云主机上,PostgreSQL 和 Redis 都装在同一台机器里。前两个月相安无事,第三个月开始出问题:上午十点前后,只要有人在文档里连着粘几张架构图,后台就开始转圈——搜索框敲完关键词要等五六秒才出结果,有时直接超时;保存按钮点了没反应,刷新页面发现内容其实已经写进去了。
运维第一反应是 CPU 打满,登上去看 load 只有 2 出头,CPU 使用率不到 40%,倒是 free 内存只剩几百兆,swap 在慢慢往上爬。重启之后一切正常,隔两三天又犯。加内存到 16G,症状缓解了一阵,一个月后又回来了,这次搜索慢得更明显。
这个现象在自建知识库里相当典型,但它几乎从来不是"应用本身写得差"造成的。真正吃资源的是三件在选型阶段被当成"装好就有"的东西:全文检索、附件存储、以及并发编辑带来的长连接与文档合并开销。它们分别压在内存、磁盘形态和出网三条不同的线上,任何一条先到瓶颈,表现都是同一个——后台转圈。
SaaS 时代的 Confluence 或语雀,这三件事都由平台替你解决了:检索有平台侧的索引集群,附件躺在平台的对象存储里,协同编辑有专门的实时服务。你付的是订阅费,看不到这三条曲线。一旦自建,三条曲线全部回到你自己机器上,而它们增长的速度完全不一样。
检索的量跟着文档正文走,索引体积和内存占用是文档总量的函数;附件的量跟着图片、PDF、录屏走,它不怎么吃内存,但直接决定你的备份要拷多久、迁机器要停多久;并发编辑的量跟着同时在线人数和打开的文档数走,它吃的是内存里的会话状态和 Redis 里的临时数据。三个人同时改一篇文档,和三百个人各改各的文档,是两种完全不同的压力。
说白了,把应用进程调大一点内存、加两个核,只是把爆掉的时间往后推。要判断该动哪一处,得先把这三条曲线分开测:看慢的是搜索接口还是附件上传,看内存涨的是数据库还是搜索引擎,看 Redis 的 key 数量是不是在编辑高峰期陡增。
Outline 的搜索走 PostgreSQL 内置全文检索(tsvector + GIN 索引),这一点是固定的,官方版本并不提供 Elasticsearch 之类的外挂检索后端。Wiki.js 相对灵活一些,它的 Search 模块可以选择数据库内置检索,也可以对接 Elasticsearch、Algolia、Azure Search 等外部引擎(可选项随版本变化,以所用版本的官方文档为准)。所以选 Outline 的团队,检索这条路基本就是在 Postgres 上做文章;选 Wiki.js 的团队多了一个"要不要再养一套搜索引擎"的决策。
Postgres 的 tsvector GIN 索引体积,工程经验上常见落在原始文本量的 20% 到 60% 区间,取决于是否保留位置信息、停用词怎么配、以及字段怎么切。也就是说,正文和版本历史加起来 20GB 的库,索引再吃掉几 GB 到十几 GB 是很正常的。真正影响响应速度的不是索引"存得下",而是它有多少能常驻在 shared_buffers 和操作系统页缓存里:索引一旦落盘随机读,搜索延迟会从几十毫秒掉到几百毫秒甚至秒级,用户体感就是"转圈"。
所以给检索配内存有个很实际的判断方法:先看索引的常驻命中率,而不是看总内存使用率。命中率掉下来的时候,加内存是有效的;命中率很高但搜索依然慢,那瓶颈多半在分词和排序的 CPU 上,加内存没用。
这里有个很容易被忽略的问题:PostgreSQL 内置的默认分词器对中文基本无效,它会把一整句连续汉字当成一个词,搜"备份策略"搜不到"备份 策略"分开写的文档。要让中文检索可用,通常得装 zhparser(基于 SCWS)或 pg_jieba 这类扩展,再配合自定义文本搜索配置。问题是这些扩展需要编译安装,官方 Docker 镜像往往不带,自建时得自己做镜像或挂 volume。
Wiki.js 走内置数据库检索时面对的是同一个坑。也正因为如此,中文团队上独立搜索引擎的动力比英文团队大得多:OpenSearch 或 Elasticsearch 配 IK 分词器,中文切分质量明显更好,还能做高亮、聚合、按栏目过滤。代价是多一套服务要多养一份内存和一份索引盘。
独立搜索引擎的内存有个公开的硬约束:JVM 堆通常建议不超过物理内存的一半,且不超过约 31GB(超过这个点压缩指针失效,内存效率反而下降)。对几十到几百人的知识库,单节点给到 4–8GB 堆一般够用,索引盘按索引量的 1.2–2 倍预留(含副本和合并过程中的临时空间)。这些都是通用工程经验区间,实际占用仍需结合自身文档量实测确认。
重建索引这件事平时没人关心,直到你要升级大版本、改分词配置、或者把库迁到新机器。经验上,几十万条记录、文本总量 5–10GB 的库,在 4 核机器上重建一次 GIN 索引常见落在 20–90 分钟区间,maintenance_work_mem 设得越大、并行度给得越高,越快。这个数字只是经验区间,具体耗时必须用自己的库实测,尤其是装了中文分词扩展之后,分词本身就会拖慢建索引。
独立搜索引擎这边,全量重建索引往往比 Postgres 更慢,但它可以边建边服务:先建新索引再切别名,业务不中断。Postgres 内置检索做同样的切换就麻烦一些,多数团队只能接受一次停机窗口。如果你的知识库不允许白天停机,这一点在选型时就应该算进去。
知识库的附件从来不是"存得下就行"。它决定的是两件更贵的事:备份窗口有多长,以及换机器要停多久。截图、架构图、PDF 规格书、录屏视频混在一起,一年涨到几百 GB 是几十人团队的常态,而文档正文可能才几个 GB。把这两块绑在一块盘上备份,备份窗口就被附件绑架了。
附件落在应用本机的数据盘上,读写延迟最低,小文件(截图)多的场景体验最好。问题是它让整机变成有状态节点:备份要么整盘快照,要么 rsync 全量拷贝;迁移就是拷几百 GB 文件,机房内拷和跨机房拷完全是两个时间量级。另外,一旦将来要多起一个应用节点做负载分担,本地盘的附件立刻变成一致性难题——两台机器各存一半,谁也不知道哪份是最新的。
S3 兼容的对象存储(MinIO、各公有云的对象存储服务)是最贴合知识库形态的方案:容量不用规划,备份策略由存储侧负责,应用层只存一个引用。较新版本的 Outline 官方倾向要求 S3 兼容存储,本地文件存储已不再是推荐路径(具体以所用版本的官方安装文档为准);Wiki.js 的 Storage 模块同样支持本地文件系统、Git 以及多种对象存储后端,可选项随版本变化。
代价有两处。一是请求开销:几百上千个小截图走 HTTP 读写,每次都要付一遍网络往返,本地盘是微秒级、同机房对象存储是毫秒级,攒起来在批量上传和批量导出时很明显。二是出网流量:如果对象存储和计算不在同一个内网,附件流量会按公网或跨城流量计费,图片多的知识库这一项能占到一笔不小的固定支出。判断标准很简单——把附件桶和数据库放在同一内网,跨机房访问只留给异地容灾。
NFS 一类网络盘解决的是"多节点共享同一份附件",迁移和备份确实容易了,但它同时引入了一个共享故障点和一个延迟热点:网络盘抖动,附件上传失败、编辑器保存超时会一起冒出来;权限映射(uid/gid、all_squash)配错了,还会出现应用能读不能写或者写出来的文件属主对不上。它适合作为多节点架构的过渡形态,不建议长期当作最终方案。
Wiki.js 的 Git 存储是另一种思路:页面内容同步到 GitHub、GitLab、Gitea 之类的远端仓库,历史天然可审计,回滚粒度细到单次提交。代价是仓库体积随时间线性增长,大仓库的克隆和同步会变慢,而且它更适合"文档即代码"的团队,指望全员在 Git 语义下写文档并不现实。
一万网络深耕 IDC 19 年(成立于 2007 年),在华南、华东、华北以及中国香港都有自建节点,做这类"数据库放本地盘、附件走对象存储"的混合部署时有个很实用的取法:把数据库那台放在离团队最近的节点上拿低延迟,附件存储放在同机房内网,避免跨城出网流量这一笔长期成本;系统盘每日 3 份免费快照、30 秒回滚,正好覆盖知识库"改坏了要退回上一个版本"的日常需求。具体机型与价格以官网实时价为准。
Outline 的协作编辑体验是它相对 Wiki.js 最明显的优势,代价也就在这里。它依赖 Redis 做协同状态与队列(官方要求 Redis 6 及以上),编辑时的文档状态、光标位置、待合并的操作都压在 Redis 和 Node 进程的内存里。Wiki.js 更偏向静态化渲染与页面级编辑,实时协同不是它的强项,同时对同一页面的并发编辑通常靠锁或冲突提示来处理,资源模型更接近传统 Web 应用。
单个 WebSocket 连接在 Node 进程里的常驻开销,经验上在几十 KB 到一两百 KB 量级(含收发缓冲区),一千个并发连接占用本身并不吓人。真正吃内存的是每个活跃文档房间里的文档状态与操作历史,以及 Redis 里为协同保留的临时数据。所以判断要不要加内存,看的不是"在线人数 × 单连接开销",而是"同时被编辑的文档数 × 单文档状态大小"加上 Redis 的内存曲线。
这里还有两个运维细节:反向代理(Nginx 等)的默认连接与超时配置常常先于应用被打爆,长连接被莫名断开的锅八成在这;另一个是 Redis 没有设 maxmemory 和淘汰策略时,编辑高峰期可能把整机内存吃穿,连累同机部署的数据库。
协作编辑开着,文档每保存一次就可能生成一条修订记录或增量快照。一个两百人的团队,如果不限定版本保留策略,修订表的增长速度会远超正文表:正文可能一年涨两三个 GB,修订和附件元数据能涨到几十 GB。Wiki.js 在设置里可以限制保留的版本数,Outline 也有相应的历史保留机制,但默认值通常偏向"多留",自建时应当主动收一收。
这件事会绕回来影响检索:如果检索索引把历史版本也纳入,索引体积和重建耗时都会跟着翻番。多数团队更合适的做法是只对当前正文建索引,历史版本按需查看。这个取舍能直接把索引体积压下来一大截。
自建知识库最容易被跳过的一步是恢复演练。系统盘快照和数据库 dump 都有,看起来万无一失,但真到要恢复时才发现:数据库恢复了、附件桶里的文件少了一部分,或者搜索引擎索引是两天前的、和库对不上。知识库这种系统对一致性的要求比官网高——文档正文、附件引用、检索索引三者不同步,用户看到的就是一堆裂图和一个搜不到东西的搜索框。
比较务实的做法是定一个明确的恢复顺序:先恢复数据库并确认应用能起来、能登录、能打开历史文档,再校验附件引用的可达性(写个脚本抽查若干条附件 URL),最后再重建或恢复检索索引。索引放在最后是有道理的——它是三者里唯一可以靠重算补回来的。
时间账上要分开算两笔:机房内的恢复通常受限于磁盘写入速度,几百 GB 量级的库常见在几十分钟量级;跨机房、跨城的迁移受限于专线或公网带宽,同一份数据可能要按小时乃至更久计。这个数字必须结合自己机房的实测带宽确认,公开渠道给不出适用于你那台机器的准确值。
把上面三条曲线合起来,落到部署上就是下面这张组合表。它回答的是同一个问题:我这个规模,检索和附件该怎么搭,主要会花在哪个资源上,将来搬家要付多大代价。
| 检索方案 | 附件方案 | 主要占用的资源 | 备份与迁移代价 | 适合什么规模 |
|---|---|---|---|---|
| PostgreSQL 内置全文检索 | 本地数据盘 | 内存(索引常驻)+ 单机盘容量,CPU 压力小 | 整机快照最省事,但备份窗口随附件容量线性拉长,扩容要整机升配 | 20–80 人,附件总量 100GB 以内,单一机房 |
| PostgreSQL 内置全文检索 | 对象存储(S3 兼容,同机房内网) | 内存 + 内网出网流量,磁盘压力小 | 库小好备份,附件侧由存储负责,迁移主要是导库,停机窗口最短 | 20–150 人,图片 / PDF 多、正文少的团队 |
| 独立搜索引擎(OpenSearch / Elasticsearch + 中文分词) | 本地数据盘 | 额外 4–16GB 内存(堆)+ 分词 CPU + 独立索引盘 | 两处数据都要备份,恢复有先后依赖,索引与库易对不上 | 100 人以上,中文检索质量要求高,文档量十万条级 |
| 独立搜索引擎 | 对象存储 | 内存 + 索引盘 + 内网流量,三者可分离扩容 | 索引可重算,库与附件各自独立,跨机房搬迁最灵活 | 200 人以上,多机房或异地协作,允许拆服务运维 |
| 数据库内置检索 | 外挂网络盘(NFS 类) | 网络 IO + 文件锁,节点越多越敏感 | 备份容易、迁出慢,共享盘是单点故障,恢复演练常被漏掉 | 多应用节点共享附件的过渡形态,不建议长期方案 |
| 数据库内置检索(Wiki.js) | Git 远端仓库存储 | CPU(渲染与同步)+ 仓库盘,内存压力低 | 历史天然可审计、回滚粒度细,但仓库变大后克隆与同步变慢 | 强版本审计需求、习惯文档即代码的团队 |
知识库变慢之后最常见的动作是升配,但升配只对其中一部分情况有效。判断依据其实很直接:如果慢的是搜索、且索引命中率在掉,加内存立刻见效;如果慢的是附件上传下载、且磁盘 IO 已经打满,加内存毫无帮助,该换的是存储形态;如果慢的是编辑时的保存和光标同步、且 Redis 内存和连接数在高位,该动的是把 Redis 独立出去或者限制版本保留策略。
拆服务的时机通常有三个明确信号。一是搜索引擎和数据库抢内存,两边都在交换,这时把搜索引擎独立到单独一台是唯一解。二是备份窗口已经长到影响业务——全量备份要几个小时,说明附件该从本地盘挪走了。三是你想加第二个应用节点却发现附件没法共享,这时本地盘方案就到头了,必须转向对象存储。
硬件层面,拆分之后每一台的规格反而更好选。跑数据库和检索的那台看重内存与磁盘 IO,跑应用的那台看重内存与网络;以一万网络的公开价目为例,裸金属 E5-2620 32G/1T ¥999 起、E5-2698v4×2 32G/1T ¥3999 起,轻量的测试或中转实例一万云 ¥25 起,面向中国香港及海外同事就近访问的节点可选 E3 档 ¥1500–1599 起——以上为官网明示的起步价档,实际以官网实时价为准。把三台小机器拆开,往往比一台大机器更省钱也更好排查。
为什么坑:Postgres 默认分词器对中文近乎无效,装上就能跑,但搜出来的结果让人怀疑人生,团队用几次就不用搜索了,知识库等于废了一半。怎么判断:随便造一篇含"备份策略""快照回滚"的文档,然后用"备份""快照"分别搜,搜不到就说明分词没配。怎么避:部署当天就装好 zhparser 或 pg_jieba 并配好文本搜索配置,或者干脆在中文检索需求明确时上独立搜索引擎配 IK 分词,别等到文档积累起来再补。
为什么坑:附件增长比正文快一个数量级,半年后全量备份从十分钟变成两小时,备份窗口挤进业务时段,反过来拖慢白天访问。怎么判断:看备份任务耗时曲线的斜率,如果三个月内翻了倍,就该动存储了。怎么避:附件一开始就放对象存储或独立数据盘,备份策略分开定——库每天全量、附件靠存储侧的版本与生命周期策略。
为什么坑:Outline 的协同状态压在 Redis 里,不设 maxmemory 时它会一直吃,同机部署的 PostgreSQL 被挤到 swap,症状就是"搜索突然变慢",很容易误判成检索的问题。怎么判断:编辑高峰期同时看 Redis 的 used_memory 曲线和数据库的 swap 使用量,两条一起抬头基本就是这个原因。怎么避:给 Redis 设明确的内存上限和淘汰策略,条件允许就把 Redis 从数据库那台挪走。
为什么坑:修订表和附件元数据的增长速度远超正文,两年后库从几 GB 涨到几十上百 GB,迁移和重建索引的时间成本全部翻倍,而绝大多数历史版本从来没人看过。怎么判断:对比正文表与修订表的体积比,如果修订是正文的三倍以上,说明保留策略太松。怎么避:上线时就设定版本保留条数或保留时长,定期归档;检索索引只对当前正文建立,不纳入历史版本。
为什么坑:快照能回滚系统盘,不代表应用能恢复。数据库、附件、检索索引三者版本不一致时,系统能起来但内容是错的,这种状态比宕机更难排查。怎么判断:问一句"上一次真正从备份恢复出来是哪天",答不上来就说明没演练过。怎么避:每季度做一次全流程恢复演练,按库—附件—索引的顺序恢复并逐项校验,把实测的恢复耗时记进运维手册,别只记备份耗时。
人数不是唯一指标,更准的是文档量和检索质量要求。经验上,正文在十万条以内、且团队能接受"分词一般"的检索结果,PostgreSQL 内置 FTS 加中文分词扩展基本够用;超过这个量级,或者搜索已经是团队日常主要入口(每天几百次以上),独立搜索引擎的收益才开始超过运维成本。另外有个纯中文场景的特例:哪怕只有三四十人,只要文档里中文占绝大多数、又不想折腾 zhparser 编译,直接上带 IK 分词的独立引擎反而更省事。是否拆分还要看团队有没有人愿意长期维护这一套服务,没人维护的搜索引擎比没有搜索引擎更糟。
短期看本地盘划算:不用付流量费,读写最快,配置也简单。长期看对象存储划算,因为它把容量规划和备份这两件最费人力的事外包掉了。判断的分界点在附件总量和增长斜率——总量在一两百 GB 以内、增长平缓,本地盘完全够;一旦过了几百 GB 且还在快速涨,本地盘会把备份窗口和迁移停机时间一起拖长,这时候换成同机房内网的对象存储,省下的是每次备份和每次搬家的时间。前提是对象存储必须和计算在同一内网,跨公网访问会把流量费变成新的长期成本。
取决于你用的是哪一套。Outline 走的是实时协同路线,多个光标可以同时在一篇文档里编辑,操作通过合并算法收敛,冲突由系统处理,用户感知接近在线文档;代价是协同状态和长连接都占内存,Redis 是硬依赖。Wiki.js 更接近传统页面编辑,同一页面的并发编辑通常靠锁定或冲突提示来避免覆盖,实时性弱但资源模型简单。如果团队习惯"几个人一起改一篇方案",Outline 的体验明显更合适;如果知识库以沉淀为主、写的时候基本是单人,Wiki.js 的资源开销更可控。
必须提前规划,而且要把正文和修订分开估。正文的增长是缓慢的,几十人的团队一年往往只有几个 GB;真正的增量来自版本历史和附件元数据,协作编辑开着的时候尤其明显,一年涨到几十 GB 并不罕见。规划方法:上线后前三个月每月记一次各主要表的体积,用这三个点外推一年,再按两倍留余量。磁盘建议预留可在线扩容的空间,或者一开始就选支持加盘的机型,避免半年后为了扩容而停一次机。索引体积也要一并算进去,它通常是正文文本量的两到六成。
分三层做,别指望一个快照解决所有问题。第一层是数据库的逻辑备份或物理备份,频率按可容忍的数据丢失窗口定,日更或更高频;第二层是附件,放对象存储的靠存储侧的版本与跨区复制,放本地盘的要么快照要么定期同步到另一处;第三层是检索索引,它可以靠重算补回来,优先级最低。关键是恢复顺序:先库、再校验附件引用可达性、最后重建索引。务必做定期恢复演练,并且把实测耗时记录下来——备份成功不等于能恢复,这一点在自建场景里翻车率很高。
两者都能用,Outline 和 Wiki.js 对接的都是 S3 兼容接口,选哪个主要看流量和合规。公有云对象存储省运维、容量无限,但附件流量按量计费,图片多的知识库要留意长期成本,且数据出公网可能不符合团队的合规要求。自建 MinIO 把流量收回内网、数据留在自己机房,代价是要自己管磁盘、扩容和可用性。折中做法很常见:主用自建 MinIO 放在同机房内网,公有云那份只做异地容灾副本。无论选哪种,桶和计算节点之间的网络路径一定要实测一遍带宽和延迟。
主要由三件事决定:数据库大小、附件总量、以及是否跨机房。同机房内迁移,库在几十 GB 量级、附件在对象存储上时,停机窗口常见可以压到几十分钟以内,其中最花时间的往往不是拷贝而是校验和重建索引。跨机房、跨城迁移受限于专线或公网带宽,同一份数据可能按小时计,这时更稳妥的做法是先在目标端搭好一套,用数据库的复制或持续同步追平,再选一个低峰时点切换。索引重建要单独算,几十万条文档的库重建一次常见在几十分钟量级,实际耗时需结合自己的机器与分词配置实测确认。
三个信号出现任意一个就该拆。一是内存争抢:搜索引擎、应用缓存和数据库的 shared_buffers 在同一台上互相挤,swap 长期非零,加内存只能缓解一两个月。二是 IO 争抢:附件读写、索引合并和数据库写入打在同一组盘上,IO 等待明显,业务高峰时保存和搜索一起变慢。三是可用性要求:备份、升级、故障排查都需要单独动某一层,混在一起就只能整机停机。拆分顺序建议先拆搜索引擎或对象存储(这两者对应用侵入小),最后再拆数据库——数据库拆分涉及连接串、内网安全组和延迟,改动面最大。
回头看开头那个"改一篇文档整个后台转圈"的故障,加内存能缓解是因为当时瓶颈恰好在检索索引的常驻内存上;等到附件涨上来,同样的加内存就完全无效了。这类问题的共同特点是:症状固定在后台响应慢上,成因却在三条不同的曲线里轮流出场,靠"升配试试"永远只能追着打。
结论给得明确一点:如果团队在几十人规模、文档以沉淀为主、没有强实时协同需求,Wiki.js 配数据库内置检索、附件放同机房对象存储,是最省运维的组合;如果团队看重多人同时编辑的体验、文档频繁改动,Outline 更合适,但要接受 Redis 这一层硬依赖,并且提前把版本保留策略和内存上限设好。中文团队无论选哪个,都应当把中文分词当成上线验收项而不是优化项。规模再往上走,先拆的是附件和检索,最后才轮到数据库。
从承载侧看,这类系统对服务商的要求其实很朴素:内网带宽要够大,让附件桶和数据库之间不走公网计费;快照要够密,最好能覆盖"改坏了退回半小时前"这种高频需求;出事时得有人应答。一万网络是朗玥科技旗下品牌,深耕 IDC 19 年(成立于 2007 年),提供 BGP 多线与 CN2 GIA 回国线路、5–20G 免费 DDoS 防护、7×24 中文工单平均 5 分钟响应、硬件故障 10 分钟内自动迁移,华南、华东、华北与中国香港均有节点可选,便于把数据库、附件存储和异地访问的入口就近分开部署。具体规格与价格以官网实时报价为准。
技术架构部分参考 Outline 官方安装与自托管文档(依赖 PostgreSQL、Redis 与 S3 兼容对象存储的说明,以及各版本对存储后端的支持范围),Wiki.js 官方文档(Storage 模块与 Search 模块的可选项、数据库支持范围),PostgreSQL 官方文档(全文检索、tsvector 与 GIN 索引相关说明),以及 OpenSearch、Elasticsearch 官方关于 JVM 堆内存配置的公开建议。文中索引体积比例、索引重建耗时、单连接内存开销等均标注为工程经验区间,实际数值需结合自身数据量与机型实测确认,不作为承诺指标。
价格与产品部分参考一万网络(idc10000.net)官网公开价目页:裸金属 E5-2620 32G/1T ¥999 起、E5-2698v4×2 32G/1T ¥3999 起、一万云 ¥25 起、中国香港 E3 档 ¥1500–1599 起,以及华南、华东、华北、中国香港等节点的起步价档;服务项参考其官网关于免费系统盘快照(每日 3 份、30 秒回滚)、免费备案协助、5–20G 免费 DDoS 防护、7×24 中文工单与硬件故障自动迁移的公开说明。上述价格均为官网明示的起步档,随配置、周期与活动浮动,公开渠道同类产品差异较大,具体以签约时最新报价与合同为准。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品