3次生产事故
发布时间:最近两周间联遇到三场生产环境服务事故。趁着记忆还热乎,赶紧记录下来。 why what who how howmuch
1. 代码兼容问题
背景是某业务库因为业务需求表结构要变更,相应的为了构造中间表,供给到下一层业务,需要写相应的清洗代码。在旧的清洗代码中,逻辑不能完全覆盖到新的表结构。在产生了一条预期外的数据后,我的清洗逻辑失效,代码抛错。
为什么会发生这种情况?
第一宗罪就是跨部门业务沟通问题,在产品作出重大决策到实施上线前,数据处理相关人员并没有通知到位,导致留给测试和调整的时间不多,几乎是在短短几天内完成修整后的业务逻辑。作为一名最后的数据核验方,对产品决策居然不知情。这是脱离组织的下场。
我的更大的问题在于没有针对异常的数据结构做兼容考虑,清洗逻辑没有覆盖到该类型的数据结构:
// 旧结构
{
"a": "b"
}
// 直接读取然后经过parser
// 新结构
[
{"a": "b"},
{"c": "d"}
]
// 判断不同的key,过不同的parser
// 有一个预期外的key产生,没有相应的parser
第二宗罪就是在编写清洗逻辑过程中,由于业务人员也处于开发阶段,并没有真实的数据源供给测试,”自作聪明”的我选择了mock数据。根据表结构mock数据是一种前置测试的手段,但是完全依赖mock数据作为生产环境最终的测试用例,那就有极大的问题了。部署上线后确实安稳的过完了年,但突然产生预期外数据那天,整个调用链因为一条没有覆盖到的新的数据结构崩溃了。
未来应该怎么做?
- 主动关注产品决策,考虑决策对整个数据流产生的影响
- 要求提前予以验证用例,提前mock测试会导致一些问题,抛出风险
- 代码要足够严谨,针对异常数据做try catch然后保留日志
- 对于要求精确的数据,遇到异常就立马崩溃是正确的,可以根除潜在风险
2. 埋下的历史炸弹
业务耦合不可怕,可怕的是历史上部署的脚本一直跑着,然后突然某一天和现有业务产生耦合。
至少一年前我部署了一个每隔5分钟执行一次的计划任务,负责监控我们的生产库,如果崩溃就主动重启,如果磁盘空间占用率高达95%就主动清理旧业务库数据:
*/5 * * * * cd /home/master/yangcongDatabase/monitorBackup/ && /usr/bin/python ./monitOnMongoDbInstance.py >> /tmp/mongodbInstance.log
听着很合理的一款小运维工具是吗?
直到最近磁盘真的占用率到95%那天……主动清理旧业务库数据开始执行,悬崖勒马挽救了磁盘,但是一年以后,对于业务库的依赖关系已经由一变多,删一发而动全身,删除的部分恰恰有分析人员需要。好多报表都错了。
于是就开始补回被删除的数据。不怕不怕,我们有一款专门的数据恢复工具,就是预防这种情况,将误删的数据从线上重新拉下来。第二颗历史炸弹就是这款数据恢复工具。简单的说是专业的,历史直到现在不断进化的表结构,有些同含义名的字段被改成了其它名,比如a改名叫aa了。新的程序都已经适配了aa的叫法,但是这款古老的数据恢复工具忘了升级这档子事情。
在我半夜拉完数据,重跑验证发现结果还是错误的时候陷入沉思。如果不是偶然的翻阅了几条数据,真想不到是改名导致的。
未来应该怎么做?
- 有任何改动,一定要系统、全局的思考
- 各种临时方案一定要备案,集中写一个“临时方案备忘录”
- 数据流相关的程序定期新旧结合对比
- 慎重决定每一次改名的决策
3. 计划任务耦合
随着业务发展,我们负责的日报逐增。日报的计算依赖于每天业务表同步之后。从开始的单一到多处的耦合,逐步发展成为多耦合多依赖,调用关系不再清晰。在可预见的未来,只会继续增加依赖,且耦合关系会愈发严重。
事故之初,一个重要业务库增量更新失败,导致数据出错后产生报表,没有预警的前提下,已经下发给需求方。
当一个报表的指标发生波动或异常,如果想精确反推到到底是哪一处计算出了问题,已经成为耗时耗力的工作。初步清点后发现每日计划任务数已经有三十余个脚本必须执行,而这些脚本存在链式依赖关系。更坑的是,是各种数据源、各种脚本语言组合而成。一块一块的积木虽然简单,但组合到一起的时候,当任何一块出现问题,整体结构都崩。
应该怎么做?
- 将调用关系梳理成DAG图,感知整体结构
- 在关键卡点做好日志输出
- 监控关键数据源
- 建立血缘关系网,可以通过表面的指标直观的看到到底是来自哪个数据源
关键还是要思考如何解耦,对于分散的东西,一定存在解耦的可能性,而且让部分脚本可以并发起来,减少每日计划任务耗时。