缓存是新的内存
英文原文:Cache is the new RAM
这是一次在 defrag 2014的演讲。
这是经过长时间地多次技术变革后的(多个)技术优势之一。你看到了实际上突破。如果你只是看到了其中的一部分,很难正确推断。你要么短期有进展,要么落后很远。令人惊讶的不是事物变化的速度,而是一点一滴长期工程实践的突破。这是史端乔交换机,一个自动连接电话线路设备,在1891年发明的。
1951年,正是转向数字交换技术之时,一个典型的集中式交换中心基本上还是维多利亚时期的技术的放大版。每个转接过来的电话都有自己单独的strowger交换机。
当时来看,这已经是最牛逼的技术。当然我们看来,这只不过是当时世界上最大的蒸汽朋克(Steampunk,背景设在19世纪的科幻小说)风格的装置艺术(art installations)。
对此感到优越感可能是不对的。虽然集成电路(integrated circuit)已经面世65年了,仍然有数亿计的这种设备嗡嗡咔咔地运行着。直到现在,我们才真正地处在完全电子计算(solid-state computing, solid-state与机械相对,指基于半导体的)的转折点。
最令人兴奋的技术转变,一个是新的模型成为可行,另一个是旧的限制不再存在。在我们的工业界,这两种类型的转变都在上演。
分布式计算(distributed computing)现在是贯穿整个软件栈的主导性的编程模型。所谓的中央处理单元(central processing unit)不再是中心化的,甚至都不是一个单元了。它仅仅算是数据之山(a mountain of data)上爬行的一群虫子(Bugs)中的一个。数据库是最后的堡垒。
同时,内存与硬盘存储间的延迟正在变得无关紧要。30年来,数据库性能的主要关心点,在于访问内存与硬盘存储上的随机数据的巨大差别。既然现在我们可以把数据全部放在内存中,这些烦恼统统不用考虑了。当然不是这么简单,你不能用一个B树,mmap一下,然后就能搞定。在完全基于内存的设计方案推出之前还有很多相关的东西要解决。
这两种新趋势产生了完全崭新的方式来思考、设计、构建应用。现在我们来谈下我们怎么达到,我们怎么做的,未来给我们的启示。
(史前时代,从下文看应该是2000年前,用户被描述成恐龙,作者的小幽默)
那时候,架构图里的每个组件都有一个确定的描述与之相关。每个组件都是一个单独的功能:数据库、web服务器,都成为一屋之剧中的不同角色(一屋指的是机房或data center)。 顺带提一下,这就是“the cloud”这个词的来源。一个轻软/毛茸茸的云是WAN的标准符号,而WAN的细节我们完全不用操心。
(2000年,负载均衡解决一切)
容易实现的分布式计算得到了主流的亲睐。多个完全相同的应用程序服务器藏在一个负载均衡器(load balancer)之后,这个均衡器把负载差不多平均地分配到应用程序服务器上去。只负载均衡那些架构中状态无关的部分回避了很多哲学上的问题(理论上的情况?)。当系统扩展时,这些组件从侧翼包抄,最后包围了“the” database。我们告诉自己,给数据库换上更快的磁盘、更快的CPU很正常,毕竟还是只需要一台机器。硬件提供商很高兴地赚着我们的钱。
(2002:备份解决一切)
最后,数据库备份成为合情合理的,加了一个热备份数据库(hot spare database)后,我们的良心得到些许宽慰。然后我们告诉自己,不会再有任何故障了。当然,这种正确性只存在了几分钟。
当然,热备份经常是空闲的(sitting idle)。一旦商业分析员意识到,他们可以在不触及生产数据的情况下,也能对生产数据进行大规模查询,那么所谓的热备份也几乎跟生产数据一样开始忙碌并且至关重要了。我们又告诉自己,在紧急情况下把热备份暂时拿出来也还好。但这就如同说,我们完全没必要带备胎,因为我们可以从其他车上偷一个过来!
(2004:memcached/缓存解决一切)
然后,Brad Fitzpatrick发布了memcached,一个可以在内存中缓存数据的守护程序(因此叫memcached, memory cached)。这是个简化版的分布式哈希表,而且确实非常实用,因而之后就在学术界流行起来。它拥有很多特性:备份(a form of replication?),水平分区,负载均衡,简单数学运算等。 我们再次告诉自己,既然大部分的负载都是读,我们为什么还要催促数据库从磁盘一遍一遍做相同的查询?你只需要一组内存很大的小规模(small-calibre,小口径)服务器,当然硬件提供商也高兴地赚我们(买内存)的钱。
也许需要你写些缓存失效(cache invalidation)代码,这听起来不难,对吧。
(2004:memcached解决一切,添加了缓存失效)
确如其声称,memcached的方案使我们受益很长时间。它把硬盘的随机IO操作替换为内存的随机IO操作。尽管如此,那台数据库机器还是越来越大,越来越忙。我们意识到,缓存的内存开销至少会跟工作集一样大(不然就是无效了),再加上让人不能忍的缓存持久化。我们告诉自己,这就是网络级规模(web scale?)的开销。
(2006:水平分区/切分解决一切)
更令人担心的是应用越来越复杂,越来越聊天化(chattier,可能聊天程序对数据库写的次数很多)。几乎每次都会进行多次数据库写操作。现在,写,而不是读,成为了瓶颈。这时我们才最终认真对待数据库切分。Facebook最初是根据university字段来切分其用户数据,然后做成了"哈佛数据库(The Harvard Database)",并且维持了很长一段时间。Flickr是另一个好例子。他们使用PHP手动建立了一个切分系统,这个系统使用用户ID的哈希值来切分数据库,跟memcached根据key来切分很像。在技术交流会上,他们透露,不得不对数据表去规范化(denormalize),以及对一些对象(比如评论、消息、喜欢)进行两次写(doule-write)。
要解决无限伸缩(infinite scaling)总要付出点代价,对吧。
(2008:NoSQL解决一切)
手动切分关系型数据库的问题是,你的关系型数据库已经没了。切分API实际上成为你的查询语言了。你对操作的头疼还没好,而修改一组模式(schema)更加痛苦。
这就需要大家深呼一口气,列出大家选用的SQL实现的所有不足和瑕疵,然后因此责怪SQL。一波潮人似的NoSQL,难民似的XML数据库出现了,并且都作出了根本办不到的承诺。它们提供了自动切分,灵活的模式,一些冗余,...,一开始也就这么多。但是总比自己写要好多了。
你知道,“不用自己写”成为主要卖点的东西总是令人绝望。
(2010:Map/Reduce解决一切)
转移到NoSQL并不比使用手动切分差,因为我们已经放弃了使用常用的客户端工具控制和分析数据的希望。但这没好多少。之前由商业人员(business folks)编写的SQL查询变成了开发人员维护的报表代码。
还记得用于备份和分析的热备份数据库吧?现在它变身为Hadoop filestores以及上层的Hive查询而卷土重来了。既然奏效,商业人员再也不来烦我们了。但一个大问题是,这些系统的操作复杂性。就像航天飞机一样,它们是作为可靠且几乎不用维护的产品出售的,但是最后还是需要大量的手动操作。另一个大问题是,数据的存入和取出:花费一整天的时间已经相当不错了。第三个大问题是IO同时成为网络和磁盘的瓶颈。我们告诉自己,这就是从大数据(big data)毕业的代价。
不管怎样,Google就是这样做的,对吧。
(2012:NoSQL再次解决一切)
随着一些NoSQL数据库的逐渐成熟,它们的API发生了诡异的变化:它们开始长得像SQL一样。这是因为SQL是关系型集合理论(relational set theory)的相当直接的实现,而数学不是那么好愚弄的。
我重述下Paul Graham对Lisp那难以忍受、并自鸣得意的评论:一旦你添加了group by, filter, join,你也不能声称发明了新的查询语言,因为这仅仅算是SQL的一个新方言。而且语法很差,还没有优化器。
由于我们绕过了SQL,大部分系统都缺少了一些很重要的东西,比如存储引擎、查询优化器,而这些都是基于关系型集合理论设计的。拖延到后期去实现导致了严重的性能问题。即使对解决了性能问题的那些(或者通过停驻在内存中来掩盖此问题),也缺少了其他东西,如合适的备份。
我知道一个非常成功的互联网初创公司(你肯定也听过)使用了4个(!!)不同的NoSQL系统来解决问题。
(2014:现在需要什么来解决一切?)
现在已经相当明显,我们不会回到单数据库以及10毫秒一次的随机定位(10-million-nanosecond random seek,上文幻灯有提到,读一次硬盘要10毫秒)的那个从前了。在寻找一劳永逸解决所有问题的炒作周期(hype cycle, 也叫技术成熟度曲线)的过程中,有个有趣的模式:聪明的方法在减轻一个痛点的同时会引入新的痛点。
所以下一个添到这张图上的复杂工具是什么呢?也许真正的方法是能简化事情的。
例如内存:在数据库机器上有很多内存,用做缓冲和计算;Memcached机器上也有很多内存。这些系统中的内存总和至少跟你的工作数据集一样大。如果不是,你就赚到了(under-bought,低阶买到好货)。而且,我非常怀疑你的缓存层是否100%高效。我打赌你有大量数据在被替换掉之前没有被读取过,我还打赌你从来没跟踪过。这不意味着你是个坏孩子,而意味着缓存比起其所值,更是个麻烦。
这些组件共有的很多特性看起来,是可以相互组合,并且互补的。只要它们被安排得合理。
一旦你采用下面的公理:系统应该是分布式的,而数据应该是数字化的(solid-state是纯电气的,而不是mechanical机械式的),有意思的事情出现了:模型更简单了。在查询触发时才会用到的临时内存数据结构是仅有的结构。随机访问不再是大罪,而是商业的正常过程。你不必担心分页,或者再均衡(rebalancing),或者数据的位置。
(2014:SQL内存集群解决一切)
这是个优美、简单的架构。就像负载均衡器抽象了应用程序服务器,SQL聚合器(aggregators)抽象了读写的组织细节。把数据存放策略的核心放在稳定的API之下,可以在少量中断的情况下允许两边变化。
现在,一切都好了,我们最终到达了历史最后的美好之地,对吧?
不管你在何时,对计算艺术状态的自满都是错误的。总会有其他瓶颈。
这是AMD的Barcelona芯片,相当现代化的设计。它有4个核,但是大部分表面都被缓存和核心(core)周围的I/O区域占据,就像WalMart周围的大型停车场一样。奔腾时代,缓存区域只占晶圆(die)的15%。第三次计算领域的革命在于,CPU相对于内存快了多少。因此晶圆上大片昂贵的区域都为缓存保留着。
过去,数据库性能的主要关注点在内存和硬盘的延迟,现在我们打趣CPU和内存的延迟不是同样的问题,但是它确实是。
而且我们装作共享内存是存在的,但却不是。有那么多核心和内存,总会有些核心离部分内存很近。
当你仔细想下,计算机确实只做了两件事:读符号,写符号。性能是个计算机有多少数据要移动,数据要到哪里去的功能问题。最好的可能情形是大量的顺序数据流被读取一次,很快地处理后,就不再被用到。GPU是个很好的例子。但是最有意思的负载不是这样的。
(吞吐量和延迟总会笑到最后)
每个随机指针都会保证缓存一次不中,每个对同一块内存区域(比如写锁)的竞争都会引起大量的协调延迟。即使你的CPU缓存命中率达到99%(事实上不可能),等待内存的时间也会是主导性的。
或者这样说吧:如果磁盘是新的磁带,内存就是新的硬盘,CPU缓存是新的内存。位置仍然有关系。
所以,什么会解决这个问题?看起来这就是那个相同的古老的矛盾:我们优化随机访问,还是优化串行?我们接纳写,还是读的性能问题?还是我们干坐着等硬件速度跟上来?也许记忆电阻器(memristor)或者其他技术会使这些问题无关紧要。当然,我也需要些钱(pony, 小马?)。
好消息是分布式数据库的总体物理架构基本成型。数据客户端不再需要处理4或5个不同的子系统的内部细节。这还不完美,也不是主流。但是突破总归需要一段时间来传播。
但如果瓶颈还是在存储位置,这意味着其他部分都成熟了。创新可能在数据结构和算法领域发生。也很少会有清理架构的改动,来承诺一次解决全部问题。如果我们幸运,接下来的15年,SQL数据库会慢慢变得更快更高效,而API是相同的。
但是在此之前,工业界将不会平静。