网站视觉与性能的 Ruby 平衡术
|
去年十二月那把,推高地前二十秒,我正切后台看 Rake 里 GC 周期抖得跟心电图似的——队友小张在语音里吼:“Ruby 没动静了!” 我手一抖,把 bundle exec rails s —-binding=0.0.0.0 那行删成了 bundle exec rails s —-binding=0.0.0.0:3001,端口写错,服务没起来。键盘右下角那个空格键,声音比别的键脆半拍,咔——跟打野抢龙失败那下音效一样。 我原以为视觉动效加个 lazy loading + Turbo Frame 就能糊弄过去,结果用户划到轮播图那儿卡住三帧,再点“立即购买”直接白屏。不是 JS 错,是 ActiveRecord 的 .includes 后面跟了个未索引的 JSONB 字段,查完一整张表,再扔给 View 渲染——相当于团战开打你还在回城补血,等你闪现进场,人头都转三手了。 室友在旁边煮泡面,水汽糊了我眼镜,我伸手抹,摸到耳机戴久了左耳疼得发胀。这时候大刘突然静音三秒,再开口是干笑:“我刚在 prod 里删了 cache_key 的 prefix。” 他手指在机械键盘上敲得飞快,敲的是 Rails.cache.delete_matched,但删的不是 key,是整个缓存命名空间——连 session 都抖了两抖。我盯着 New Relic 里那根突然拉平的请求延迟线,心想这哪是平衡术,这是拿 Ruby 当绳子拴两头——一端挂满 Webpacker 打包出来的 SVG 动画,一端吊着 PostgreSQL 里八层嵌套的 scope,风一吹就晃。
文章配图,仅供参考 有次跟阿哲对线,在 Slack 里吵了四十三分钟。他坚持用 hotwire-view_components 做渐进式加载,我偏要 render_async + concurrent-ruby 分片预取。吵到一半外卖送到了,我去开门,回来发现他发了条新消息:“刚才我重跑了一遍 asset:precompile,sprockets 报 warning:`legacy_pipeline_enabled is deprecated`。” 我盯着那行字愣了五秒,突然想起来——我段位其实不高,去年才刚把 legacy app 从 Rails 5.2 升完,升完才发现 production.rb 里还有三处 config.serve_static_files = true 没删。充电线不够长,我只能歪着坐,脖子僵得转不动。但有些事必须当场钉死:比如 View 层不能做数据聚合,哪怕只是一行 total = items.sum(&:price)——Ruby 算一百个 item 是快,可当它发生在 layout.html.erb 里、被 every request 走一遍时,那速度就等于中单在高地塔下罚站读条。我改过三次,最后一次砍掉所有 view helper 里的 DB 查询,改成 controller 提前算好,传 hash。不是优雅,是不敢动。因为上次动,就是去年十一月,有个分支叫 feature/refactor-cart-summary,上线后 checkout 页面转化率掉 4.7%,A/B test 数据飘红,老板没说话,运营小林默默把我 Slack 状态改成了“正在深度复盘”。 有人在语音里一直咳嗽,咳得我分神。想起老陈,去年夏天退坑了,说“Ruby 写网页越来越像拼乐高,每块都带说明书,但说明书第 7 页写着‘此处请自行脑补物理引擎’”。他走那天,把自己写的那套 view_component_cache_key_generator 开源了,README 第一行写:“仅供考古,别在 prod 用”。现在我代码库里还留着它的 gemspec,注释都没删。 优胜劣汰——不是算法筛选,是活生生咬下来的。上周 QA 报个 bug:iOS Safari 下轮播图自动播放会间歇性跳帧。我看日志,发现是 Turbolinks 进入页面时触发了两次 turbo:before-render,第二次的 response body 里夹带了未 gzip 的 1.2MB 的内联 CSS(来自一个废弃的 hero_banner.scss)。没人动它三年,直到某天 Webpacker 更新默认压缩策略,顺手把 gzipped 后缀从 .css.gz 改成 .css.br,Nginx 没配 Brotli 回退逻辑……就这么晾着,等用户连上联通 4G,卡顿一秒,然后关掉页面。不是崩,是慢;不是错,是饿。Ruby 不拦你堆 fancy 效果,它只是沉默地给你分配更多内存,然后在 GC pause 那半秒里,悄悄把你的 FCP、LCP、TTI 全按在地上拖行。 小张最后那波高地塔下的反杀,我其实没看清。只记得他闪现躲掉我预判的 redirect_to root_path,然后用一个没进文档的 ActionController::Live::SSE stream 把实时库存推了出去——那不是 Ruby 做的,是 Rack::ETag 中间件在 Response 头里偷偷加了 Cache-Control: private, max-age=0,让浏览器不缓存。我事后查了 log,发现那个 SSE 请求的 response_time 是 87ms,但首字节时间是 392ms。中间那三百毫秒去哪了?答:ActiveSupport::Notifications.subscribe("sql.active_record") 的 block 里,有一行 logger.debug("#{sql} (#{duration})"),而 logrotate 恰好在那一刻锁了 /var/log/app/rails.log…… 耳机左耳还在疼。我调低音量,听见泡面汤在锅里咕嘟。阿哲又发消息:“要不要试试 view_component + cached_by :current_user?” 我没回。把那段没用的 cache_key generator 注释掉了,换了一行 new Redis.new(url: ENV["REDIS_URL"]).setex("view_cache:#{digest}", 600, rendered_html)。没测命中率,先让它跑着。反正网站没崩,视觉也还行,性能嘛……打得挺艰难,但好像还行。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |

