[Snowflake核心技术解读系列二]云原生技术

2022-03-10 00:00:00 数据 文件 节点 计算 仓库

背景:2020年9月16日,Snowflake成功IPO,交易首日市场估值达到704亿美元,募集资金34亿美元。Snowflake成为迄今为止规模大的软件IPO,市值高突破1200亿美元。Snowflake提供基于云的数据存储和分析服务,一般被称为 "数据仓库即服务",它允许企业用户使用基于云的硬件和软件来存储和分析数据。Snowflake自2014年起在亚马逊S3上运行,自2018年起在微软Azure上运行,自2019年起在谷歌云平台上运行,其Snowflake Data Exchange允许客户发现、交换和安全地共享数据。[维基百科]

Snowflake取得了巨大的商业成功,技术是如何支撑起它的千亿美元市值呢?它技术强在哪?OLAP内核技术爱好者浙川为大家倾情解读Snowflake的核心技术原理。本文为该系列二。

Snowflake中的云原生技术

本章节将围绕数据存储、虚拟仓库和云服务组件,来详细展开介绍Snowflake设计中的技术细节,分析他们是如何充分利用底层云服务的架构和资源的。

数据存储

Snowflake的数据存储组件是构建在Amazone S3存储服务之上的,之所以采用这样的设计选择,是因为Amazon S3存储服务提供了高易用性、高服务可靠性,以及严格的数据持久性保证。S3的这些特性大大降低了Snowflake进行数据存储开发的成本,Snowflake的数据存储设计只需要考虑本地缓存、数据倾斜修复等特性即可。

然而,和直接使用本地存储比起来,S3却存在一些限制。S3具有更高的数据访问延迟,同时每个S3的I/O请求需要更高的CPU开销,尤其是使用HTTPS连接的情况下。另外,S3是对象存储,其数据存储操作接口就是PUT/GET/DELETE等简单接口,数据对象只能作为整体被写入,不支持带有偏移量的写入(但支持带有偏移量的读取),也不支持数据追加写(append)。

S3的这些特点影响着Snowflake在数据表文件格式上的设计选择。Snowflake将其数据库横向分区为多个大文件,每个文件就类似于传统数据库存储系统中的数据块或者数据页概念,文件一旦生成,就不再改变。每个文件中,表数据以列存格式进行存储,每一列数据在物理上存放到一起,然后再进行压缩。每个文件都对应一个文件头,文件头包括基本存储元数据信息,例如每一列数据在文件中的偏移。由于S3是支持带有偏移量的数据读取的,因此,只需要将文件头数据拿到,就可以根据需要访问某一列的数据了。

Snowflake还会利用S3存储复杂查询(例如大规模join)计算过程中产生的临时数据,这样可以支持系统运行任意复杂类型的查询,而不会导致系统内存或本地存储被耗尽。Snowflake还会将查询结果存储到S3中,以简化用户执行交互式查询的系统复杂度,因为不需要再像传统数据库那样针对交互式查询维护一个cursor了。

数据库级别的元数据信息如表的元数据、表对应的S3文件、统计信息、事务信息等,保存在云服务相关组件中的分布式key-value存储中。

虚拟仓库

虚拟仓库实质上是由EC2实例组成的集群,组成虚拟仓库的每个独立的EC2实例又称为计算节点。Snowflake的用户不会直接和计算节点进行交互,他们甚至不需要关注由多少计算节点组成了他们的虚拟仓库。用户只需要选择虚拟仓库的配置,而这些配置就像T恤衫的尺码一样,被抽象成X-Small到XX-large的范围供用户选择。

弹性和隔离性。作为纯粹、弹性的计算资源,虚拟仓库可以在任意时间进行按需地创建、销毁或者重新配置。创建或者销毁虚拟仓库不会影响数据仓库中存储的用户数据,这样设计的好处就是用户可以根据他们的计算需求动态地申请计算资源,用户甚至可以在没有任何计算需求的情况下销毁所有的数据仓库,而不用关心他们数据存储量的大小。

用户的每个查询请求会被分配到某个虚拟仓库上进行执行。由于不同的虚拟仓库并不会共享同一个计算节点,因此,不同虚拟仓库上运行的查询之间互相不会有资源竞争和性能影响,即性能隔离性得到了很好的满足。当然,虚拟仓库不共享计算节点也有它的弊端,比如整个系统的资源利用率会很低,Snowflake后续会专门针对这一点做优化和提升。

虚拟仓库在执行查询请求的时候,每个计算节点上会对应生成一个计算进程,计算进程的生命周期就是这个查询请求的生命周期。当计算进程遇到了错误,可以简单通过重试来解决。因此,Snowflake在查询层面具有较高的容错性,但是Snowflake目前只执行整个查询的重试,并不支持重试查询下的某个子任务。需要强调的是,查询开始时创建计算进程、结束后销毁计算进程这种模式适合执行时间比较长的分析型查询,但对于执行时间很短的短查询,创建、销毁计算进程的开销就太大了。为了对短查询进行优化,Snowflake在一个由若干个计算节点组成的集合中维护特定的计算进程,这些进程专门用来执行短查询,短查询执行结束后,这些进程并不会被销毁,而是重复利用执行后续的短查询。

在Snowflake中,用户可以申请多个虚拟仓库,每个虚拟仓库上可以并发运行多个查询。每个虚拟仓库都共享相同的表数据,他们不需要为了运行计算任务而单独地拷贝数据。这样的设计方案有效地利用了弹性和隔离性的优点:Snowflake的用户可以任意按需扩展虚拟仓库的数量和配置,但是只需要存储和操作一份数据;不同负载的查询任务可以运行在不同的虚拟仓库上,虚拟仓库之间的计算资源是互相独立的,这样可以保证不同负载的查询之间互不影响性能。

根据Snowflake统计的实践经验,虚拟仓库的弹性还可以为用户带来性价比更高的服务:在几乎同样的价格下,用户可以享受更快的性能。例如,如果某个工作负载在4个计算节点上执行需要花费15个小时,那么在32个计算节点上执行可能只需要花费2个小时。虽然这两种模式的价格是差不多的,但是带给用户的体验却有着根本的区别:在同样花费的情况下,性能越快用户感受就会越好。而虚拟仓库的弹性恰恰为用户提供了体验的选择,用户可以配置虚拟仓库,以更快地完成计算任务,但是并不需要额外多的花费开销。

缓存和文件转移。虚拟仓库中,每个计算节点会在本地对访问过的表数据进行缓存,缓存对象即为数据表在S3上对应的文件。本地一定会进行缓存的数据是文件头(详细内容参见5.3.1章节),根据文件头的信息可以下载任意指定要访问的数据计算节点上的缓存数据是可以被该节点上的查询任务共享。当缓存容量达到上限时,Snowflake采取简单的LRU(Least-Recently-Used)算法来进行缓存替换。

为了提高缓存命中率,同时为了避免在同一个虚拟仓库中不同的计算节点上缓存相同的表数据文件,Snowflake将不同的表文件分布到不同的计算节点上,分布算法采用的是针对表名的一致性hash算法。需要操作同一个表文件的查询请求都会访问同一个计算节点。同时,Snowflake采用的是惰性一致性hash算法:即当计算节点的数目发生改变的时候,不会立即对计算节点上的文件进行重新分布,而是当LRU算法需要对缓存的文件进行替换的时候,再将新的文件根据新计算节点的数目进行一致性hash分布。这样设计的好处就是不会因为立即的数据重新分布而影响正在执行的查询任务,相比于会立即进行大量数据重新分布的shared-nothing架构,具有更高的可用性和用户体验。

在实际运行中,Snowflake还会遇到倾斜(skew)问题:一些计算节点可能由于虚拟化、网络竞争、负载过高等问题,其响应速度远远低于其他计算节点,这就会导致这些计算节点可能会拖慢需要访问它们缓存数据的其他查询的性能。为了解决这样的倾斜问题,Snowflake在文件扫描阶段做了额外的处理逻辑。当某个计算节点扫描完输入文件而需要从其他计算节点读取数据文件时,它会向目标节点发送文件读取请求。如果目标节点目前负载很高,它不会直接返回文件数据,而是回复将该文件的使用权转移给请求节点。发生文件转移后,请求节点在当前查询的生命周期内,可以直接从S3上下载数据文件,而不需要再从目标节点那里访问数据文件。这样就避免了在慢节点上再增加额外的负载,以减轻系统的倾斜情况。

执行引擎。保证系统的可扩展性十分重要,提升系统单节点的计算性能同样十分重要:同样的执行时间下,能用10台计算节点就肯定不用1000台计算节点。Snowflake针对其系统的执行引擎也做了大量优化,以提升其单计算节点上的计算性能,进而为用户提供更好性价比的服务。Snowflake的计算引擎采用了三大技术:列存、向量化、结果下推。

对于分析型场景来说,列式存储的收益往往优于行式存储的效率,因为在只访问较少列的情况下,列式存储的CPU利用率和I/O利用率会更高。同时,列式存储可以更方便地应用SIMD指令集,也具有更高的数据压缩率。

Snowflake采用的向量化执行方案源自于MonetDB/X100方案。Snowflake避免了中间结果的物化,而是以流水线的形式批量地处理数据(一次处理几千行对应的列数据),这种方式不仅大大降低了I/O的开销,而且有效地提升了cache的使用效率。

Snowflake的结果下推采用的是经典的火山模型:在Snowflake中,关系型算子可以将它们的结果直接下推给下游的算子,而不是等着下游的算子来这里拉取结果。这种下推方式可以减少不必要的控制流逻辑,提升cache的使用效率。结果下推还让Snowflake有能力处理DAG(Directed Acyclic Graph)执行计划,DAG执行计划可以对中间结果数据进行更高效的共享和流水线化。

很多传统数据库执行引擎所不得不面临的设计开销对Snowflake的执行引擎而言可能并不需要关注。比较典型的就是Snowflake在执行查询请求的时候,并需要考虑事务管理相关的问题。如5.3.1章节所述,Snowflake的文件一旦写入到S3中,就不会再改变了,因此,Snowflake的查询执行是建立在不可变文件上的,所以事务管理是不需要的。同时,Snowflake也没有维护内存缓冲区。在Snowflake中,大部分查询操作的数据量都非常的大,如果采用内存缓冲区来缓存这么大量的数据,虽然会提升查询性能,但是代价也是非常大的:会消耗大量宝贵且昂贵的内存。相反,Snowflake会将复杂查询(包括join、group by、sort等)的中间结果保存到本地磁盘上,并递归地对磁盘数据进行操作。磁盘的容量远远高于内存,价格也相对便宜。基于这样的设计,Snowflake几乎可以处理任意负载类型的查询任务,包括极其复杂的大规模join或者agg计算。

注:译文来自 https://www.snowflake.com/resource/sigmod-2016-paper-snowflake-elastic->

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

相关文章