从YouTuber大型项目谈,Python 是个烂语言吗?

从YouTuber大型项目谈,Python 是个烂语言吗?

不过再怎么吐槽,YouTube不管从历史,代码量,码农数量,还是支撑的业务规模看,都是一个成功的大型Python Web项目 —— 至少曾经是,为什么是曾经,请一定要看到最后。


...


YouTube从一开始就是一个典型的Python + MySQL项目。代码随着业务发展自然生长,在很多年前代码量就以百万计了。

这时候的主要工程实践就是一些老生常谈:接口文档,单元测试和集成测试,严格的code review, 还是能维持feature开发;性能上前面一个高度优化的模板引擎 (https://github.com/youtube/spitfire) 后面一个 DB Proxy (https://vitess.io/) 就撑起了巨大的流量。Google的代码管理、构建系统和持续集成系统确实是好,serving infrastructure也够强悍,这些底子打得好是这个项目不用很多tricks都能撑到这么大的重要原因。

但此时软件复杂性的问题已经非常明显,在这个规模上YouTube居然还是一个单体项目,不是不想拆是拆不了。

问题就出在“自然生长”上,YouTube代码有典型的分层和业务模块分离设计,但是在这个开发团队规模上,很难有一个独裁架构师管控代码设计,工程师为了一些局部优化目标加一些例外破坏原始架构设计,比如在貌似ORM层的地方放业务特定代码,或者为了实现功能跨模块直接调用。


Code review能保证局部代码设计合理,高覆盖的测试能大概保证上线能跑,但长期下来那么多工程师那么多例外,软件整体就变成了一团分层不明,依赖混乱,无法拆分的东西。

这是常见的软件工程问题,其实跟Python没什么关系,它在一个Python项目上的体现就是随便改一两行代码都有可能break掉完全不相关的不知什么东西,而且不花至少半个小时跑完至少几千个单元测试你都发现不了(跑完了也不保证能发现就是了),开发者肯定是不爽的,我自己就变成了祥林嫂,至于什么快速开发,从来就不存在。

由于核心数据库访问逻辑在这团无法拆分的大包里,要加涉及核心数据的功能,只能像滚屎球一样捏着鼻子继续往上加代码。

但是对相对独立的新功能,工程师首先考虑的是不再需要用MySQL了 —— Google唯二大规模MySQL部署是广告后台和YouTube, 广告后台切Spanner/F1之后就只剩YouTube了,新功能都被鼓励用Spanner等Google原生存储平台。既然如此,那也就没有那坨Python貌似ORM又不是ORM的代码什么事了,业务逻辑当然就可以写成C++或者Java的后台服务。这时候YouTube就已经是一个Python+MySQL+一大堆C++/Java后台的混合架构。

真正让YouTuber觉得日子没法过了的,其实还是运行时的问题。由于GIL, Python在多线程环境中很难扩展,YouTube用的是prefork多进程模式。

YouTube为了接入Google生产环境以及跟各种Google系统交互,难免要大量调用Google通用的C++库,然而这些C++库全都假定是在一个典型的单进程多线程环境下跑的,开几个后台线程,加载一个共享数据表稀松平常,因为它们都假定这些都是给几千个线程共享的资源开销平摊下来没什么,但到了YouTube的多进程环境就尴尬了,最直接的影响就是巨大的内存占用,那些年内存价格高企,问题就尤其突出。

CPU单核性能不再提高之后,CPU开销也成了大问题。YouTube尝试过PyPy, 但是发现由于之前明显的CPU瓶颈已经被用C模块的方法优化掉了,接下来上PyPy并没有带来期望的明显性能提升。

从profiler上看到的就是做大型复杂工程的人熟悉的:处处都性能不佳,处处都不是瓶颈的景象,软件太复杂,响应最简单的请求都要做很多免不了的事,就得花那么多CPU. pypy-stm 还很不成熟,破除GIL带来的prefork魔咒遥遥无期。

走投无路之下YouTube甚至搞过Grumpy(https://github.com/google/grumpy) 这种疯狂的东西,就是一个用Go写的Python runtime, 企图用goroutine和将Python代码渐进移植成Go的方法破除性能屏障,项目似乎理所当然地没有了下文。


光从性能上说另一个有希望的方向是Cython, 然而就像上面说的,明显CPU瓶颈已经用手写C模块优化过了,要再广泛铺开Cython必然要手工改写大量业务代码,话说既然都要手工改写了,那为什么还用Python呢?

...

花开两朵各表一支,在codebase上,YouTuber历尽千辛万苦,还是将代码分出来严格的Web前端和API层,API层严格划分出了服务模块,各层和模块间只能采用Protocol Buffer的RPC API交互。虽然由于各种不得已这个严格划分好了的Python codebase(居然)还是要整体发布,但是现在逐个模块重写,至少在技术上成了可能。

轰轰烈烈的 #YTFExit 运动开始了(YTFE = YouTube FrontEnd),运动还有一个契机:MySQL是真的撑不住了,就算技术上撑得住,Google SRE也不愿再为YouTube维护一套全Google唯一的巨型MySQL环境了。YouTube决定将所有存储迁移到Spanner, 并且重新设计表结构,那些绕不开的数据访问Python代码横竖都是要重写的了。

YouTube决定将API层的服务逐个用C++重写成独立部署的微服务,最大限度利用Google完善的C++ infrastructure, prefork什么的自然不会存在,性能的话,其实不管用什么语言只要把陈年代码推倒重写一次都能有显着提升,更别说是C++了。

...

最后只剩下Web层还是Python, 而由于YouTube Web前端已经迁移到Polymer, 可以直接跟API层通信,需要服务器拼接Web页面的地方越来越少,剩下的Web层代码也越来越少,最终也将迁出Python体系。YouTube也将不再是一个Python项目了。

YouTube的故事就讲到这里。YouTube无疑是一个使用Python的成功项目,但Python是否就是YouTube各个时期最好的选择,每个参与者都有自己的结论。

我们可以问很多假设性问题,像是假如YouTube是个独立公司不需要依赖其他Google软件,会不会在运行时问题上没那么痛苦,有更多优化的机会;假如及早在架构上严格拆分,开发效率是否就不会掉得这么严重;假如早年在用Go写Vitess的时候咬咬牙把YTFE也用Go重写了项目发展会不会更顺利。要是去深入思考每一个假如,就会发现这从来不是一个单纯的语言技术问题。

我自己的看法是,在一个大团队,长期开发的项目中,“自然生长”和滚屎球难以避免,在因此导致的软件复杂和冗余面前,单纯写代码的速度没有什么意义,在技术上能对项目有帮助的主要是 1. 编译和静态分析速度要快 2. 有个靠谱的运行时。

Python在这两方面都没能做得很好(pytype没能在YouTube大规模铺开,因为对YouTube的规模来说它实在太慢了),不是说Python就一定做不好,只是在这个领域选择太多,为什么非要抱着Python不放?社区和生态系统就没有选择往这方面优化。

#YTFExit 最终的选择居然是 C++, 要说C++在很多方面也非常不适合现代大型项目,比如编译时间就不及格,但是架不住那么多非C++不可的地方Google不得不下死力气优化,也符合了YouTube的要求。

#YTFExit没有选择Go的最大考量是C++互操作性不好,如果不是因为Google的这个C++马太效应,要我开始一个YouTube这样的大型Web项目,我会选择Go.


...


当然命运没有给我这个如果,我离开YouTube后去了搞一个大型机器学习项目。本以为逃出了YouTube的焦油坑,结果到项目第一天就被糊了一脸稀烂的Python code. Python成了机器学习默认语言这事也有很多偶然,不过已是既成事实,对构建神经网络的前端DSL是Python这件事我没有什么意见。

其实一个机器学习项目除了这一点神经网络构建外其他大部分的工作都是各种倒腾protobuf数据,我认为这是非常不适合Python的东西,但是既然Python是机器学习默认语言,大部分researcher开始倒腾数据的时候也是直接上Python, 你跟谁说理去?

也许以后Python社区能大力出奇迹,让语言和生态变得特别适应这类的大型项目。所以一个语言的命运啊,当然要靠自我奋斗,但是也要考虑到历史的行程,这个显而易见的道理知乎小编认为政治敏感,摊手。