非线性规模问题
2023-06-05
我发现很多人都不理解"规模问题",或者只模糊的理解,缺少更深刻的"洞见"。可以这么说,复杂系统,其复杂的原因,规模问题占很大比例。
二分排序
最基础的排序方式,算法复杂度是 O(n2),这意味着它不是一个线性的。随着 n 的增加,消耗的时间是多项式增长的。
将 10000 条数据排序,其代价是 100002。如果我们把数据拆分成两半,分别对 5000 条数据排序,再做一次归并,代价是 50002 + 50002 + 归并代价。归并代价是线性复杂度的,所以 50002 + 50002 + 归并代价 < 100002
二分排序通过不断拆小问题的规模,直接将 O(n2) 复杂度的算法,变成了 O(nlogn) 算法。这就是规模效应的体现:当我们知道这个问题是非线性复杂度的,它会随着规模变大而变得更复杂。拆小规模则可以简化问题。
delete n 的 batch
数据库里面有个删除操作,delete from t,我们一般要求用户分页一批一批的删除,循环多次的执行 delete from t limit N,那么为什么不一次删除全表?谁告诉我为什么?因为删除代价不是"线性"的。
- 假设每次是 delete from t limit 1,删除 10000 条记录需要 10000 次的网络交互,以及 10000 次的语句执行时间;
- 假设每次是 delete from t limit 10,需要 1000 次的网络交互,以及 1000 次执行时间;
limit 1 和 limit 10 的语句执行时间在这两个场景下,都不是大头的,网络交互次数占主导地位,所以从 limit 1 到 limit 10 可能有近 10 倍的提升。但是你应该敏锐地注意到,语句执行耗时随 limit n 的 n 增长不是线性的,所以这就会导致 batch 的大小选择会有一个最优值,而不是无脑增加:由于执行时间的非线性,n 的增大带来的负担会很快抹掉减少网络关互次数带来的好处。
go runtime
我一直觉得 go runtime 就不是多核 scalable 的。如果它是真的线性 scalable,那么表现应该是随着核数增加,部署单个大的 go 进程得到线性的性能提升。但是经常观察到一个佐证是,随着核数变多,往往需要多个 NUMA 绑核的部署形式才能取得更高的吞吐。 Go 在调度、垃圾回收的设计方面都没有关于 NUMA 的考量,而现代 CPU 架构下,一个核心对于自己核心的内存和远端的内存访问速度是不一样的。这些硬件因素都会影响到规模问题是否线性。
分布式系统
十年前,NoSQL 很火,kv存储系统都喜欢做成最终一致性的。为什么?因为这样更容易实现出高性能,线性可扩展的系统。分布式系统本身的复杂性就远高于单机了,直到 paxos/raft 被大众所熟知。CAP 不可兼得,如果倾向性能和可用性,就要在 C 上面妥协。
分布式共识解掉的问题是 C 的实现,但并不是解掉了 scale 问题。就说一些很浅显的不能够被 scale 的部分,比如 n 个节点,相互之间都需要通讯,这个代价就是 O(n2) 的,如果你说这些节点之间都不用通讯了,那就不算是分布式系统了。就拿 multi-raft 来说,它随规模的增长,节点间心跳代价就不是一个线性的。
再比如说,kv 系统设计时都希望业务是对 key range 均匀访问的,这样只需要加机器就可以支撑更高的吞吐,假设存在热点,那么这个 scale 能力就没了。现实世界中呢?热点是必然存在的,所以线性 scale 只存在于假想中。
feature 矩阵
我们产品设计之初,迭代很快。后来 4.0 5.0 6.0 7.0...渐渐越来越慢了下来。看着那一坨代码,心里直呼:改不动了。
为什么?因为 feature 越加越复杂了,每个 feature 可能和多个其它 feature 有交集,组合爆炸了。如果一个 feature 它只需要考虑它自己,那太好了。但现实是,一个新 feature 得小心翼翼地想,它和其它 feature 是怎么交互的,改动是否兼容。它和其它外部组件是怎样交互的,是否兼容?会不会有某个设计打破了显式或者隐式的一些约束?测试组合也是爆炸的,可能测试 A 没问题,测试 B 没问题,但是当 A 和 B 一起使用,就出现问题了。
可以看到,feature 矩阵就非线性规模问题,它是 O(n2) 的,而不是 O(n) 的,产生了 1 + 1 > 2 的效果。
有些技能层面的,还可以在技术层面简化规模问题,而 feature 是产品层面的,这个客户说,我需要这样的功能。另一个客户说,我需要这样的功能,都必须得加加加,那没法子,复杂化是必然的。
KISS 哲学等其它
规模问题还可以推导到很多技术之外的领域,上升到哲学。Unix 哲学里面,有很(最)重要的一条,叫 KISS,意思是 keep it simple, stupid。
它的本质,其实是从哲学层面,去规避掉非线性规模问题。只要将规模做小做简单,那么复杂的东西也是会是 simple stupid 的。 你看,它的工具设计都是只做一件小事情,并将这件事情做好。通过小工具的组合,去完成复杂的任务。sed grep cat less ... vi ... 等等等,而不是大而臃肿的 IDE。
其它的,为什么公司规模对其组织架构形态有很大的影响?为什么提 "小而美" 的公司,而不是 "大而美",因为管理的复杂度就不是线性的。当规模上来之后,就必须拆部门,划多个层级,这些都是为了将问题模块拆小从而到可被理解的程度,然而无休止的各种会议,各种日报周报,就跟分布式系统一样交互必然,存在,且复杂。