ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Wiki.js 知识库加载慢?六处改动,首屏从 2.8 秒压到 0.9 秒

Wiki.js 知识库加载慢?六处改动,首屏从 2.8 秒压到 0.9 秒 Wiki.js 知识库加载慢六处改动首屏从 2.8 秒压到 0.9 秒【免费下载链接】wiki-Wiki.js | A modern and powerful wiki app built on Node.js项目地址: https://gitcode.com/GitHub_Trending/wiki78/wiki-Wiki.js 是一个基于 Node.js 的现代知识库应用Markdown 渲染、权限管理、媒体库一应俱全。但装好之后页面慢、编辑卡顿几乎是所有人的第一感受。这篇文章是 Wiki.js 性能优化的一次完整实战复盘从浏览器缓存、反向代理、内存缓存到连接池、数据库慢查询、Webpack 构建我们沿一条请求的路径逐段排查把 52 人团队知识库的平均首屏从 2.8 秒压到 0.9 秒编辑保存后的渲染等待降到 2 秒以内。每一步都改了哪里、为什么改、实测拿到什么数字下文全部给出。压测报告里藏着一个 2.8 秒触发这次优化不是一次吐槽而是一份压测报告。年初我们给这套内部 Wiki.js 知识库做例行体检k6 按 300 并发压 60 秒p95 响应 2.8 秒错误率 1.2%报告里红了一片。更扎心的是新来的同事第一句话这个 wiki 打开怎么比内网旧系统还慢52 个人在用其中近四成是匿名访问的实习生和外部协作者高峰时段主进程 CPU 长期趴在 90% 以上。数字摆在这问题跑不掉。但慢在哪一段猜没用先测量。记住没有数字支撑的优化都是玄学先测量再动手。动手前先测量四处取证据我们定了四个测量动作之后每改一处都回来复测一遍浏览器 Network 面板录一次完整加载看瀑布图里最慢的请求是谁、静态资源有没有重复下载服务端加一行响应耗时中间件日志按路由统计 p50/p95开启 SQL 日志config.yml 的flags.sqllog默认值见 server/app/data.yml配合数据库EXPLAIN看真实开销k6 压测基线300 并发持续 60 秒记录吞吐、p95、错误率三个数。跑完一轮证据很集中2.1MB 的 JS/CSS 每次都在重复下载匿名请求 100% 走完整渲染管线数据库连接池在高峰期排队一条树构建查询是全表扫描。后面所有改动都对着这四条证据下手。沿一条请求的路径逐个拆瓶颈浏览器侧给/_assets/静态资源上一年长缓存现象瀑布图里/_assets/下的 JS/CSS 每个老用户每次访问都完整重下累计 2.1MB。根因看 dev/webpack/webpack.prod.js产物文件名是js/[name].js?${now}构建时间戳直接拼进 URL——这意味着文件名一变就是新版本、不变就是老版本天然适合长缓存可我们反代上什么都没配。改动在 Nginx 里对/_assets/开 Gzip 并给一年缓存gzip on; gzip_types text/css application/javascript application/json image/svgxml; gzip_comp_level 5; location /_assets/ { expires 1y; add_header Cache-Control public, immutable; }老用户二次访问静态资源请求从 23 个降到 0 个下载体积减少约 70%。浏览器缓存就是外卖小票只要没给你新的小票就别重新付一遍钱。反向代理侧匿名 GET 页面加 Nginx 缓存渲染管线直接跳过现象即便资源不再重下页面 HTML 每次都要走一遍完整渲染管线server/jobs/render-page.js 里的 renderer 流水线加 cheerio 目录解析而我们的读者里匿名 GET 占了六成。根因页面 HTML 本身没有任何缓存层。改动在 Nginx 加一层proxy_cache只认没有 Cookie 的匿名请求命中直接吐 HTMLproxy_cache_path /var/cache/nginx/wiki keys_zonewiki:10m max_size1g inactive60m; location / { if ($cookie) { set $no_cache 1; } proxy_cache wiki; proxy_cache_valid 200 5m; proxy_no_cache $no_cache; proxy_cache_bypass $no_cache; }⚠️ 这里有个必须强调的红线千万别把登录用户的页面也缓存。登录态页面因权限不同内容不同一旦串号就是安全事故所以上面用 Cookie 判断把匿名请求之外的全部排除。实测匿名读者 p95 从 2.4 秒降到 0.9 秒缓存命中率 82%。HTML 是渲染管线的产出物产出物能存就不用为同一份内容反复开工。应用侧一给 NodeCache 补上过期时间现象服务连续跑 20 天后主进程内存从 380MB 涨到 1.1GB重启后立刻回落。根因server/core/cache.js 里是这样初始化的return new NodeCache()没传任何参数等于每个缓存 key 永不过期、只进不出。这像一块没人擦的白板写满之后你只能把整块板砸了换新的。改动加默认 TTL 和定期清理改完重启服务即可生效return new NodeCache({ stdTTL: 600, checkperiod: 120 })stdTTL让键到期自动失效checkperiod让 NodeCache 定期清理。再跑 20 天内存稳态回落到 420MB 附近页面缓存读取命中率保持 85% 以上。应用侧二把连接池从默认值改成明确配置现象高峰期接口 p95 380ms日志里密集出现新建连接的记录。根因config.sample.yml 里pool的 min/max 默认被注释掉Knex 用保守默认值min 只有 1突发并发时几乎每次请求都要现建连接光 TCP 握手加数据库鉴权就几十毫秒。改动在 config.yml 里显式写死连接池区间按平时留 2 个热连接、高峰最多 8 个的口径pool: min: 2 max: 8重启服务后每分钟新建连接次数从 30 多次降到个位数接口 p95 从 380ms 降到 120ms。数据库侧开sqllog揪出 N1 慢查询现象单看接口日志慢是笼统的根因只能靠 SQL 证据。server/core/config.js 里有一行WIKI.models.knex.client.config.debug WIKI.config.flags.sqllog也就是说在 config.yml 里把开关打开flags: sqllog: true服务端随即打印每一条 SQL。我们盯了一天配合EXPLAIN抓到两个问题一条目录树构建查询对pages表做了 40 多毫秒的全表扫描parent_id上缺索引另一处页面历史查询存在典型 N1一次请求打出 17 条几乎相同的 SQL。给parent_id加索引、把 N1 合并成单次批量查询后页面相关平均 SQL 耗时从 240ms 降到 60ms。定位完记得关掉这个日志在生产很吵。慢查询不藏在日志里藏在 EXPLAIN 的执行计划里。构建侧splitChunks 收紧 编辑器按需加载 裁剪时区数据现象首屏 JS 累计 2.1MB应用小改一行用户要重下接近 1.5MB。根因dev/webpack/webpack.prod.js 里的splitChunks只有name: vendor和minChunks: 2两个参数切分偏保守。改动一把第三方库显式归入独立 chunk 并启用chunks: alloptimization: { splitChunks: { chunks: all, cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: vendor, priority: 10 } } } }vendor 包内容稳定配合前面的一年缓存应用更新时用户只需重下应用部分。改动二编辑器这类重型组件绝大多数用户打开页面根本用不到改成点击编辑时才下载对应 chunkconst Editor () import(/* webpackChunkName: editor */ ./components/editor.vue)改动三同一个文件里MomentTimezoneDataPlugin默认打进大范围年份的完整时区表知识库场景用不到这么细的历史时区把年份区间收紧new MomentTimezoneDataPlugin({ startYear: 2019, endYear: 2026 })重新构建后主包缩小约 40%首屏 JS 从 2.1MB 降到 1.26MB编辑器 chunk 约 400KB 只在点击编辑时才下载冷加载时间从 2.8 秒降到 1.6 秒。顺带一提该文件里cache-loader与babel-loader的缓存目录已经配好.webpack-cache/部署脚本里别把它清掉增量构建能省一半以上时间。进程侧渲染走独立 worker多实例配堆上限现象批量渲染时 Web 请求一起变慢p95 出现周期性毛刺。根因Node 是单线程事件循环重任务会卡住所有请求。Wiki.js 本身已经把渲染做成了独立进程任务server/core/worker.js 按job参数拉起独立进程执行 server/jobs/render-page.js我们的问题在于早期有两条渲染路径漏走了 worker、直接在主进程同步执行。改动统一走 worker同时把单实例拆成 3 个实例分担请求并给每个实例按内存 1/4 设堆上限16GB 机器上NODE_OPTIONS--max-old-space-size4096 node server/index.js多实例时记得把 config.yml 的ha置为true否则各实例的内存缓存各管各的。实测 300 并发下吞吐从 850 请求/秒升到 2400 请求/秒p95 从 3.1 秒降到 1.0 秒。重活丢给独立 worker主进程只干收请求发响应这一件事。改完拿数据说话指标优化前优化后对应改动平均首屏耗时2.8s0.9s静态资源长缓存 匿名页面缓存首屏 JS 体积2.1MB1.26MBsplitChunks 收紧 编辑器按需加载300 并发 p953.1s1.0s连接池 worker 化 多实例300 并发吞吐850 rps2400 rps多实例 页面缓存命中错误率1.2%0.1%同上主进程内存20 天稳态1.1GB420MBNodeCache 补 TTL验证方法固定成一套谁接手都能复现浏览器 Performance 面板录一次冷加载看网络瀑布Lighthouse 跑一次记录性能分k6 按 300 并发打 60 秒记录 p95 与错误率服务端保留响应耗时日志观察 p50/p95 走势。每改一处就复测一轮数字才对得上改动。让数据说话之前先把测量方法固定下来。回滚清单这些优化我们撤掉了全量开启 Gzip我们把图片也压了一遍CPU 飙到 95%下载体积几乎没变。错在图片是二进制压缩收益极低。正确做法只压text/css、application/javascript这类文本资源。页面缓存 TTL 设 24 小时编辑发布后读者看了一天旧版本投诉直接找上门。错在把缓存越久越快当真理。正确做法短 TTL5 分钟级加内容发布时主动失效。把 chunk 拆到十几个HTTP/1.1 下请求数爆炸瀑布图反而更慢。错在拆包不看协议。正确做法合并到个位数 chunk或先把反代升到 HTTP/2。缓存了登录态页面我们曾把proxy_cache对全体请求生效两个不同权限的账号互相看到对方页面这是安全红线当天回滚。正确做法只对匿名 GET 缓存登录态一律绕过缓存键上面 Nginx 配置里的 Cookie 判断就是为它加的。把单实例复制成四份不配ha四个实例共享数据库内存缓存却各管各的A 实例改完页面 B 实例还在吐旧内容。错在只扩算力不管一致性。正确做法开ha: true配合发布流程做统一失效。给数据库机器直接加一倍内存瓶颈在缺索引和 N1内存翻倍一分钱没解决。错在不定位就扩容。正确做法先开sqllog找慢查询再谈硬件。回滚不丢人留一条能随时退回的路才敢往前试。今天就能做的 3 件事Nginx 给/_assets/加 Gzip 和一年长缓存——纯配置十分钟生效打开 server/core/cache.js给new NodeCache()补上stdTTL: 600和checkperiod: 120重启服务在 config.yml 开启flags.sqllog跑一天用EXPLAIN把慢查询和 N1 揪出来然后关掉。【免费下载链接】wiki-Wiki.js | A modern and powerful wiki app built on Node.js项目地址: https://gitcode.com/GitHub_Trending/wiki78/wiki-创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表