牛马日记之屎山寻宝:上传不慢,慢的是身后那条流水线
记录人:一个亲手写完项目的人
工资:3000/月,交完房租剩得不多,刚好够给服务器续一天电费
信念:文件上传应该比人走路快
简历特长:精通"文件传完不算完,后面还得蒸一遍"
九月十日 · 星期四 · 屎山考古
昨天我还在跟安全组较劲,今天的战场换了:公司说,上传文件特别慢,你去看看。
我以为又是网络、带宽、大文件那一套。看了一圈发现:慢的根本不是上传。
先把山摸一遍。这个项目,Java 8 配 Spring Boot 2.5.15,763 个 Java 文件、约 11.4 万行,还有 81 个 Mapper XML、67 个 SQL 迁移脚本;前端 Vue 3,134 个页面文件、约 7.7 万行,数据库 93 张表。
我复述这些数字的时候,像是在给服务器上坟。
问题只有两个入口,一个叫"编辑议程上传",一个叫"一键上传议题"。两个按钮不同,路却统统汇到同一条河:上传后要同步生成 PDF 预览。Word、PPT、Excel 都会被抬进 LibreOffice 转 PDF,Excel 更惨,先转 ODS 再转 PDF,两步流程,单文件十几秒起步,运气不好直接超时。一键上传 ZIP 更狠,一个包拆出几十个文件,在同一请求里排队挨个转。
用户看到的进度条,其实早就传完了。它在等一堆文件在办公室后面排队盖章。
"上传慢"是个谎言。上传很快,上传之后的流水线很慢。
九月十日 · 星期四 · 真相还有三层
除了 PDF 转换,我又往下挖了三铲,一层比一层经典。
第一铲,分片是串行的。一个大文件切成几十片,浏览器一片一片发,宁可排队也不并行,像只有一个窗口的银行,取号机却是摆设。传完还不算完,临时文件还要被复制一份搬进正式仓库,大文件白白多写一整遍磁盘。
第二铲,N+1。一键上传之后,要给每一位参会人分发文件。代码的做法是:对每个文件、每个参会人,先查一遍再插一遍。一场会一百人,十个文件,就是一千轮查询加一千轮插入。 数据库不知道自己做错了什么,只知道自己的腰越来越疼。
第三铲,是那种最隐蔽的:改完复制为移动之后,文件大小会突然读成零。改代码的人是我,踩坑的人也是我。牛马修路,牛马掉坑,牛马自己爬起来把坑填上。
九月十日 · 星期四 · 手术
今天做的手术不算大,也算不上小。
PDF 预览改成事务提交后后台异步生成,HTTP 请求不再等它;服务器上本来就有三个 LibreOffice 进程,之前像三头拴着不干活的牛,现在终于排队上岗,队列也给批量 ZIP 撑大了。文件先出现在列表里,预览随后到,转换失败也不影响原文件。
分发改成一次查已有记录 + 每 500 条批量插入,一千轮查询变成一轮,一千轮插入变成三趟。
前端分片改成最多 4 路并行,后端锁也从会话级细到分片级,不再互相排队。
分片落盘从复制改成移动,少一次全量写入,顺手把转移后大小读零的坑也补了,还加了一个测试钉死它。
从"不知道哪里慢"到全部改完,一共 2小时24分钟。
三千块的月薪,2小时24分钟干完别人可能摸三天的活。我算了一下,这 2小时24分钟里,我每分钟都配得上这份工资的 0.01% ——四舍五入,值。
尾声
改完之后我做了一遍完整验证:编译通过,相关测试通过,前端生产构建通过,差异检查干净。改动还没提交,先让它们热一热。
现在回头看,今天真正的收获不是"上传快了",而是一个更痛的领悟:
屎山不可怕,可怕的是屎山旁边还修了金碧辉煌的排队区。
这个上传按钮从来都不慢。慢的是它身后那串没人敢动的链:传完文件要转格式,转完格式要按人头分发,分完还要让所有分片排队等窗口,最后再复制一遍。每一环都不算大,环环扣起来,就是用户眼里的"传个文件要半小时"。
我把最粗的几截链子换掉了:异步了,并行了,批量了,移动了。
然后坐在屏幕前,对着那个终于只是"上传"的上传按钮,忽然有点感动。
代码还是那片屎山。但今天,我至少在里面挖出了一条能让文件走过去的路。
——选自《牛马日记》第一卷,"上传"篇。
作者按:本文所有进程数、队列、超时均为实测;本次整改后,文件先落库,预览后补票。
编辑部按:作者改完点了一次上传,盯着进度条看了十秒,确认它不是演给自己看的。
评论
还没有人说话。