抽象是昂贵的 - specbranch


当你建立一个计算机系统的时候,一些小事情就开始出现了:

  • 也许一个数据库查询对于你正在建立的功能来说是尴尬的,
  • 或者你发现你的服务器在传输数千兆字节的十六进制ASCII数据时陷入困境,
  • 或者你的应用程序为成千上万的独立用户即时翻译成日语。

这些都是你的抽象概念错位的地方,如果你的应用程序有:
  • 一个更好的数据库模式,
  • 一个传输二进制数据的方法,
  • 或者为你的日本用户提供原生的国际化,

那么上述小事情就不是事了。

但是这种抽象都是有代价的。

对于许多计算机系统来说,抽象错位是我们花费大部分资源的地方:在工程成本和计算时间方面都是如此。在高层次上构建应用程序,可以为它们的建立带来好处,但最终要付出代价,要么是技术债务,要么是缓慢的性能,或者两者都是。相反,在整个技术堆栈中,抽象概念被很好地统一起来的系统,如高频交易系统和(具有讽刺意味的)聊天应用程序,能够实现惊人的工程壮举。

大多数做普通事情的小规模web服务并没有为抽象的错位付出多少代价,但大规模的系统和做奇怪事情的系统则付出了巨大的代价。错位也会随着系统的老化和事情的变化而显现出来--迁移很困难,你宁愿增加一个功能也不愿做重构(好吧,也许不是你,但你的经理肯定会这样)。

当我离开谷歌时,我正在开发一个新的存储系统,它利用了一些很酷的技术来实现快速发展。然而,我们真正的力量在于,我们可以设计堆栈,使每一个抽象都与它上面和下面的层保持一致,消除那些占用代码行和计算周期的垫片。我在高频交易中也看到了这种系统。我不知道我有多好......

假设、价值和要求
每个项目的建立都有一套假设、要求和价值观。这些将定义系统建设的约束条件,并最终定义系统的特性。

需求很简单:它们是你的产品绝对需要的东西。例如,如果你正在建立一个NoSQL数据库,你需要某种键值接口和某种存储介质。一个项目的价值定义了你想达到的目标。也许你正在建立一个以性能为重点的NoSQL数据库。在这种情况下,你需要一个NoSQL数据库接口,并且你重视性能。

Bryan Cantrill在价值观方面做了一个很好的演讲。“假设前提”是最后一个支柱:这些假设告诉你,你在构建系统时有哪些无形的伪前提要求。例如,我们大多数人对编程语言和计算环境都有一定的假设:至少,大多数系统是建立在计算机会运行的假设之上的。打破这种假设会带来巨大的成果(和大量的工作)。

假设、价值和要求往往决定了一个系统的最终特征。一个典型的初创CRUD应用可能有以下特点。

  • 要求我们做我们起步的CRUD
  • 重视进入市场的时间
  • 重视廉价运行的能力,直到获得产品与市场的契合度
  • 假设我们在一个托管提供商或云中运行

一个高频交易系统的建立有不同的约束。
  • 要求我们执行一个交易策略
  • 重视交易盈利能力
  • 重视速度
  • 重视安全/遵守法规
  • 假设你在一个共处的数据中心内控制所有的硬件

前一组约束条件为你提供了毫秒级的响应时间、功能和相对的可承受性。

后一组约束条件给了你10-100纳秒的响应时间,奇特的硬件,相对较高的能见度,以及巨大的启动成本。

比较两个数据库
让我们看一下两个容易比较的例子。像ScyllaDB这样的产品可能有以下特点(免责声明:我不与ScyllaDB一起工作,所以这些不是他们的说法)。

  • 需要。分布式NoSQL数据库
  • 重视速度
  • 重视可扩展性
  • 重视与现有NoSQL数据库生态系统(Cassandra)的兼容性
  • 假设Linux机器上有NVMe闪存和现代网卡

这两种产品在其需求和假设的空间内,从其价值出发,驱动其大部分的设计决策。然而,具有不同假设和要求的产品结果是非常不同的。将ScyllaDB与一个具有这些假设、价值和要求的项目进行对比。
  • 要求:分布式SQL数据库
  • 重视可扩展性
  • 重视全局一致性和耐久性
  • 重视与现有SQL数据库生态系统的兼容性(PostgreSQL)。
  • 假设你在Linux机器上运行

这些是非常相似的要求、价值和假设,但它们驱动着完全不同的产品。第二组是CockroachDB或Google的Spanner数据库的近似值(与ScyllaDB的免责声明相同),它们在相同的硬件上执行事务的速度都比ScyllaDB慢两个数量级,但提供SQL接口和全局一致性。

统一抽象概念
理想情况下,你希望你使用的所有抽象都能与你的系统有一致的目标。如果你能买到与你的目标一致的依赖关系,那就太好了。如果没有,你很可能不得不 "按摩 "你的依赖关系,以便能够做你想要的事情。这是抽象概念第一次让你付出代价。如果你使用了错误的数据库模式(或错误的技术),你可能会发现自己在扫描数据库表的时候,不同的模式会做一次查询。对于一个非数据库的例子,如果你做一个基于电子的计算机游戏,它可能会慢得无法玩耍(但你将能够在创纪录的时间内建立它!)。

回到CRUD应用,让我们挑选一个数据库。ScyllaDB集群是个好选择吗?CockroachDB集群呢?我们可能并不介意我们的数据库不能扩展到最好,或者它是最快的,但我们介意运行集群的费用,所以也许我们应该寻找一个替代品。

与我们假设的ScyllaDB和CockroachDB的情况相比,SQLite有一些不同的假设、要求和价值。

  • 要求:可嵌入的SQL数据库
  • 重视易用性
  • 重视可靠性
  • 重视跨平台的兼容性
  • 假设你在某种计算机上运行

其中哪一个更符合CRUD应用?可能是SQLite,至少在产品-市场契合之前,因为它的运行会更容易和更便宜。DynamoDB或其他托管数据库(或CockroachDB的无服务器产品)可能更符合你的要求。毕竟,如果你使用的是云计算,你可能不太关心跨平台的兼容性,而且如果你保持小规模,数据库实际上是免费的--希望当它开始变得昂贵时,你会赚钱。

大多数公司不会建立自己的数据库,因为有大量的可用选项可以帮助你完成任何类型的项目。然而,对于许多其他的抽象概念,你并没有丰富的选择:通常,你只有一两个可供选择,而这些抽象概念的建立并没有考虑到你的用例。

每一个抽象都很重要
很容易看出,使用正确的数据库模式或选择正确的编程语言可以帮助你节省CPU时间和工程师时间,但抽象税在堆栈的上层和下层都会对我们产生影响。

作为一个极端的例子,考虑TCP(是的,那个TCP)。我们中的大多数人都把HTTP/TCP作为一个既定的应用程序,并使用内核的TCP驱动运行,对于大多数项目来说,做一些不同的事情是完全愚蠢的。对于谷歌来说则不然(免责声明:我和发表这篇论文的人一起工作)。存储和搜索人员需要更快、更有效的RPC网络,而云计算需要有可能开发虚拟化功能。结果是Snap,一个用户空间网络驱动,和Pony Express,一个为谷歌大用户的需求而设计的传输协议。通过放弃未使用的功能,从 "可靠的字节流 "转换为 "可靠的消息传递",Pony Express最终比TCP快3-4倍。

谷歌的另一个例子是tcmalloc。许多大公司都设计了自己的内存分配器,但在我的偏见看来,tcmalloc的论文是最好的,它描述了内存分配器如何影响应用程序的性能。通过明确地将内存分配器的目标与车队的目标相一致,他们发现通过增加在分配器中花费的时间,他们可以提高分配效率,由于更好的定位性和减少行走页表(TLB错过)所花费的周期,最终的应用程序要快得多。

事实证明,在线程调度器、文件系统、编程语言和其他层面上也有类似的收益。其中一些被证明是相当昂贵的!

另外,尽管我在这里一直专注于性能,但使用现成的抽象并不总是更容易:Snap网络系统的部分动机是可编程性和可扩展性。

一切都会随着时间而改变
即使是在项目开始时排列整齐的抽象概念也会发现自己变成了错误的选择。通常情况下,这种变化来自于基础假设的变化或你的价值观的变化。回到数据库方面,很多成功的应用程序往往会超越使用SQLite或PostgreSQL的单一服务器。对此有很多解决方案,但根本的变化是 "嵌入式 "变得站不住脚,"易于使用 "的价值开始被削弱,而我们开始重视可扩展性、可用性和速度。我们在这里想到的其他替代方案,ScyllaDB和CockroachDB,开始变得更有吸引力。迁移的成本很高,也很困难,一开始你必须处理一些错误,但是数据库的扩展性。

当然,迁移的替代方案是放入一个兼容层,这样你就可以在生产中保留旧的数据库,同时将新的条目放入新的数据库。这也会导致速度变慢和bug。这并不是一种罕见的模式--做数据库迁移往往成本太高或者风险太大。当然,运行两个数据库系统会进一步误导你的抽象概念。

我们在谷歌的用户空间网络论文的案例中也看到了这一点。对谷歌来说,改变的不是他们的价值,而是他们的假设:现代数据中心网络非常快,CPU可以压缩大量的数据,NVMe闪存比旋转磁盘快2-3个数量级。用TCP饱和一个100Gbps的网卡是很昂贵的--根据Snap的论文,需要16个核心--而用自定义协议饱和则要便宜4倍,而且你可以用一个流来饱和一个网卡。在谷歌的案例中,网卡的带宽扩展导致TCP抽象变得陈旧。

你的系统没有什么可以改变,你的抽象仍然可以变坏。你的软件环境可以改变,你的用户可以改变他们的使用模式,或者你的依赖关系可以简单地以一种你不喜欢的方式被更新。过去运行良好的抽象的磨损通常也被称为 "技术债务"。然而,如果你能很好地选择你的抽象并定义清晰的边界,你使用的抽象可以远远超过你的系统。

结论
作为一名软件工程师,你无法避免抽象--软件本身就是一种抽象。在某种程度上,软件工程师是专业的抽象管理人。我们唯一能做的就是保持对我们的抽象、它们的基本假设以及它们的影响的关注。仅仅关注你的 "核心业务需求 "和你的 "独特的附加值 "并不能单独建立一个成功的企业--如果你用来达到目的的抽象概念没有很好地与你的目标相一致,你最多只能取得一个徒劳的胜利,而你对底线的关注和奉献可能使你失去了扩大规模或运行盈利的机会。

在拥有庞大工程力量的公司,抽象管理是他们很多人花时间去做的事情。通常情况下,这些工程师实际上是最有 "生产力 "的,因为他们节省了很多钱--基础设施项目往往能带来8-9位数的节省或独特的能力,而性能工程(抽象化调整的另一种形式)经常有每位工程师8位数的回报。另一大群工程师负责确保旧的抽象概念不被破坏,不使整个系统崩溃。

反过来说,这也是初创公司尽管拥有小得多的工程团队,却能在大技术上形成技术优势的地方,也是小公司能够超越大型公司的地方。如果你能自由地将你的抽象概念与你的目标结合起来,那么令人惊奇的事情是可能的。