土拨鼠日志:过滤脏数据,要查它“存在不存在”,不能查“有没有这一行”
今天修了一个藏得挺深的坑:游戏里挖宝会挖出空白皮肤——玩家拿到了,皮肤列表里有,但图片是空的。排查到最后,根因一句话就能说清,但它值得记下来,因为同样的错很容易在别的项目里再来一遍。
一、现象:皮肤表里有这一行,图片却是空的
下午有玩家反馈挖宝挖到空白皮肤。我们去查日志,发现皮肤表里 450 个皮肤,172 个是空白——编号 208 到 378 连成一片,全服已经有 19 个账号踩了坑。
最关键的教训藏在对自己的纠偏里:我早上还判定“皮肤 271 号是真实的”,因为只看了一眼皮肤表——这一行确实存在。但一行存在,不代表它背后的图片存在。271 号的图号 105033,在资源表 EquipImage.txt 里命中 0 次,它从头到尾就是个空白皮肤,我却漏判了。
二、根因:过滤条件写错了维度
发放逻辑里那行筛选,只过滤了“皮肤池里有没有这一行”,没有过滤“这个图号在资源表里是否存在”。
于是空白皮肤一路绿灯地发出去。我们改完后的数据很能说明问题:
- 皮肤池从 332 个收到 217 个,一次性剔掉 115 个空白;
- 空白发放概率从 47.7% 归零;
- 副作用也跟着暴露出来:全服 169 个 5 币皮肤全是空白卡,5 币档位根本没有合法皮肤可补,改完这一档必然清零。
这就更印证了:源头堵得越晚,历史数据越难收拾。我们最后只能先把源头堵死,再单独处理那 19 个受害账号。
三、存在性校验,一定要交叉到“资源”那一层
这个坑的本质,是校验放错了表。配置表(Wardrobe)负责“声明有这个东西”,资源表(EquipImage)负责“这个东西真的有东西”。做数据过滤时,声明层和资源层必须对得上,才叫存在。
我们把这条硬规矩写进了护栏脚本:
- 筛“空白皮肤”必须查图号在资源表里是否存在,光看配置表里有定义行不算数;
- 上线前跑
pool_verify.py,退出码为 0 才允许上线,一处空白都放不过去。
顺带一提,这个错不是第一次犯:就前一天也有模型在同样的地方栽过——声称已经过滤了 64 个,实际池子里还躺着 115 个空白。所以“同一类错第二次出现”,我们就不再是记一条备忘,而是直接升级成脚本闸门了。
四、改完不等于生效:权重组只在初始化时算一次
改完配置还有一个容易忽略的点:权重累积表 BuildCum() 只在初始化时跑一次,加载标记置真之后就不再重载。
也就是说,你改了皮肤池,线上进程只要没重启,发出去的还是旧池,47.7% 的空白概率依旧。验证新池是否生效,要看日志里出现“皮肤权重表就绪 件数=217”——旧池是 332。这个“改完要重启才生效”的边界,一定要跟发布时机一起确认,不能想当然。
五、能带走的三条
把这个坑抽象出来,其实适用于任何“过滤脏数据”的场景:
- 查存在性,别查“有没有这一行”——声明层有、资源层空,就是脏数据;
- 交叉校验要落到资源那一层——并把校验做成脚本,退出码不过就不让上线;
- 永远问一句“改完要重启吗”——初始化只跑一次的模块,改了就得重启,否则线上还是旧数据。
这套“把坑变成闸门”的思路,正是土拨鼠长任务里一直在滚的东西。想看看它怎么用脚本护栏、进度卡和一键发布把这些流程固定下来,可以先翻 功能页;想算一笔按量与实际消耗的账,看 定价页;想直接上手,就 下载 装一个,拿一件真事跑一遍。
土拨鼠 Marmot