目录
3.2.2 本地缓存和文件窃取(file stealing)
4.3.1 关系型之外的(Post-relational)操作
4.3.3 乐观的转换(Optimistic Conversion)
=====================================================================================
本技术论文的翻译工作由不动明王1984独自完成,特此声明。
翻译辛苦,珍惜劳动,引用时请注明出处!
=====================================================================================
摘要
我们生活在一个分布式计算的黄金时代。公有云平台现在按需提供了无限的虚拟计算和存储资源。同时,软件即服务(SaaS)模型为用户带来了企业级的系统,这些用户在之前由于开销和复杂性的问题并无法负担的起这样的系统。传统的数仓系统在适应这样的环境时陷入挣扎。一方面,它们被设计用于固定的资源,因而并不能利用云提供的弹性。另一方面,它们对于复杂的ETL管道和物理调参的依赖与云上新型的半结构化数据及快速变更的工作负载对于灵活性和新鲜性的要求不相符。
我们认为一个根本的重新设计是势在必行的。我们的任务是构建一个基于云的为企业准备好的数仓解决方案。结果就是Snowflake弹性数仓,或者简称为“Snowflake”。Snowflake是一个多租户的、事务型的、安全的、高扩展性的系统,支持完整的SQL及内置的对于半结构化数据和无模式数据的扩展。系统被提供为一个在AWS云上按使用付费(pay-as-you-go)的服务。用户上传他们的数据到云上,然后可以立即通过熟悉的工具和接口来管理及查询它。其在2012年下半年开始开发实现,并在2015年六月开始基本可用了。今天,Snowflake被不断增长的小企业和大企业应用在生产环境中。系统目前每天在数PB的数据上运行数百万的查询。
在本论文中,我们描述了Snowflake的设计及其新颖的多集群(multi-cluster),数据共享(shared-data)架构。本论文强调了一些Snowflake的核心特性:极大的伸缩性和可用性,半结构化和无模式数据,时间游移(time travel),以及端到端的安全。论文最后总结了学习到的经验教训及对于正在进行工作的一个展望。
关键字
数据仓库(data warehousing),数据库即服务(database as a service),多集群数据共享架构(multi-cluster shared data architecture)
1. 介绍
云的出现标志着一种对于在本地服务器上软件的交付与执行方式的抛弃,以及对于运行于像Amazon, Google, 或Microsoft等平台提供方的共享数据中心及SaaS解决方案的趋向。云的共享底层基础设施保证了规模庞大之后的经济性,极大的弹性和可用性,以及一种可以适应无法预测的使用需求场景的按使用付费模型。但是这些优势只有在软件本身可以在基于云上资源的基础上进行弹性伸缩时才能被利用。传统的数仓方案出现在云之前。它们被设计为运行在较小的,静态的集群上,其中包含了行为正常的机器,这些都使得它们不是一个合适的云上的架构。
但是不仅仅是平台发生了变化。数据也发生了变化。过去的情况往往是数仓中的绝大部分数据都来自于机构内部:事务系统,企业资源管理(ERP)应用,客户关系管理(CRM)应用,等等。数据的结构、数量、和频率等都可以很好的预料和知晓。但是在云环境中,一份巨大的和迅速增长的数据可能来自于不太可控的或外部的资源:应用日志、网络应用、移动设备、社交媒体、传感数据(物联网)。除了不断增长的数据量以外,这种数据经常是无模式的、半结构化的。传统的数据仓库方案对于这种新型数据的处理会很挣扎。它们的方案深度依赖于ETL管道及物理调优,这些从根本上是假定了数据是来自于内部数据源,并且是可预测的、缓慢变化的、以及可轻易归类的。
为了应对这些不足,数仓社区的一部分人开始转向“大数据”平台,例如Hadoop或Spark。虽然这些是数据中心尺度上的数据处理任务的不可或缺的工具,开源社区仍在继续做出巨大的改进,例如Stinger Initiative,它们仍然缺少已建立的数仓技术的很多的高效性及特性集合。但最重要的是,它们需要巨大的工程学上的努力才能展开并使用(使用门槛很高)。
我们相信有很大一类的用户场景及工作负载可以从云的经济的、弹性的、以及服务性的方面得到好处,但是却无法从传统数仓技术或大数据平台上获得很好的服务。因此我们决定针对云来构建一个完全新的数据仓库系统。该系统被称为Snowflake弹性数据仓库,或者“Snowflake”。与很多在云数据管理空间上的系统不同,Snowflake不是基于Hadoop,PostgreSQL或者其他类似的系统。执行引擎和绝大部分其他组件都是从零开发的。
Snowflake的核心特性如下:
- 纯粹的软件即服务(Saas) 体验用户不需要购买机器,雇佣数据库管理员,或者安装软件。用户要么已经在云上拥有他们的数据,要么上传(或邮寄)数据。他们随后可以立即的操作并查询他们的数据,使用Snowflake的图形化接口或例如例如ODBC这样的标准接口。不同于其他的数据库即服务(DBaaS)产品,Snowflake的服务层面延展到整个用户体验上。在用户的层面上没有调参开源,没有物理设计,没有存储梳理任务。
- 关系型 Snowflake拥有对于ANSI SQL和ACID事务的完全的支持。绝大多数用户可以基于很小的修改或者根本无需修改来迁移已存在的工作负载。
- 半结构化 Snowflake提供了内置函数和SQL扩展来转换、平铺、以及嵌套半结构化数据,提供对于流行的格式例如JSON和Avro的支持。自动的模式(schema)发现和列存储使得在无模式、半结构化数据上的操作几乎和普通的关系型数据一样快,无需用户任何的额外努力。
- 伸缩性 存储资源和计算资源可以被独立的及无缝的扩展,不会影响到数据的可用性或并发查询的性能。
- 高可用性 Snowflake可以容忍节点、集群、甚至整个数据中心的失效。在软件和硬件更新升级时,系统不会有任何的下线时间。
- 持久性 Snowflake被设计用于极端的持久性,伴随着对于偶然数据丢失情况的极端的保障;复制、撤销删除操作、以及跨区域的备份。
- 费用高效性 Snowflake是高度计算高效(compute-efficient)的,且所有表数据都被压缩了。用户只会支付他们实际使用的存储和计算资源。
- 安全性 所有数据(包括临时文件)以及网络通信都是端到端加密的。没有用户数据会被直接暴露给云平台。另外,基于角色的访问控制给了用户在SQL语句层次的细粒度访问控制能力。
Snowflake当前运行在Amazon云(AWS)上,但是我们可能会在未来将其移植到其他的云平台上。在写本文的时候,Snowflake每天在数PB的数据上执行数百万的查询,服务了来自不同区域的正在快速增长的大大小小的机构。
概述
本论文按如下结构进行组织。章节2解释了Snowflake背后的核心设计选择:存储与计算的分离。章节3展现了多集群、共享数据结构带来的结果。章节4强调了一些差异化的特性:不间断的可用性、半结构化和无模式数据、时间游移和复制,以及端到端的安全。章节5讨论了相关的工作。章节6通过获得的经验教训及对于正在进行工作的一个展望来结束本文。
2. 存储与计算
无共享(Shared-noting)架构在高性能数据仓库中成为了占据绝对优势的系统架构,基于两个原因:可测量性和商用硬件。在一个无共享架构中,每一个查询执行节点都有自己的本地磁盘。表数据在多个节点上被水平的切分,每一个节点只会负责计算其自己本地磁盘上的数据。该设计对于星型模型上的查询扩展性很好,因为在Join一个小维度表(broadcast)和一个巨大的事实表(分区的)时所要求的网络带宽是很小的。又由于在共享的数据结构或硬件资源上很少有竞争,因而没有必要使用昂贵的,定制化的硬件。
在一个纯粹的无共享架构中,每一个节点都有相同的责任,并运行在相同的硬件上。这种策略形成了优雅的易于推理的软件,伴随着所有好的次要效果。然而,一个纯粹的无共享架构有一个很重要的缺点:其将计算资源和存储资源紧耦合了,这某些场景下会导致很多问题。
- 多样的工作负载 虽然硬件是同质的,但是工作负载一般不是。一个对于大块加载(高I/O带宽,低计算)任务比较理想的系统配置对于复杂查询(低I/O带宽,高计算)任务来说就会适应的很差,反过来也一样。因而,硬件配置就需要以一种较低的平均使用率做一个权衡。
- 成员变更 如果节点的集合变化了————无论是因为节点失效,还是因为用户重新扩缩容了系统————大量的数据就需要被reshuffled。由于相同的节点既要负责执行数据shuffling又要执行查询,此时会观察到一个巨大的性能影响,因此限制了整个系统的弹性及可用性。
- 在线更新 虽然较小的成员变更所造成的影响可以通过复制来进行一定程度的降低,但软件和硬件的升级最终会影响系统中的每一个节点。实现一个接一个节点的更新而整个系统没有任何下线时间这种在线更新理论上是可能的,但是由于所有东西都是紧耦合的且预期是同质的这样的事实会使其实现起来非常困难。
在一个预部署(on-premise)的环境中,需要经常的忍受这些问题。工作负载可能会各种各样,但是在一个小的、固定的节点集群上基本上没什么好方法可做。升级节点是很罕见的,节点失效或系统扩缩容也一样稀少。
在云上情况就非常不同了。像Amazon EC2这样的平台会有很多不同的节点类型。利用它们是一个简单的将数据放到合适的节点类型的问题。同时,节点失效会更加频繁,并且节点性能可能会有巨大的差异(慢节点),即使在相同类型的节点之间。因而集群成员变化就不再是一个异常,它们是正常情况。最终,有强烈的动机来支持在线更新和弹性扩缩容。在线更新极大的缩短了软件部署的周期并增加了可用性。弹性扩缩容进一步增加可用性并允许用户为其临时的需求匹配相应资源。
由于上述及其他的原因,Snowflake将存储和计算分离开来。这两个方面被两个松散耦合的、独立扩展的服务来分别处理。计算会通过Snowflake(专有)的无共享计算引擎来提供。存储通过Amazon S3来提供,虽然原则上任何类型的blob存储都可以满足(Azure Blob Storage, Google Cloud Storage)。为了降低在计算节点与存储节点之间的网络通信,每一个计算节点都会在本地磁盘上缓存一些表数据。
该方案的一个额外的好处是,本地磁盘空间不会花费在复制整个基础数据上,这些基础数据可能会给常巨大和“冷”(很少被访问到)。而是,本地磁盘会排他的使用在临时数据和缓存数据上,这两者都是“热”的(建议使用高性能的存储设备,例如SSDs)。因此,一旦缓存是“热”的,性能会接近甚至超过一个纯粹的无共享系统。我们称这个新颖的架构为多集群、共享数据架构。
3. 架构
Snowflake被设计为一个已经为企业准备好的服务。除了提供高度的易用性及互操作性,为企业准备好的意味着高可用性。为了这个目的,Snowflake是一个面向服务的架构(SOA),由高度的错误容忍性和独立扩展性的服务所组成。这些服务通过RESTful的接口相互沟通,并分别属于三个结构层次中:
- 数据存储层 本层使用Amazon S3来保存表数据及查询结果(物化视图、中间结果)。
- 虚拟仓库层 系统的“肌肉”。本层在弹性的虚拟机集群上执行查询,被称为虚拟仓库层。
- 云服务层 系统的“大脑”。本层是一组用于管理虚拟机、查询、事务的服务,以及所有围绕这些功能的元数据:数据库schemas,访问控制信息,加密秘钥,使用统计等等
图1展示了Snowflake架构上的三个层次,以及它们的主要组件:
3.1 数据存储层
AWS被Snowflake选为初始的平台有两个主要的原因。首先,AWS是最成熟的提供云平台市场的平台。其次(与第一点有关),AWS提供了最大的潜在客户群体。
下一个选择是使用S3还是基于HDFS或类似产品开发我们自己的存储服务。我们花费了一些时间来试验S3,然后发现虽然其性能可能会有所差异,但是其易用性、高可用性、及强持久化保障这些特性很难被打败。所以相较于开发我们自己的存储服务,我们选择将我们的精力放在虚拟仓储层的本地化缓存及写偏斜恢复等技术上。
相较于本地存储,S3天然的有一个更高的访问延迟,并且伴随着每一次I/O请求都会有一个更高的CPU开销,特别是如果使用了HTTS链接。但是更重要的是,S3是一个blob存储,伴随着一个相对简单的基于HTTP(S)的PUT/GET/DELETE接口。也就是说,对象文件只能被整个写入或覆盖。甚至不可能往一个文件的末尾追加数据。实际上,一个文件确切的大小需要在PUT请求中被首先声明。然而,S3却支持GET请求可以获得一个文件的部分内容。
这些特性强烈的影响了Snowflake的表文件格式和并发控制方案(参见3.3.2节)。表被水平切分为大的,不可变的文件,相当于传统数据库系统中的块或页。在每个文件中每一个属性或列的值被组织在一块并被高度的压缩,这是一种众所周知的在文献2中被称为PAX或混合列式存储的方案。每一个表文件都有一个header,该header包含了每一列数据在文件中的偏移(offsets)。由于S3允许GET请求得到文件的某些部分内容,因此查询只需要下载文件headers并据此获取它们感兴趣的列的内容便可。
Snowflake使用S3存储的不仅仅是用户的表数据。它同样使用S3来存储由查询操作(例如巨大的joins)产生的临时数据,一旦计算节点本地磁盘空间被耗尽,也会存储巨大查询的结果。将临时数据溢出(spilling)到S3允许系统计算任意巨大的查询,而不会产生内存溢出或磁盘溢出错误。将查询结果存储到S3支持了新型的客户端交互并简化了查询处理,由于其移除了在传统数据库系统中的服务端游标支持的需要。
例如catalog对象、哪些表包含了哪些S3文件、统计信息、锁信息、事务日志等等的元数据都被保存在一个可扩展的、事务性的key-value存储中,该kv存储是云服务层的一部分。
3.2 虚拟仓库层
虚拟仓库层由EC2实例的集群构成。每一个这样的集群通过一个被称为虚拟仓库(VW)的抽象被展现给其对应的单个用户。构成一个VW的单独的EC2实例被称为工作节点。用户永远也不会直接与工作节点进行交互。实际上,用户并不知道或者并不关心哪些工作节点或多少工作节点构成了一个VW。相反VWs以一种“T-Shirt sizes”抽象来展示,包括了从X-Small到XX-Large。这种抽象允许我们以和底层云平台独立的方式演变服务及其价格。
3.2.1 弹性和隔离性
VWs是纯粹的计算资源。它们可以按需的在任意时间点被创建、销毁、或改变尺寸。创建或销毁一个VW不会对数据库的状态有任何影响。用户在没有查询的时候关闭它们所有的VWs是完全合法的(并且是被鼓励的)。系统的弹性允许用户动态的为其使用需求匹配计算资源,独立于数据的量级。
每一个独立的查询会在一个VW中运行。工作节点不会在VWs之间共享,这会形成查询之间的强力的性能隔离。(话虽如此,我们将工作节点共享认为是一个未来工作的重要方面,因为其能支持更高的资源利用及更低的费用,对于那些并不太关心性能隔离的场景。)
当一个新的查询被提交,对应的VW中的每一个工作节点(或者节点的一个子集,如果优化器检测到这是一个小查询)会产生一个新的工作进程。每一个工作进程只在其对应的查询期间存活。一个工作进程自己——即使是一个更新语句的一部分——永远不会导致对外可见的效果,由于表文件是不可变的,参见3.3.2节。工作进程失败于是就可以被轻易的包含并例行的通过重试来解决。Snowflake现在不会执行部分重试,因此非常巨大的,长时间运行的查询是需要关注并在未来重点工作的领域。
每个用户在某一时刻可能拥有多个VWs,每一个VW同样可能运行着多个并发的查询。每一个VW会访问相同的共享表数据,不需要物理上拷贝多分数据。
共享的,无限的存储意味着用户可以共享并集成他们所有的数据,这是数仓的一个核心的原则。同时,用户会从私有的计算资源得到好处,避免了不同工作负载及不同机构部门之间的相互影响————这是使用数据集市的一个原因。这种弹性和隔离性形成了一些新颖的使用策略。Snowflake的用户会普遍的持有多个VWs用于来自不同部门的查询,这些VWs经常会持续的运行;并时不时的按需启动VWs,用于临时的大块加载任务。
另一个重要的关于弹性的观察是其对于大体相似的价格往往能达到更好的性能表现。例如,一个在4节点系统上花费15小时的数据加载任务,对于一个32节点的系统可能仅仅花费2小时。由于用户是按照计算时间进行付费的,整体的费用基本相似————但是用户体验会有巨大的差异。我们于是相信VW弹性是Snowflake架构的最大的好处及差异之一,其说明了需要一个与众不同的设计来利用云的独特的能力。
3.2.2 本地缓存和文件窃取(file stealing)
每一个工作节点在本地磁盘上维护了表数据的一个缓存。该缓存是一组被该节点访问过的表文件(也就是S3对象)。更准确的说,缓存维护了文件头(header)以及文件中的独立的列数据,由于查询只会下载它们需要的列数据。
缓存会在工作节点生命周期内存活,并且会在并发的和顺序的工作进程(也就是查询)中共享。它只会看到一个对文件和列请求的流,并遵循一个简单的最近最少使用(LRU)替换策略,并不会注意到一个个的查询。这种简单的方案效果出奇的好,但是我们在未来可能会改进它以适应不同的工作负载。
为了改善缓存命中率及避免在同一个VM的不同工作节点中重复缓存表数据文件,查询优化器会对表文件名使用一致性哈希来将输入文件集合分发到工作节点上。访问相同表文件的顺序的或并发的查询于是也会在相同的工作节点上执行(Mesa的计算集群也使用同样的策略)。
Snowflake中的一致性哈希是懒惰的。当工作节点集合发生变化————由于节点失效或VW调整大小————不会有数据被立即shuffled。相反,Snowflake会依赖LRU替换策略来最终替换掉缓存内容(也就是什么都不做,通过cache miss进行重新加载,反正只是缓存数据)。该方法将缓存内容的迁移分摊到了多次查询中,产生了比热切的缓存替换或一个纯粹的无共享系统(其需要立刻在节点之间shuffle大量的表数据)更好的可用性。这种方案同时也简化了系统,既然不需要有一种“降级的”模式。
除了缓存,数据倾斜的处理对于一个云数仓是特别重要的。一些节点可能执行的比其他节点慢的多,由于虚拟化问题或网络竞争。Snowflake在扫描的层次上处理该问题。任何时候一个工作进程完成了它自己扫描的输入文件集合以后,它会向它的同事请求额外的数据文件,这是一种我们称为文件窃取的技术。如果一个同事在这样的一个请求到来时发现它自己剩余很多的文件,那么它就会通过在当前查询的范围和生命周期内将一个剩余文件的所有权转移到请求进程来应答该请求。该设计确保了文件窃取行为不会由于往挣扎的节点上转移额外的负载而使情况变得更糟。
3.2.3 执行引擎
有能力在1000个节点上执行一个查询是毫无价值的,如果另外一个系统能够在10个节点上以相同的时间完成该查询。因此虽然扩展性是主要的特性,但是每节点效率也同样重要。我们想要给用户市面上的DSaaS类产品中最好的性价比,因此我们决定实现我们自己的最新型的SQL执行引擎。我们构建的引擎是列式的、向量化的,并基于推执行的。
- 列式的存储和执行往往被认为对于分析型工作负载来说是优于基于行的存储和执行的。基于更加高效的对CPU高速缓存和SIMD指令的使用,以及更多的机会来做(轻量级)的压缩。
- 向量化执行意味着,与例如MapReduce这样的对比,Snowflake避免了对中间结果的物化。相反,数据以一种管道(pipeline)的方式被处理,以一批批的包含数千行数据的列存格式的方式。该方法,由向量方式处理(最初在MonetDB/X100中使用)探索的,节省了I/O并极大的改善了缓存效率。
- 基于推的执行指的是相关的操作将它们的结果推给它们下游的操作,而不是等待这些操作来拉数据(典型的火山模型)。基于推的执行改善了缓存效率,因为它删除了紧循环的控制流逻辑。其同样支持了Snowflake高效的执行DAG形状的计划,而不仅仅是树形,产生了额外的机会来共享和管道式处理中间结果(这一点倒是,基于推的执行,由下游算子来控制推送,因此可以在pipeline中执行DAG形状的计划)。
同时,很多传统查询处理时的资源开销在Snowflake里并不会出现。注意,在查询执行时无需进行事务管理。正如引擎所关心的一样,查询会在一个固定的不可变文件的集合上执行。同样的,并不会有缓存池。大部分查询会扫描大量的数据。使用内存作为每操作的表数据缓存在这里会是一个很坏的交易。然而,Snowflake确实允许所有的主要的操作(join, group by, sort)将结果溢出(spill)到磁盘上,当内存被耗尽时。我们发现一个纯粹的基于内存的引擎,更精简的并且可能更快的,在执行所有感兴趣的工作负载时候太受限制。分析型工作负载可能产生极大的joins或聚合。
3.3 云服务层
虚拟仓库层是朝生暮死的,用户指定的资源。相反,云服务层是重度多租户的。本层的每一个服务————访问控制、查询优化、事务管理,以及其他————都是长时间存活的,并在多个用户之间共享的。多租户改善了利用率并减低了管理负担,这允许了更好的扩展的经济性,相较于传统架构中每个用户都有一个完全私有的系统化身。
每一个服务都是复制的,用于高可用及扩展性。因此,单独服务的失效不会导致数据丢失或可用性的丢失,虽然一些正在运行的查询可能会失败(并会透明的重新执行)。
3.3.1 查询管理和优化
用户提交的所有查询都会穿过云服务层。在这里,所有查询生命周期中的早期阶段都被处理了:解析,对象决议,访问控制,以及执行计划优化。
Snowflake的查询优化器遵循了一个典型的瀑布风格的方法,一种自顶向下的基于代价的优化。所有用于优化的统计信息会在数据加载及更新时被自动维护。由于Snowflake没有使用索引(参见3.3.3节),查询计划需要查找的空间会比其他一些系统更小。而通过将很多决策延迟到执行时进行,例如join时数据分布的类型,会进一步减少计划空间。这个设计降低了优化器做出坏决定的数量,通过一点点峰值性能损失的代价增加了健壮性。其同样使得系统更加易于使用(性能变得更加可以预测),这与Snowflake整体专注于服务体验的目标相一致。
一旦优化阶段完成,产生的执行计划会被分布式分发到该查询相关的所有工作节点上去。在查询执行期间,云服务会持续的追踪查询执行的状态以收集执行业绩计数并探测节点失效。所有的查询信息及统计数据都会被保存以用于审查和性能分析。用户能够监控并分析过去的和当前正在进行的查询,通过Snowflake的图形化用户接口。
3.3.2 并发控制
正如前面提到过的,并发控制完全由云服务层来处理。Snowflake被设计用于分析型工作负载,其会被巨大的读取、大块的或者小股的插入、以及大块的更新所充斥。像处于这种工作负载空间的绝大部分系统一样,我们决定通过快照隔离级别(Snapshot Isolation)来实现ACID事务。
在快照隔离级别下,一个事务的所有读取都会看到一个事务开启时数据库一致性的快照。遵照习俗,SI被基于多版本并发控制(MVCC)来实现,意味着每一个变化了的数据库对象的拷贝都会在一定期间内被保留。
基于表文件是不可修改的这种事实,MVCC是非常自然的选择,而文件不可修改又是使用S3作为存储的直接结果。对一个文件的修改只能通过使用包含了修改的新文件替换掉原文件来实现。由此得出在一个表上的写操作(插入,更新,删除,合并)会产生一个该表的新的版本,通过添加和删除与先前表版本相关的整个所有文件。文件的添加和删除会在元数据中被追踪(在全局的key-value存储中),以一种可以非常高效的计算某个特定的表版本对应的文件集合的形式来组织。
除了快照隔离级别,Snowflake同样使用这些快照来实现时间游移及高效的克隆数据库对象,细节请查看4.4节。
3.3.3 剪裁
限制只访问与给定查询相关的数据,这是查询执行过程中最重要的方面之一。
在历史上,数据库中的数据访问被限制在通过使用索引的方式,以一种B+树或类似的数据结构。虽然这种方法已被证明在事务性处理中很高效,但是其会在类似于Snowflake这样的系统中引发很多问题。首先,其重度依赖于随机访问,而随机访问无论是基于存储介质(S3)还是数据格式(压缩文件)都是一个问题。其次,维护索引会极大的增加数据量以及数据加载时间。最后,用户需要明确的创建索引————这对于Snowflake的纯服务的方式有些走的太过了。即使在调参建议的帮助下,维护索引也会是一种复杂的、昂贵的、以及有风险的处理过程。
一种替代技术最近在大尺度数据处理中获得了流行:基于最小-最大值的剪裁,也被称为小的物化聚合(small materialized aggregates)、区域映射(zong maps)、和数据略过(data skipping)。这里,系统对于一个给定的数据大块(chunk:一组记录、文件、块(block)等等)维护了其数据分布的信息,特别是该块中的最小及最大值。基于查询谓词,这些值可以被用来决定在一个特定的查询中一个给定的数据块是不是有可能不需要被读取。例如,想象一下文件f1和f2在x列上分别包含了值3..5和4..6。然后,如果一个查询带有谓词WHERE x >= 6,我们就会知道只有文件f2需要被访问。不像传统的索引,这种元数据往往比实际数据小的多,这会产生很小的存储负担,以及很快速的访问。
剪裁技术很好的匹配了Snowflake的设计原则:不依赖于用户的输入;能扩展的很好;并且可以很容易的维护。而且,其对于大块数据的顺序访问非常友好,并且它对于加载、查询优化、以及查询执行等增加了很少的负担。
Snowflake对于每一个独立的表文件都维护了剪裁相关的元数据。该元数据不仅涵盖了普通的关系型列,同样也是半结构化数据内自动检测出的列的一个选择,查看4.3.2节。在优化时,会通过查询谓词来检查元数据以减少(“剪裁”)传递给查询执行的输入文件集合。优化器不仅仅为简单的基本值相关谓词执行剪裁,同样会为复杂的表达式例如WEEKDAY(orderdate) IN (6, 7)来执行剪裁。
除了这种静态的剪裁,Snowflake同样会在执行期间执行动态剪裁。例如,作为hash join执行过程的一部分,Snowflake会收集build端记录的join keys分布的统计信息。该信息随后被推送到probe端,并被用于过滤或整个忽略(若有可能的话)probe端的数据文件。这是除了其他众所周知的像bloom joins这样的技术之外的附加技术。
4. 重点特性
Snowflake提供了关系型数仓所期望的很多特性:完全的SQL支持、ACID事务、标准接口、稳定性和安全性、客户支持,当然还有强大的性能和扩展性。另外,它还引入了一些关系型系统中很少或从来没见过的特性。本章节展示了一些我们认为展现出技术分化的一些特性。
4.1 纯粹的软件即服务(SaaS)体验
Snowflake支持标准的数据库接口(JDBC, ODBC, Python PEP-0249)并可以与形形色色的第三方工具及服务一起工作,例如Tableau, Informatica, 或者Looker。然后,它也提供了与只使用浏览器的系统交互的可能性。一个网页可能看起来是个无关紧要的小事,但其很快就证明了它是一个至关重要的分化。网页使得在任意位置和任意环境下更加容易的访问Snowflake,极大的降低了启动和使用系统的复杂性。对于已经有很多数据存储于云上的情况,它允许了很多用户通过仅仅将Snowflake指向他们的数据就可以执行查询,省掉了下载任何软件的工作。
正如所料,web页面不仅仅允许SQL操作,同样也可以访问数据库catalog、用户和系统管理、监控、使用信息,等等。我们持续的扩展web页面的功能,在诸如在线协作、用户反馈及支持,以及其他方面不断做工作。
但是我们对于易用性和服务体验的专注不会仅仅停留在用户接口上;其会延伸到系统架构的每一个方面。没有失败模式(failure modes),没有调参开关(tuning knobs),没有物理设计(physical design),没有存储梳理任务(storage grooming tasks)。所有的全部都只是数据和查询。
4.2 不间断的可用性
在过去,数据仓库方案是隐藏的很好的后端(back-end)系统,与世界的绝大部分相隔离。在这种环境下,下线时间————无论是计划中的(软件更新或管理任务)还是非计划的(故障)————往往不会对操作产生巨大的影响。但是随着数据分析对于越来越多的商业任务变得至关重要,对数仓的不间断的可用性变成了一个重要的要求。这种趋势反应了现代Saas系统的期望,其中绝大多数是总是在线、直接面对用户的应用,并且没有下线时间。
Snowflake提供了不间断的可用性来满足这些期望。这其中需要考虑的两个主要的技术特性是故障恢复和在线更新。
4.2.1 故障恢复
Snowflake在架构的各个层次上容忍单个的和相关的节点故障,如图2中展示。Snowflake现在的数据存储层是S3,其在多数据中心之间进行复制,在Amazon的术语中被称为“可用区域”或AZs。在多个AZs之间进行复制允许S3抵抗整个AZ的失效,并能确保99.99%的数据可用性和99.999999999%的数据持久性。匹配S3的架构,Snowflake的元数据存储同样是分布式的且在多个AZs之间复制的。如果一个节点失效了,其他节点可以捡起运行其上的活动而不会太影响到终端用户。云服务层剩余的服务是由处于多个AZs中的无状态节点组成的,伴随着一个将用户请求分发到它们的负载均衡器。由此可见一个单个节点失效或甚至是一整个AZ的失效都不会导致整个系统范围的影响,可能会导致当前连到失效节点用户的一些查询会失败。这些用户在执行他们下一次查询时会被重定位到不同的节点。
相反,虚拟仓库(VWs)不会跨AZs进行分布。这个选择是为了性能的原因。高网络吞吐量对于分布式查询的执行是至关重要的,而在相同的AZ内部的网络吞吐量会比跨AZs高的多。如果在查询执行时一个工作节点失效了,查询会失败但会被透明的重新执行,要么基于被立即替换故障节点的节点集合,要么基于一个临时减少个数的节点集合。为了加速节点替换,Snowflake维护了一个后备节点的小资源池。(这些节点也被用于快速VW供应。)
如果整个AZ变得不可用,在该AZ上的一个特定的VW中跑的所有查询都会失败,此时用户需要主动的在一个不同的AZ中重新准备该VW。由于整个AZ的失效属于真正的大灾难,属于极度稀少的事件,我们目前接受这种部分系统不可用的场景,但是希望会在未来解决它。
4.2.2 在线更新
Snowflake不仅在故障发生时提供不间断的可用性,也会在软件升级时提供。系统被设计为允许各种服务的多个不同版本被并列的部署,既包括云服务层组件也包括虚拟仓库层。这是可能的,因为所有的服务都是无状态的。所有的硬状态都被维护在一个事务性的key-value存储中,并会通过一个顾及到元数据版本及schema变更的映射层来访问。任何时候我们修改元数据schema,我们都会确保与先前版本的向后兼容性。
为了执行一个软件升级,Snowflake首先在原先版本服务的旁边部署新版本的服务。随后逐步的按用户账户为单位切换到新版本的服务上,在每一个账户切换的点上,对应用户新发布的所有查询都会被路由到新版本的服务上。所有在老版本服务上执行的查询会被允许在老服务上继续执行完成。一旦所有在老版本服务上的查询及用户完成执行,该版本的所有服务都被终止并停止使用。
图3展示了一个正在进行的更新进程的快照。其中并列的运行着两个版本的Snowflake,版本1(亮色)和版本2(暗色)。其中有一个单体云服务的两个版本,控制着两个虚拟仓库(VWs),每一个都有两个版本。负载均衡器将到来的调用路由到合适的云服务版本上。一个版本的云服务只会与对应版本的VWs进行沟通。
正如上面提到的,两个版本的云服务会共享相同的元数据库。更有甚者,不同版本的VWs可以共享相同的工作节点和它们相应的缓存。结果是,在升级完成后不需要重新填充工作节点上的缓存。整个过程对用户是透明的,没有任何下线时间或性能降级。
在Snowflake中在线升级对于我们的部署速度,以及如何处理重要的bugs同样产生了巨大的影响。在写本论文时,我们每周升级一次我们所有的服务。这意味着我们以每周为频率发布新的特性和改进。为了确保升级过程执行平滑,无论升级还是降级都会持续的在一个特殊的处于生产环境之前的Snowflake集群中进行测试。在一些罕见的场景中,当我们在生产环境中发现了一个重要的bug(未必一定是在升级过程中),我们可以很快的降级到先前的版本上,或者实现一个修复并执行一次非预先安排的升级。这个过程并不像听起来那么吓人,由于我们一直在持续不断的测试并练习升级/降级机制。在这点上是高度自动化和有经验的。
4.3 半结构化数据和无模式数据
Snowflake针对半结构化数据扩展了标准的SQL类型的系统,通过使用三种类型:VARIANT, ARRAY, 和OBJECT。VARIANT类型的值可以存储任何SQL类型(例如DATE, VARCHAR等等)的值,同时也可以存储变长的ARRAYs数据值,以及JavaScript风格的OBJECTs值,和从string到VARIANT值的映射等等。后者在文献中也被称为文档(documents),这引起了文档存储的概念(MongoDB, Couchbase)。
ARRAY和OBJECT类型只是对于VARIANT类型的不同的限制。它们内部的表现是一样的:一个自描述的、压缩的二进制序列化表现,支持快速的key-value查找,同时支持高效的类型校验,比较,以及哈希计算。VARIANT列因此可以被作为join keys,分组keys,以及排序keys,与其他列一样。
VARIANT类型允许Snowflake被用于一种ELT(抽取——加载——转换)的方式,而不是传统的ETL(抽取——转换——加载)方式。没有必要在加载时就指定文档的模式或执行转换。用户可以从JSON,Avro,或XML格式的文件中直接将输入数据加载到一个VARIANT列中;Snowflake会处理解析及类型推导(参见4.3.3节)。这种方法,在文献中被恰当的称为“模式推后(schema later)”,通过解耦信息生产者与信息消费者及中间媒介来允许模式演变(schema evolution)。与此相反,在一个传统ETL流水线中,任何的数据模式变化都需要多个组织和部门的协作,可能需要数月才能执行。
ELT模式和Snowflake的另一个优势是,如果在后面确实有数据转换的需求,则该转换任务可以使用并行SQL数据库的全部力量来完成,包括支持像joins、排序、聚合、复杂的谓词等操作,这些操作在传统的ETL工具链中往往是缺失或不完全的。在这一点上,Snowflake基于完全的JavaScript语法和VARIANT数据类型,提供了可编程的用户自定义函数(UDFs)。对于可编程UDFs的支持进一步增加了可被推进Snowflake中执行的ETL任务的数量。
4.3.1 关系型之外的(Post-relational)操作
对于文档(document)的最重要的操作是对其数据元素的提取,要么通过字段名(对于OBJECTs类型),要么通过偏移(对于ARRAYs类型)。Snowflake在功能性的SQL符号和JavaScript风格的路径语法中都提供了提取操作。内部编码使得提取行为非常高效。一个子元素只是父元素内部的一个指针;并不需要执行数据拷贝。跟随在提取之后的往往是一个投射(cast)操作,将提取出来的VARIANT值转换为一个标准的SQL类型值。再一次,编码使得这些投射行为非常高效。
第二个很常见的操作是铺平(flattening),也就是说,将一个嵌套的文档(document)旋转处理为一个多行数据。Snowflake使用SQL的侧面像来表示铺平操作。这种铺平可以是递归的,允许层次结构的文档被完全的转换成一个适合SQL处理的关系型表。与铺平相反的操作为聚合。Snowflake为此引入了一些新的聚合和分析函数,例如ARRAY_AGG和OBJECT_AGG。
4.3.2 列式存储和处理
对于半结构化数据使用一个序列化的(二进制的)表示是一种将半结构化数据集成到关系型数据中的传统设计选择。不幸的是,行式表示使得这样的数据在存储和处理时没有列式关系型数据高效————这也是将半结构化数据转换为普通关系型数据的通常的原因。
Cloudera Impala(使用Parquet)和Google Dremel已经证明了对半结构化数据的列式存储是可行的而且有好处的。然而,Impala和Dremel(及其外化的产品BigQuery)要求用户提供完整的表schemas用于列式存储。为了同时满足无模式(schema-less)的序列化表示这样的灵活性以及列式关系型数据库的性能,Snowflake引入了一个新颖的自动化的方法来执行类型推断和列式存储。
正如在章节3.1中提到的,Snowflake以一种混合列式格式来存储数据。当存储半结构化数据时,系统自动的对一个表文件中的文档集合执行静态分析,执行自动的类型推断,并决定哪些(带类型的)路径是频繁出现的。这些列随后会被从文档中删除并被单独的保存起来,使用与关系型数据相同的压缩的列存格式。对于这些列,Snowflake甚至会为其计算物化的聚合值以用于剪裁(参见3.3.3),和普通的关系型数据一样。
在一个扫描过程中,不同的列可以被重新聚合成一个VARIANT类型的列。然而,绝大部分查询只对原始文档中的一部分列感兴趣。在这些场景下,Snowflake将投影和类型投射表达式下推到扫描操作符中,如此一来只会有必要的列被访问并被直接投射转换到目标SQL类型上。
上面描述的优化行为会为每一个表文件独立的执行,这允许了高效的存储和提取,即使在schema变更时。然而,考虑到查询优化,其确实引起了一些挑战,特别是对于剪裁。假设一个查询有一个在某路径表达式上的谓词,我们想要使用剪裁来限制被扫描的文件集合。该路径及其对应的列可能在绝大部分文件中都存在,但是只在其中的一些文件中足够频繁从而有必要被提取出来单独存储并保存其元数据。保守的解决方法是,简单的扫描所有没有合适元数据的文件。Snowflake改进了这个方法,通过计算在文档中出现的所有路径(不是值!)的Bloom Filters。这些Bloom Filters与其他的文件元数据保存在一起,并在剪裁时被查询优化器查看。不包含查询所需要路径的表文件就可以被安全的忽略了。
4.3.3 乐观的转换(Optimistic Conversion)
由于某些原生的SQL类型,特别是date/time类型值,会在常用的外部格式例如JSON或XML中被表示为字符串,因此这些值需要从字符串类型转换为它们实际的类型,无论是在写入的时候(在insert或update时)还是在读取的时候(在查询时)。没有一个类型模式(typed schema)或等价的线索,这些字符串转换就需要在读取的时候被转换,而这在一个读占大多数的工作负载环境中肯定不如写入时只转换一次来的高效。无类型数据的另一个问题是缺少合适的元数据来执行剪裁,这对于日期类型来说尤为重要。(分析型工作负载经常会带有日期列上的谓词。)
但是如果在写入时进行转换,自动转换可能会导致丢失信息。例如,一个包含了数字类型的产品标识符的字段可能实际上不是一个数字,而是一个前面有大量0的字符串。相似的,看上去像一个日期的列可能实际上是一个文本消息的内容。Snowflake通过执行乐观的数据转换来解决这个问题,同时保留自动转换的结果和原始字符串(除非这是一个完全可逆的转换),在独立的列中。如果一个后续的查询需要原始的字符串,其可以被简单的获取或重构出来。由于不使用的列不会被加载和访问,因此对某些列的双倍存储对查询性能的影响是极小的。
4.3.4 性能
为了评估在半结构化数据上的列存、乐观转换,以及剪裁对于查询性能的混合效果,我们使用类似于TPC-H的数据和查询指导了一组实验。
我们创建了两种类型的数据库schemas。首先,是一个传统的,关系型TPC-H schema。其次,一个“无模式的”数据库schema,其中每一个表只包含一个类型为VARIANT的列。我们随后创建了聚集的(排序的)SF100和SF1000数据集(相应的大小为100GB和1TB),将数据集保存为普通的JSON格式(也就是说,日期变成了字符串),接着将数据加载到Snowflake中,同时使用关系型和无模式的数据库schemas。没有给系统任何关于无模式数据的字段、类型,以及群集相关的线索,并且没有做任何的参数调优。我们随后在无模式数据库上定义了一些视图,为了在所有的四个数据库上可以跑完全相同的TPC-H查询。(在写入时,Snowflake不会使用任何视图来做类型推断或其他优化,因此这仅仅是一个语法上的方便,不会对性能造成任何提升。)
最终,我们在四个数据库上跑了所有22个TPC-H的查询,使用一个中等标准的仓库。图4展示了测试结果。其中的数值是在三轮测试中被观测的,在被预热的缓存之下。标准错误是不显著的,因此从结果中被忽略了。
正如所见,对无模式数据的存储和查询执行的开销大约在10%,除了两个查询(在SF1000上的Q9和Q17)。对于这两个查询,我们认为慢的原因对join顺序选择了不太优的解,由一个已知的对于不同值(distinct value)估计时的bug所导致。我们会持续的改善对于半结构化数据的元数据收集和查询优化。
总之,在具有相对稳定和简单的schema的半结构化数据(例如,实践中大部分机器产生的数据)上的查询性能,几乎可以匹敌传统的关系型数据的性能,其享受了列式存储、列式执行、以及裁剪的所有好处————不需要用户的任何努力。
4.4 时间游移和克隆
在章节3.3.2中,我们讨论了Snowflake是如何基于多版本并发控制(MVCC)实现快照隔离(SI)的。在一个表上的写操作(insert, update, delete, merge)会产生该表的一个新版本,通过添加和删除整个文件的方式。
当文件被一个新版本删除,它们会在一个可配置的使其内被保留(当前是90天)。文件的保留允许Snowflake高效的读取表的原先的版本;也就是说,对数据库执行时间游移。用户可以在SQL中方便的使用AT或BEFORE语法来使用这个特性。时间戳可以是绝对值,与当前时间的相对值,或与先前语句的相对值(通过ID指定)。
SELECT * FROM my_table AT(TIMESTAMP =>
’Mon, 01 May 2015 16:20:00 -0700’::timestamp);
SELECT * FROM my_table AT(OFFSET => -60*5); -- 5 min ago
SELECT * FROM my_table BEFORE(STATEMENT =>
’8e5d0ca9-005e-44e6-b858-a8f5b37c5726’);
甚至可以在一个查询中访问同一个表的不同版本。
SELECT new.key, new.value, old.value FROM my_table new JOIN my_table AT(OFFSET => -86400) old -- 1 day ago
ON new.key = old.key WHERE new.value <> old.value;
基于相同的底层元数据,Snowflake引入了UNDROP关键字来快速的恢复被意外删除的表、schemas、或整个数据库。
DROP DATABASE important_db; -- whoops!
UNDROP DATABASE important_db;
Snowflake也实现了一个我们称为克隆的功能,通过新的关键字CLONE来表示。克隆一个表会快速使用相同的定义和内容来创建一个新表,并不会对表数据文件进行物理拷贝。克隆操作简单的拷贝了原表的元数据。在克隆之后,两个表都引用了相同的数据文件集,但是在此之后每个表都可以被独立的修改。克隆特性也支持整个的schemas或者整个的数据库,考虑到高效的快照。在大批量更新之前,或执行冗长的、探索性的数据分析之前打快照是一个很好的实践。CLONE关键字甚至可以和AT及BEFORE组合,允许这样的快照在事实发生之后被制造出来。
CREATE DATABASE recovered_db CLONE important_db
BEFORE( STATEMENT => ’8e5d0ca9-005e-44e6-b858-a8f5b37c5726’);
4.5 安全
Snowflake被设计为在架构的所有层次上都会保护用户数据以免被攻击,包括在云平台的层面上。为此,Snowflake实现了双重认证,(客户端)加密数据的导入和导出,保护数据的传输和存储,以及对数据库对象的基于角色的访问控制(RBAC)。在任何时候,数据在被发送到网络之前、被写入到本地磁盘或共享存储(S3)之前都要加密。这样一来,Snowflake提供了完整的端到端的数据加密和安全。
4.5.1 秘钥层次体系
Snowflake使用强劲的AES 256位加密算法,带有一个源于AWS CloudHSM的分层的秘钥模型。秘钥会被自动的旋转并重新加密(“秘钥更新”)以确保秘钥完成了完整的NIST 800-57秘钥管理生命周期。加密和秘钥管理对用户而言是完全透明的,不需要任何的配置和管理。
Snowflake的秘钥层次,如图5中所示,有四个级别:root keys, account keys, table keys, 和file keys。每一层秘钥(父亲)会加密(也就是包裹)下面一层秘钥(孩子)。每一个account key会对应一个用户账户,每一个table key会对应一个数据库表,每一个file key对应一个表文件。
层次的秘钥模型是一种很好的安全实践,因为它们约束了每一个秘钥保护的数据量。每一层都减小其下面层次的秘钥的范围,正如图5中的盒子所指示的。Snowflake的层次秘钥模型确保了在其多租户架构中用户数据的隔离性,因为每一个账户都有一个独立的account key。
4.5.2 秘钥生命周期
......本部分暂不关注,有兴趣的自行去看......
4.5.3 端到端的安全
Snowflake使用AWS CloudHSM作为一个防篡改的、高度安全的方式来创建、保存、及使用秘钥层次结构中的root keys。AWS CloudHSM是一组硬件安全模块(HSMs),其被连接到AWS中的一个虚拟的私有集群上。root keys永远也不会离开HSM设备。所有使用root keys的加密操作都会由HSMs自己来执行。这样一来,低层次的秘钥不可能在没被授权访问HSM设备的情况下被解密。HSMs也会被用于创建account级别的和table级别的秘钥,包括了秘钥的旋转和更新。我们将AWS CloudHSM配置为其高可用模式,以最小化服务中断的可能性。
除了数据加密,Snowflake也会以如下的方式保护用户数据:
- 通过S3上的访问策略来实现存储隔离。
- 在用户账户中基于角色的访问控制,支持对数据库对象的细粒度访问控制。
- 加密数据的导入和导出,云供应商(Amazon)无法看到数据明码。
- 对安全访问控制的双重的和联邦的认证
总之,Snowflake提供了一个基于AWS CloudHSM的层次化的秘钥模型,并使用秘钥的旋转和更新来确保秘钥遵循一个标准的生命周期。秘钥管理对于用户是完全透明的,不需要任何配置、管理、或停机时间。它属于一个详尽的安全策略的一部分,该安全策略确保了完全的端到端的加密和安全。
5. 相关工作
基于云的并行数据库系统
Amazon有一些DBaaS产品,底层基于Amazon Redshift作为数据仓库产品。由并行数据库系统ParAccel演化而来,Redshift可以认为是第一个真正的被提供为一个服务的数据仓库系统。Redshift使用一个标准的无共享架构。如此一来,虽然是可扩展的,但是添加或删除计算资源时需要数据重分布。相对的,Snowflake的多集群,共享数据架构允许用户立刻的扩大、缩小、或甚至暂停计算,与存储完全独立地,无需数据移动————包括使用隔离的计算资源来集成数据的能力。同样,遵循一个纯服务的原则,Snowflake要求在客户的角度不需要有物理调优,数据梳理,手动收集表统计信息,或者表清理等。虽然Refshift可以集成例如JSON这样的半结构化数据作为一个VARCHAR,但是Snowflake对于半结构数据有原生的支持,包括诸如使用列式存储这样重要的优化。
Google的云平台提供了一个被称为BigQuery的全托管的查询服务,其是对Dremel的公开实现。BigQuery服务允许用户在TB级数据上以飞快的速度执行查询,在数千节点上并行执行。Snowflake的一个灵感来源就是BigQuery对于JSON和嵌套数据的支持,我们发现这对于一个现代的分析平台是必要的。但是虽然BigQuery提供了一种类SQL的查询语言,但它在语法和语义上与ANSI SQL有一些基本的背离,这使得它在应用到基于SQL的产品时有些难办。同时,BigQuery的表是只可追加且要求schema的。相对的,Snowflake提供完整的DML(insert, update, delete, merge),ACID事务,并且对于半结构化数据不要求schema定义。
Microsoft SQL数据仓库(Azure SQL DW)是一个最近添加到Azure云平台上的服务,基于SQL Server及其分析平台系统(APS)。与Snowflake相似,它将存储与计算分离了。计算型的资源可以通过数仓单元(DWUs)而扩展。其最大并发的程度被限制了。对于任何的数仓,最大并发执行查询的数量都是32。相对的,Snowflake允许通过虚拟仓库来独立的扩展并发的工作负载。Snowflake的用户也被从选择合适的分布式键及其他管理任务中解放出来。虽然Azure SQL DW通过PolyBase确实能支持在非关系型数据上的查询,但相较于Snowflake的VARIANT类型及相关优化,它并没有对半结构数据的内置支持。
文档存储和大数据
文档存储,例如MongoDB、Couchbase Server、以及Apache Cassandra,近些年在应用开发者中变得越来越流行,由于其提供的扩展性、简单性、以及schema灵活性。然而,这些系统提供的简单的key-value和CRUD(create, read, update, 和delte)接口导致的一个挑战就是难以表达复杂的查询。作为回应,我们看到了一些类SQL查询语言例如Couchbase的N1QL或Cassandra的CQL的出现。此外,很多“大数据”引擎现在支持在嵌套数据上的查询,例如Apache Hive,Apache Spark,Apache Drill,Cloudera Impala,以及Facebook Presto。我们相信这说明了一种在无模式数据及半结构化数据上执行复杂查询的确切需求,并且我们对于半结构化数据的支持是受到了很多这种类型系统的启发。使用模式推断、乐观转换、以及列式存储,Snowflake将这些系统的灵活性及存储效率与关系型列存储数据库的执行速度结合起来。
6. 经验教训和展望
当Snowflake在2012年被发起时,整个数据库世界完全专注在SQL on Hadoop之上,在短短的时间之内一打系统冒了出来。那时候,在一个完全不同的方向上工作,构建一个云上“标准的”数据仓库的决定看起来是一种逆势且非常冒险的举动。在经过了3年的开发之后,我们现在有信心说这是正确的。Hadoop并没有替换掉RDBMSs;它对其做了补充。人们仍然想要关系型数据库,但是需要更加高效、更加灵活、更加适应云环境。
Snowflake符合了我们对于一个构建在云上的系统能够提供给其用户和开发者哪些东西的期望。多集群、共享数据架构带来的弹性已经改变了用户执行他们数据处理任务的方式。SaaS模型不仅使得用户更容易的尝试和接受本系统,同事也极大的帮助了我们的开发和测试。有了一个单个生产环境版本和在线升级,我们可以比在一个传统的开发模式下更快的发布新特性、提供改进、以及修复问题。
虽然我们希望对半结构化数据支持的扩展被证明是有用的,但我们还是被对其接受的速度所震惊了。我们发现一个非常流行的模式,其中机构会使用Hadoop做两件事:存储JSON,以及将其转换成可被加载到RDBMS的格式。通过提供一个可以高效的原样存储和处理半结构化数据的系统————顶部带着一个强大的SQL接口————我们发现Snowflake不仅替换了传统的数据库系统,也替掉了Hadoop集群。
这当然不会是一个毫无痛苦的旅程。虽然我们的团队加起来有超过100年的数据库开发专业经验,但我们一路上仍然犯了很多可避免的错误,包括早期对于一些关系型操作符的过于简化的实现,早期不去合并引擎中的所有数据类型,对资源管理的关注太晚,耽搁对于日志和时间功能的工作,等等。而且,我们持续的专注于避免调节开关,引发了一系列的工程上的挑战,最终带来很多令人激动的技术方案。结果是,今天,Snowflake只有一个调节参数:用户想要多少性能(同时也意味着想花多少钱)。
虽然Snowflake的性能已经非常有竞争力,特别是考虑到无需调节参数这方面,但我们知道还有很多的优化我们尚且没有时间来实现。有些出乎意料的是,对于我们的用户来说,核心性能几乎不是问题。原因是通过虚拟仓库带来的弹性计算可以提供偶尔出现的对性能的爆炸需求。这使得我们将我们的开发努力专注在了其他方面。
我们面临的最大的技术挑战与系统的SaaS和多租户方面相关。构建一个可以支持成百上千的并发用户的元数据层是很有挑战性且很复杂的工作。处理形形色色的节点失败、网络失效、以及支持服务是一场永无止境的战斗。安全已经是而且将持续是一个大的话题:保护系统和用户的数据免受外部攻击、用户自己、以及我们内部用户的伤害。维护一个活跃系统,其中包含了成百上千的节点,每天运行数百万的查询,在带来成就感的同时,也要求一个高度集成的方案来部署、操作、及支持。
Snowflake的用户持续的把越来越大并且越来越复杂的问题抛给本系统,这影响了其演变路线。我们现在致力于通过提供额外的元数据结构以及数据重新组织任务来改善数据访问的性能————着重在无需用户交互。我们持续的改进和扩展核心的查询功能,既包含标准SQL也包含半结构化扩展。我们计划进一步改善数据偏斜处理和负载均衡策略,其重要性随着我们用户工作负载的增加而增加。我们致力于简化用户的工作负载管理,使系统更加的弹性。并且我们也致力于与外部系统的集成,包括例如高频数据加载这样的问题。
对于Snowflake,未来最大的挑战是转变为一个完全自助的服务模型,其中用户可以注册并与系统交互,不需要我们在任何阶段的参与。这会带来大量的安全、性能、以及支持方面的挑战。我们期待着它们的到来。
7. 致谢
略......
=====================================================================================
本技术论文的翻译工作由不动明王1984独自完成,特此声明。
翻译辛苦,珍惜劳动,引用时请注明出处!
=====================================================================================