这套程序升级之前会先备份。把要被替换的文件复制一份到备份目录,然后再动手。
我一直觉得这一步挺稳妥的。直到我把它的三处细节连起来看了一遍。
备份目录里只有最近一次
备份的文件直接放进备份目录,不加任何区分。而下一次升级开始的时候,同样的文件会再复制一遍,名字一样,就把上一份盖掉了。
结果是备份目录里永远只有「最近一次升级之前」的状态。上一次的、上上次的,全没了。
有意思的是,我做归档的时候想到了这一层。脚本执行完要归档,归档目录我是用当天日期拼出来的,不同日子各存一份。备份这一步的写法跟它几乎一样,就差那一行日期。
我漏掉了那一行。
第二条是,失败了也算成功。复制一个文件,成功没成功我连看都没看;失败了抛异常,我只记一行,然后继续复制下一个,最后这一步照样算成功。
所以备份一个文件都没拷成的时候,界面上是绿的。
这一条我在另一篇里说过。放在这里说的意思不一样,它意味着你没法确认备份到底有没有做。界面不会告诉你备份是空的,你要想知道,得自己打开备份目录去看。
最根本的一条
前两条都能改,改起来都是几行的事。真正让我没法退回去的是第三条。
备份的是文件。升级的时候改的不只是文件,还有数据库里的内容,那些脚本会往库里加表、加字段、插数据。
数据库不在备份范围里。
所以升级失败之后,就算文件全部退回了原样,库里已经跑掉的那部分改动还在,退不回去。而那些改动跟退回来的旧文件对不上,系统处在一个两边都不匹配的状态。
更要命的是,脚本执行完要归档,归档没做的话脚本还留在待执行的地方。下次再升级,它们会被再执行一遍。加一列的脚本跑第二遍会报错,插一行数据的脚本跑第二遍会插出两行。
我现在的看法
备份不是目的,能退回去才是。
我当初做备份的时候,想的是「万一要还原,有东西可以拿」,这个想法本身没错。但我没有往下推一步,真出事的那天,我要怎么退? 退哪些?退到哪一次?库怎么办?
推一步就能发现,文件能退、库退不回,这个备份只能算做了一半。
四年没出事,是因为升级失败的次数本来就少,少到没撞上这个缺口。这不是设计得好,是运气。
现在要补的话,顺序我排好了,先让备份失败真的算失败,再给备份目录加日期,最后才考虑数据库那半边。前两条几行就能改,第三条要重做整个流程,得单独安排。
这个工具本身后来开源出来写成了《部署器开源记 · 第 1 篇 · 发版那五步,我为什么没做成命令行》:发版那五步,我为什么没做成命令行。