Snowflake弹性数仓论文翻译(上)

2022-03-10 00:00:00 查询 数据 执行 文件 节点

在公有云时代,公有云平台几乎能够按需提供无限的计算和存储资源。同时,SaaS模型将企业级系统带给了无法负担成本和相关系统复杂性的用户。传统的数仓系统正在努力的适应新的环境,但是传统数仓毫无疑问还存在一些设计上的局限性,传统数仓往往都是为固定资源而设计,因而没办法利用云的弹性能力。另外,传统的数仓依赖复杂的ETL流水线和物理调优,这个是和云的半结构数据和快速解决工作问题的新形式所需要的弹性和快速不一致的。

基于此,我们决定进行根本性的重新设计,建立云上的企业级数据仓库的解决方案。这便是Snowflake的设计初衷。本文将会主要介绍Snowflake的设计、多集群共享数据的结构和Snowflake的关键特性:的弹性和可用性、半结构化和schema-less的数据支持、时间旅行、端到端的安全性。


一、Snowflake介绍

云技术的出现标志着软件的发展将从本地服务器上的交付和运行转移到由亚马逊、谷歌或微软等平台提供商提供的共享数据中心和软件即服务的解决方案。云的共享基础设施保证了规模经济,极高的可扩展性和可用性,现收现付成本模式,以便适应不可预测的使用需求。但是,只有当软件本身能够在商品资源池(云)上弹性伸缩时,才能获得这些优势。传统的数据仓库解决方案先于云,被设计成在小型静态集群上运行,这些集群由性能良好的机器组成,这使得传统的数仓在体系结构上不适合云。

随着平台的变化,数据也跟着发生了变化。过去的情况是,数据仓库中的大多数数据都来自组织内部的数据源事务化:事务系统、企业资源规划(ERP)应用程序、客户关系管理(CRM)应用程序等。数据的结构、数量和速率都是可预测和可掌握的。但随着云技术的发展,大量且快速增长的数据来源于不那么可控或外部的来源:应用程序日志、web应用程序、移动设备、社交媒体、传感器数据(物联网)。除了不断增长的数据量,这些数据经常以schema-less、半结构化的格式存储。传统的数据仓库解决方案正在努力处理于这种新的数据。这些解决方案非常依赖于ETL管道和物理调优,但管道和物理调优是假定来自内部的可预测、不易移动且易于分类的数据。

为了应对这些缺点,部分数仓社区已经转向Hadoop或Spark等大数据平台。尽管这些工具对于数据中心规模的处理任务来说是不可或缺的,开源社区也在不断地进行重大改进,如Stinger Initiative(号称让Hive提速100倍),但它们仍然缺乏现有数仓的效率和功能。但重要的是,他们需要大量的努力来推广和使用。

我们设计Snowflake的初衷就是基于当下传统的数据仓库技术或大数据平台并不能很好的与云进行联动,为此我们专门为云构建了一个全新的数据仓库系统,这便是Snowflake弹性数据仓库。与云数据管理领域的许多其他系统不同,Snowflake并不是基于Hadoop、PostgreSQL之类的,处理引擎和其他大部分组件都是从新开发的。


Snowflake的关键特点如下:

Saas级服务体验。用户无需购买机器、雇佣数据库管理员或安装软件。用户基于自身系统在云上的数据,借助于Snowflake的图形界面或ODBC等标准化接口就可以立即操作和查询数据。

关系型。Snowflake支持ANSI的SQL和ACID的事务。大部分的用户几乎无需改动或者很小的改动就能迁移已经存在的工作内容。

半结构化。Snowflake提供了用于遍历、展平和嵌套半结构化数据的内置函数和SQL扩展,并支持JSON和Avro等流行格式。自动模式发现和列式存储使得对schema较少的半结构化数据的操作几乎与对普通关系数据的操作一样快,而无需用户额外的操作。

弹性。存储和计算资源可以独立无缝地扩展,而不影响数据可用性或并发查询的性能。

高可用。Snowflake能够容忍节点,集群,甚至全部的数据中心失败。在软件和硬件更新的时候不会下线。

持续性。Snowflake的设计具有极高的持续性,可防止意外数据丢失:克隆、下线和跨区域备份。

经济节约。Snowflake具有很高的计算效率,所有的表数据都被压缩。用户只为他们实际使用的存储和计算资源付费。

安全。所有数据包括临时文件和网络流量都是端到端加密的。没有用户数据暴露在云平台上。此外,用户能够基于角色在SQL级别执行级别细粒度的访问控制。

Snowflake现在运行在亚马逊云上面(截止目前已经扩展在亚马逊、微软、谷歌三家主要云厂商),但是未来我们也会支持其他云平台。在写这篇文档的同时,Snowflake被大大小小的组织用于生产。每天运行几百万次查询在几PB的数据上。


二、存储与计算

Shared-nothing结构已经成为高性能数据仓库的主流体系结构,主要原因有两个:可扩展性和商用硬件。在Shared-nothing结构中,每个查询处理器节点都有自己的本地磁盘。表是跨节点水平分区的,每个节点只负责其本地磁盘上的行。这种设计可以很好地扩展星型模式查询,因为将一个小的(广播的)维度表与一个大的(分区的)事实表连接起来只需要很少的带宽。而且,由于共享数据结构或硬件资源几乎没有争用,因此不需要昂贵的定制硬件。

在纯粹的Shared-nothing结构中,每个节点都有相同的功能并在相同的硬件上运行。这种结构设计无疑是良好的。不过,纯粹的Shared-nothing结构有一个重要的缺点:它将计算资源和存储资源紧密耦合,这在某些场景中会导致问题。

异构工作负载。由于对于工作负载的要求不同,在同一硬件配置下,对于大容量加载(高I/O带宽,轻计算)来说,均衡的系统配置不适合复杂查询(低I/O带宽,重计算),反之亦然。这使得硬件的总体资源使用率较低。

节点关系变化。如果发生节点更改、节点故障,或是用户主动进行了系统调整,则大量数据需要重新shuffle。由于相同的节点同时负责数据shuffle和查询,因此会对性能有显著的影响,从而限制了灵活性和可用性。

在线升级。虽然通过副本机制可以在一定程度上减轻节点关系变化更改的影响,但软件和硬件升级终会影响系统中的每个节点。虽然原则上还是可以进行在线升级。但是由于所有节点都是紧密耦合的,并且都是同质的,这使得实现在线升级变得非常困难。

在内部部署环境中,这些问题通常不会产生太过巨大的影响因为节点的升级、故障和系统扩容发生的频率并不高。

而在云中情况则不大相同。像亚马逊EC2这样的平台具有许多不同的节点类型。它们只需简单将数据移动到正确类型的节点,同时,节点故障更为频繁,性能可能会发生巨大变化,甚至在相同类型的节点之间也是如此。因此,节点关系的改变不是偶然,而是常态。后,在线升级和弹性扩展逐渐成为了当下客户的一个重大痛点。在线升级能够大幅缩短软件开发周期,提升系统的可用性。

基于此,Snowflake实现了存储和计算分离。存储和计算由两个松耦合、独立可扩展的服务来处理。计算是通过Snowflake的(专有的)shared-nothing引擎提供。存储是通过亚马逊S3提供的,支持多类型的对象存储(Azure 对象存储,Google云存储)。同时,为了减少计算节点和存储节点之间的网络流量,每个计算节点在本地磁盘上缓存了一些表的数据。

存储和计算分离的另一个好处是实现了数据的冷热分离,本地磁盘空间不用存储整个数据,这些数据可能非常大,而且大部分是冷数据(很少访问)。本地磁盘专门用于临时数据和缓存,两者都是热数据(建议使用高性能存储设备,如ssd)。因此,缓存了热数据,性能就接近甚至超过纯shared-nothing结构的性能。我们称这种新的体系结构为multi-cluster、 shared-data结构。


三、结构

Snowflake是Saas服务商。除了提供高可用性和操作性之外,还需要兼顾高可用。为此,Snowflake是一个面向服务的体系结构,由高度容错和独立可扩展的服务组成。这些服务通过RESTful接口进行通信,分为三个体系结构层:

数据存储。该层使用amazon s3存储表数据和查询结果。

虚拟仓库。系统的“肌肉”。该层通过弹性的虚拟集群(称为虚拟仓库),执行查询。

云服务。系统的“大脑”。这一层是管理虚拟仓库、查询、事务和围绕虚拟仓库的所有元数据的服务的集合,包含数据库元信息、访问控制信息、加密密钥、使用情况统计等。

图1展示了Snowflake三个架构层及其主要组件。

图1:multi-cluster、 shared-data架构


3.1 数据存储

AWS被选为Snowflake的初始平台主要有两个原因。首先,AWS是云平台市场上成熟的产品。其次(与点相关),AWS提供了大的潜在用户资源。

接下来的选择是使用S3还是基于HDFS或类似的技术开发我们自己的存储服务。我们花了一些时间对S3进行了测试,发现虽然它的性能可能有些不同,但它的可用性、高可用性和强大的持续性有可靠的保证。因此,我们没有开发自己的存储服务,而是决定将精力投入到虚拟仓库层的本地缓存和数据倾斜处理技术上。

相比于本地存储,S3虽然具有更高的访问延迟。但是,S3是一个对象存储,具有一个相对简单的基于HTTP的PUT/GET/DELETE接口。对象(即文件)只能完全写入。甚至不可能将数据附加到文件的末尾。文件的确切大小需要在PUT请求中前就确定。并且,S3支持对部分(范围)文件的GET请求。

这些属性对Snowflake的表文件格式和并发控制方案有很大的影响(参见第3.3.2节)。表被水平地划分成大的、不可变的文件,这些文件相当于传统数据库系统中的块或页。在每个文件中,每个属性或列的值都被分组在一起并进行了大量压缩,这是一种普遍采取的方案,在文献[2]中称为PAX或hybrid columnar。每个表文件都有一个表头,其中包含文件中每列的偏移量,以及其他元数据。因为S3允许对部分文件执行GET请求,所以查询只需要下载文件头和它们需要的列。

Snowflake不仅在表数据上使用S3。当本地磁盘空间耗尽时,它还使用S3存储由查询(例如,大量连接)生成的临时数据,以及大型查询结果。将temp数据溢出到S3,系统可以计算任意大的查询,而不会出现内存不足或磁盘不足的错误。将查询结果存储在S3中,实现了客户端交互新方式并简化查询处理,因为它消除了对传统数据库系统中的服务端游标的需要。

元数据,例如catalog信息,由S3文件、统计信息、锁、事务日志等组成,存储在可伸缩的事务KV存储中,这也是云服务的一部分。


3.2 虚拟仓库

虚拟仓库层由EC2实例集群组成。每个这样的集群通过一个称为虚拟仓库(VW)的抽象呈现给用户。构成虚拟仓库的单个EC2实例称为工作节点。用户不直接与工作节点交互。事实上,用户不需要关心哪个或者多少个工作节点组成了一个虚拟仓库。虚拟仓库按照大家所熟悉的“T恤尺寸”从X-Small到XX-Large不等来标识规模大小。这样的抽象展示允许我们独立于底层云平台来发展服务和定价。

3.2.1 弹性与隔离

VM层是纯计算资源,可以按照需求创建、销毁并且可以随时改变大小。创建或者销毁一个VM对数据库状态没有任何影响。当没有查询时候,用户可以关闭所有的VM资源。这种弹性容许用户独立于数据层,按照需求动态的伸缩他们的计算资源。

每个查询只在一个VW上运行。工作节点不会在VW之间共享,从而使查询具有强大性能隔离。(也就是说,我们将工作节点共享视为未来工作的一个重要领域,因为对于性能隔离不太重要的用例,它将实现更高的利用率和更低的成本。)

提交新查询时,相应VW中的每个worker节点(或者如果优化器检测到一个小查询,则为节点的子集)生成一个新的worker进程。每个工作进程只在其查询时间内工作。worker进程本身,即使有update语句,不会对外部可见造成影响,因为表文件是不可变的,参见第3.3.2节。因此,worker故障很容易被控制,并且通过重试来解决。不过,Snowflake目前不支持部分重试,因此非常大的、长时间运行的查询是一个值得关注的领域和未来的工作。

每个用户可以在任何给定的时间运行多个VW,而每个VW又可以运行多个并发查询。每个VW都可以访问相同的共享表,而无需物理复制数据。

共享的无限存储意味着用户可以共享和集成所有数据,这是数据仓库的核心原则之一。同时,用户受益于私有计算资源,避免了不同工作和组织的干扰,这也是数据集市的原因之一。这种弹性和隔离使得一些新的使用策略成为可能。对于Snowflake用户来说,通常有几个VM用于来自不同组织的查询,通常是连续运行的,并按需定期启动VM,例如批量加载。

另一个与弹性有关的重要结果是,通常可以用大致相同的价格获得更好的性能表现。例如,在具有4个节点的系统上,数据加载需要15小时,而在具有32个节点的系统上,数据加载可能只需要2小时。由于计算时间是付费的,所以总体成本非常相似,但用户体验却截然不同。因此,我们相信弹性是Snowflake架构大的优点和区别之一,因为需要一种新颖的设计来利用云的独特功能。


3.2.2 本地缓存和文件窃取

每一个worker节点在本地磁盘上缓存了一些表数据。缓存的文件是一些表文件,即节点过去访问过的S3对象。准确地说,缓存保存文件头和文件的各个列,因为查询只下载它们需要的列。

缓存在工作节点的工作时间内有效,并在并发和后续工作进程(即查询)之间共享。它只是一个文件流和列请求,并遵循一个简单的近少使用(LRU)替换策略,而忽略了单个查询。这个简单的方案效果比较好,但是我们将来可能会改进它,以便更好地匹配不同的工作。

为了提高命中率并避免VW的工作节点之间对单个表文件进行冗余缓存,查询优化器使用表文件名上的一致哈希将输入文件集分配给工作节点。因此,访问同一表文件的后续查询或并发查询将在同一工作节点上执行。

Snowflake中一致的hash是lazy的。当工作节点由于节点故障或VW调整大小而更改时,不会立即对数据进行shuffle。相反,Snowflake依赖LRU替换策略终替换缓存内容。此解决方案将替换缓存内容的成本分摊到多个查询上,从而获得比立即缓存或纯shared-nothing系统更好的可用性,后者需要立即在节点之间shuffle大量表数据。它简化了系统,因为没有“降级”模式。

除了缓存,倾斜处理在云数据仓库中尤为重要。由于虚拟化问题或网络争用,某些节点的执行速度可能比其他节点慢得多。在这点上,Snowflake在扫描文件的时候就处理了这个问题。每当工作进程完成对其输入文件集的扫描时,它就会从其对等进程请求其他文件,我们称之为文件窃取。当请求到达时,如果一个worker发现它的输入文件集合中还有许多文件要处理,这个时候又有其他worker请求帮助,这个worker将这个请求中他需要的查询的范围内的一个剩余文件的处理权力转移给其他worker。然后其他worker直接从S3下载文件,而不是从这个worker下载。这种设计确保了文件窃取不会给当前worker增加额外的处理负担。


3.2.3 执行引擎

如果一个1000个节点的系统执行一个查询,而另一个系统可以使用10个这样的节点就可以用同样时间完成这个查询,那么这个系统就没有什么价值了。因此,虽然可伸缩性是首要的,但每个节点的效率同样重要。我们希望给用户提供市场上任何数据库服务产品中好的性价比,因此我们决定实现我们自己的SQL执行引擎。我们构建的引擎是列式的、向量化的和基于push的。

列式存储和执行通常被认为优于行式存储和执行,因为它更有效地使用了CPU缓存和SIMD指令,并且更有可能进行(轻量级)压缩。

向量化执行,与MapReduce相比,Snowflake避免了中间结果的物化。相反,数据是以pipeline方式处理的,每次以列成批处理几千行。这种方法由VectorWise(初是MonetDB/X100)首创,这能节省了I/O并大大提高了缓存效率。

基于push的执行是指,关系运算符将结果推送到其下游运算符,而不是等待这些运算符拉取数据(经典的火山式模型)。Push-based提高了缓存效率,因为他消除了循环中的控制逻辑。它还使Snowflake能够高效地处理DAG形式的计划,而不仅仅是树的结构,从而可能更好的采用共享和管道化的方式利用中间结果。

同时,传统查询处理中的许多开销在Snowflake中并不存在。需要注意的点,在执行时候不需要事务管理。就引擎而言,查询是针对一组固定的不可变文件执行的。此外,没有缓冲池。大多数查询扫描大量数据。这里没有使用内存进行表缓冲和操作。不过,Snowflake,在内存耗尽时,允许所有主要操作(join、group by、sort)溢写到磁盘。纯内存引擎虽然更精简,也许更快,但它的限制性太强,无法处理所有的查询情况。分析型工作有的时候会有大量的join或aggregation。


3.3 云服务

虚拟仓库是临时的、特定于用户的资源。相比,云服务层在很大程度上是多租户的。这一层的每个服务访问控制、查询优化器、事务管理器和其他服务都是长期存在的,并在许多用户之间共享。多租户提高了利用率并减少了管理开销,这比传统体系结构中每个用户都有一个完全私有的系统在体系结构上具有更好的规模经济。

每个服务都被复制以实现高可用性和可扩展性。因此,单个服务节点的故障,可能导致某些正在运行的查询可能会失败(并透明地重新执行),但是不会导致数据丢失或可用性下降。


3.3.1 查询管理和优化

用户所有查询都通过云服务层。云服务层处理查询生命周期的所有早期阶段:解析、对象解析、访问控制和计划优化。

Snowflake的查询优化器遵循典型的层叠式方法,采用自顶向下的基于成本的优化。所有用于优化的统计信息都会在数据加载和更新时自动维护。由于Snowflake不使用索引(参见第3.3.3节),因此执行计划搜索的空间会比其他一些系统更小。通过将许多决策下推到执行时候(例如连接的数据分布类型),执行计划搜索的空间进一步减少。这种设计减少了优化器做出的错误优化,以峰值性能的小损失为代价增加了健壮性。它还使系统更易于使用(性能变得更可预测),这与Snowflake对服务体验的整体关注是一致的。

优化器完成后,生成的执行计划将分发给部分查询节点。当查询执行时,云服务会不断跟踪查询的状态,收集性能指标并检测节点故障。所有查询信息和统计信息都存储起来,进行审计和性能分析。用户可以通过Snowflake图形界面监视和分析之前和正在进行的查询。


3.3.2 并发控制

如前所述,并发控制完全由云服务层处理。Snowflake是为分析工作而设计的,这些工作往往会有大量读取、批量或随机插入以及批量更新。与大多数系统一样,我们决定通过快照隔离(Snapshot Isolation,SI)实现ACID事务。

在SI下,事务的所有读取都会看到事务启动时数据库的一致快照。通常,SI是在多版本并发控制(MVCC)之上实现的,这意味着每个更改的数据库对象的一个副本都会保留一段时间。

MVCC是一个自然的选择,因为表文件是不可变的,这是使用S3存储的结果。只有将文件替换为包含更改的其他文件,才能对文件进行更改。因此,表上的写操作(insert、update、delete、merge)通过添加和删除相对于上一个表版本的整个文件来生成新版本的表。在元数据(在全局键值存储中)中跟踪文件的添加和删除,这种形式对属于特定表版本的文件集计算非常高效。

除了SI之外,Snowflake还使用这些快照来实现时间旅行和数据库对象的高效复制,详细信息请参见第4.4节。


3.3.3 剪枝优化

限制对给定查询数据的访问是查询处理重要的方面之一。从历史上看,数据库中的数据访问是通过使用B+树或类似数据结构形式的索引来限制的。虽然这种方法被证明对事务处理非常有效,但对于像Snowflake这样的系统,它会引发多个问题。首先,它严重依赖随机访问,由于存储介质(S3)和数据格式(压缩文件),这将是一个问题。其次,维护索引显著增加了数据量和数据加载时间。后,用户需要显式地创建索引,这与Snowflake的纯服务方法不一样。即使在调优人员的帮助下,维护方面也可能是一个复杂、昂贵有风险的过程。

另一种技术近在大规模数据处理中得到了广泛应用:基于小-大值的修剪,也称为小物化聚合、区域映射和数据跳跃。这里,系统维护给定数据块(记录集、文件、块等)的数据分布信息,特别是块内的小值和大值。根据查询谓词的不同,这些值可用于确定给定查询可能不需要给定的数据块。例如,假设文件f1和f2在某个列x中分别包含值3..5和4..6。然后,如果查询有一个谓词,其中x>=6,我们就知道只需要访问f2。与传统索引不同,这种元数据通常比实际数据小几个数量级,因此存储开销小,访问速度快。

剪枝优化很好地符合Snowflake的设计原则:它不依赖于用户输入;它可以很好地扩展;并且易于维护。更重要的是,它可以很好地对大数据块进行顺序访问,并且在加载、查询优化和查询执行时间方面增加的开销很小。

Snowflake不断地为每个单独的表文件做相关元数据的剪枝优化。元数据不仅包括普通的关系列,还包括半结构化数据中自动检测的列的选择,参见第4.3.2节。在优化过程中,根据查询谓词检查元数据,以减少(“删减”)查询执行的输入文件集。优化器不仅对简单的基值谓词执行修剪,还对更复杂的表达式(如WEEKDAY(orderdate) IN (6, 7))执行剪枝优化。

除了静态剪枝,Snowflake还在执行过程中执行动态剪枝。例如,作为hash join处理的一部分,Snowflake收集有关build-side记录中join key分布的统计信息。然后将此信息推送到probe-side,并用于过滤probe side的整个文件,甚至可能跳过这些文件。除此之外,还使用其他技术,如bloom joins。

来源 https://zhuanlan.zhihu.com/p/366369705

相关文章