MySQL 的 bug 必须修复吗?兼论海勒姆法则


written by Alex Stocks on 2021/12/05,版权所有,无授权不得转载

昨晚(2021-12-04)邀请在 PingCAP 工作的老弟屈鹏到 dubbogo 社区在线讲解 TiDB,其间讲到 TiDB 并没有百分百兼容 MySQL,因为 MySQL 有些 bug,TiDB 将错就错照着实现了,而有些 bug 实在无法也去照着兼容实现,线上有社区同学质疑道,MySQL 也有 bug 吗?

且不论只要是软件都会有 bug,TiDB 为了兼容 MySQL,其 bug 也要兼容实现,实在令人匪夷所思。其中一些原因是银行在替换 MySQL 的过程中,对 TiDB 进行评估时,判断标准是 TiDB 的计算结果与 MySQL 是否一致,TiDB 为了做到结算结果一致,其中一些功能便会依照 MySQL Bug 一并实现。

1 一次思想实验

这便引申出一个话题,如何在不可靠的软硬件体系之上构建可靠的计算体系。作为吃瓜群众一方,我们可以与银行一方,进行如下思想实验:

吃瓜群众:你们银行方面不知道 MySQL 有 bug 吗?
银行:我们是知道的。

吃瓜群众:那为何不绕开这个 bug?
银行:含有此 bug 的 MySQL 功能很重要,不能因为这个 bug 便舍弃功能不用。诚如不能因为会拉屎撒尿,便否认熊猫不是可爱的动物。

吃瓜群众:换一个 DB 不行吗?
银行:bug 分为已知 bug 和 未知 bug,我们使用 MySQL 已久,它的大部分问题我们是清楚的。如果换一个我们不熟悉的 DB,且不说切换后导致的开发运维成本,新 DB 即便保证在这个功能上无 bug,它肯定还有其他潜在的 bug,因为所有的软件都有 bug。或者说,他们都不会保证他们 100% 不会不出 bug。

吃瓜群众:所有的软件都必然会有 bug?
银行:是的。就算软件不会出 bug,底层的硬件体系也不能保证 100% 不出问题。

吃瓜群众:硬件也不可靠吗?
银行:是的,AWS 就曾出过一次事故,某次阳光紫外线照射到 AWS 的机器内存后,导致一些 bit 位翻转,而新数据的 checksum 恰好又与老数据的 checksum 一致,这便导致了一次严重事故。当然这种事故,大概也只有亚马逊这种级别的公司有实力和意愿去追究发现,如果资损不严重,一般实体是不愿意去深究的。其实我们的整个通信体系也不是 100% 可靠的,譬如 TCP 协议以及其依赖的 IP 层协议包都有一个 checksum 字段,这些都是整个软硬件体系不可靠的明证。

吃瓜群众:那你们银行如何保证我们财产的安全性?
银行:针对 MySQL 已有的 bug,如果该功能非主要功能,则事前规避不用。如果该功能很重要,则调查触发已知 bug 的边缘条件,然后在上层应用中规避。银行每天还会进行常规性的对账,核对资损,通过事后补偿的手段保证资金的绝对安全性。

2 Go sync.Pool

关于 MySQL bug 的话题,让我回想起前一阵子 dubbogo 所依赖的网络库 getty 遇到的一次问题。今年(2021 年) 9 月 11 日【真是一个好日子】集团相关同学反馈 getty “在一个大量使用短链接的场景,XX 发现造成内存大量占用,因为大块的buffer被收集起来了,没有被释放”。

通过定位,发现原因是 sync.Pool 把大量的 bytes.Buffer 对象缓存起来后没有释放。集团的同学简单粗暴地去掉了 sync.Pool 后,问题得以解决。复盘这个问题,其根因是 Go 1.13 对 sync.Pool 进行了优化:在 1.13 之前 pool 中每个对象的生命周期是两次 gc 之间的时间间隔,每次 gc 后 pool 中的对象会被释放掉,1.13 之后可以做到 pool 中每个对象在每次 gc 后不会一次将 pool 内对象全部回收。

所以,Go 官方没有 ”修复“ sync.Pool 的这个 bug ,其上层的 dubbogo 还能稳定运行,当他们 ”修复“ 之后,上层的 dubbogo 运行反而出了问题。

Go 语言 另外一个比较著名的例子便是 godebug=madvdontneed=1。Go 1.12 对其内存分配算法做了改进:Go runtime 在释放内存时,使用了一个自认为更加高效的 MADV_FREE 而不是之前的 MADV_DONTNEED,其导致的后果是 Go 程序释放内存后,RSS 不会立刻下降。这影响了很多程序监控指标的准确性,在大家怨声载道的抱怨后,Go 1.16 又改回了默认的内存分配算法。

3 Gosling 的看法

关于软件的 bug 是否应该修复这个问题,个人的看法是,对于出现的大部分 bug 我们当然需要修复。但对于一些有着悠久历史的 bug,需要慎重对待。

今年(2021 年) 11 月,Java之父 James Gosling 在一次名为 ”你需要的软件可靠性越高,静态类型语言的帮助就越大“ 的采访中,有如下论道。

经常让人感到不适的地方是:如果某个功能存在 bug,人们为这个 bug 采取了变通方法,如果你修复了 bug,你可能会打破这些变通方法。在 Java 世界中,确实有过这样的例子,我们要么决定不修复 bug,要么引入一种正确的方法,这甚至体现在硬件上。

现代的数字世界就是在这套看似 ”不可靠“ 的软硬件体系之上构建出来的。换言之,我们需要做的是,如何在有 bug 的软硬件体系上,构建出一个可稳定运转且最终数据一致的数字世界。即便后退一步,如果做不到数据严格一致,如何把资损控制在最小范围, 控制到我们可接受的范围内。

4 Hyrum's Law

时过境迁,又一年。来到 2022 年,也是我在蚂蚁集团的第四年,还是在干一线。

今年(2022 年)工作内容主要是 DBMesh,明白话就是:实现内部 Java 语言实现的分库分表系统 zdal 【阿里集团的 tddl 的蚂蚁分支版本】的 Go 语言版本,内部命名为 zdas。今天遇到了 SQL 的语义分析问题与同事产生了歧义,zdal 对某个 SQL 的解析明显是个 bug,但既然实现的是一个 “完全兼容” 的系统,我个人坚持在 zdas 中按照这个 ‘bug' 实现了相关语义。我最根本的考量是,用户已经形成了对这个 bug 的认知与心智,如果按照 SQL “正确语义” 去实现,则上层用户的教育成本会非常高,将来线上的 “高 P 故障” 是可以预期的。

其实,业界早有一个 Hyrum's Law(海勒姆法则) 对这种现象予以描述:"With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody",翻译过来就是 "某个 API 接口的语义不是由制定者和实现者定义的,而是首批使用者的理解以及其后续跟随者决定的"。【法则详细内容见参考文档1 】

这种 “bug 兼容” 现象,海勒姆法则【参考文档1 】用一个专业词语予以描述: “bug-for-bug compatibility”,闻之不禁莞尔。

参考文档

Payment

Timeline