本文是对于Snowflake论文的一个完整版解读,对于从事大数据数据仓库开发,数据湖开发的读者来说,这是一篇必须要详细了解和阅读的内容,通过全文你会发现整个数据湖设计的起初原因以及从各个维度(架构设计、存算分离、弹性伸缩、查询优化、故障恢复、性能优化等等)展开而来的设计思路。
可能内容本身站在当下并不显得新颖和具备创意,但是从2012年那个时候,云计算和大数据也才刚刚普及不久,在以结构化数据分析为主的行业里,出现了以非结构化和非模式数据的概念,并且是基于云计算为底座来做的。不得不说很有一定的挑战性。
下面内容是我对于各个不同段落加了一些自己的认知和解读,也是基于大数据经验和云计算从业者的一些认知进行了补充和完善。希望对于每个读者都能有不同程度的收获。
我们生活在分布式计算的黄金时代。公有云平台现在可以根据需要提供几乎无限的计算和存储资源。与此同时,软件即服务 (SaaS) 模型为那些由于成本和复杂性而无法负担此类系统的用户带来了企业级系统。遗憾的是,传统的数据仓库正在努力适应这种新环境。一方面,它们是为固定资源设计的,因此无法利用云的弹性。另一方面,它们对复杂的提取、转换和加载管道以及物理调优的依赖与云的新类型半结构化数据和快速发展的工作负载的灵活性和新鲜度要求相矛盾。
我们决定进行一次彻底的重新设计,我们的任务是为云计算构建一个企业级的数据仓库解决方案。 结果便是“ snowflake 虚拟数据仓库”, 简称 snowflake。snowflake 是一个多租户、 事务性的、 安全的、 高可扩展性和弹性的系统, 全面支持 SQL 和半结构化和非结构化数据的内建扩展。 这个系统以亚马逊云服务的形式提供, 按使用付费。 用户可以将他们的数据上传到云端,并且能够利用常见的 工具和接口立刻管理及查询数据。 项目于 2012 年底启动, snowflake 从 2015 年 6 月开始成为通用可用版本。 如今, snowflake 被越来越多的大小组织用于生产环境中。 这个系统每天处理数百万条查询并管理着多个 PB 的数据。
解读:
开头就重点输出了两点,并且整个论文开篇重点和SaaS化的数据仓库技术也是基于这两点问题展开的。
-
在当下云计算的时代,大数据还是以固有的资源体系架构进行设计,无法满足灵活的弹性伸缩能力。
-
在整个计算处理过程中,对于半结构化和非结构化数据的处理不够友好。
所以,snowflake的目标是解决这种在云资源之上实现极致弹性伸缩,能够全面支持SQL处理的半结构化和非结构化数据的能力,从snowflake的设计概念上体现为:是一个多租户、 事务性的、 安全的、 高可扩展性和弹性的系统
在本文中,我们描述了 Snowflake 的设计以及其新颖的多集群、共享数据架构。文章强调了 Snowflake 的一些关键特性:极端弹性和可用性、半结构化且无模式的数据、时间旅行和端到端安全。最后以经验教训和正在进行的工作展望作为结尾。
一、简介
云计算的出现标志着从在本地服务器上交付和执行软件,转向共享数据中心和基于云的软件即服务 (SaaS) 解决方案。 云共享基础架构承诺实现规模经济、极端可扩展性和可用性,并采用按使用量付费的成本模型来适应不可预测的使用需求。 但是,只有当软件本身能够弹性地扩展到云资源池时,才能利用这些优势。 传统数据仓库解决方案早于云计算。 它们旨在运行在小型静态群集上,这使它们成为较差的体系结构选择。
解读:
从大数据技术和云计算技术的诞生来说,都是解决的当下那个时代所面临的问题,大数据是在2008年前后发展起来,解决的是互联网时代数据量爆发引申出的大数据量数据分析和处理的问题,云计算的本质还是在于硬件发展速度受限,摩尔定律失效如何提升整个资源利用率的问题。在大数据诞生的时期,计算机硬件的发展还是非常迅速的。
但不仅平台变了。数据也变了。过去,数据仓库中的大部分数据都来自组织内部:事务系统、企业资源计划(ERP)应用、客户关系管理(CRM)应用等。结构、容量和速度都是可预测且已知的。但是云计算使得相当大且快速增长的数据量来自于不太可控或外部来源:应用程序日志、网络应用、移动设备、社交媒体、传感器数据(物联网)。除了不断增长的数量之外,这些数据经常以无模式的、半结构化格式出现。传统数据仓库解决方案正在努力解决这种新数据。这些解决方案依赖于深入的ETL管道和物理调优,从根本上假定来自主要内部来源的可预测、缓慢变化且易于分类的数据。
针对这些不足,数据仓库社区的一部分转向了“大数据”平台,如Hadoop或Spark。 虽然这些对于数据中心规模的数据处理任务来说是不可或缺的工具,而且开源社区仍在不断取得重大进展,例如Stinger倡议, 但它们仍然缺乏成熟的数据库技术所具有的许多效率和功能集。但是最重要的是,它们需要大量的工程努力才能推出和使用。
我们相信,有一大批用例和工作负载可以从云计算的经济性、弹性和服务方面受益,但这些用例和工作负载并不适合传统的数据仓库技术。
解读:
这一段讲了大数据的产生的背景,从过去单体架构服务所产生的事务型数据,到后面大数据量的产生而出现的大数据相关技术比如Hadoop和Spark,主要是通过分布式架构的技术来做这种多节点资源的分配和调度,但是从弹性和应用上来讲不是当时的核心问题。
通过大数据平台。所以我们决定为云环境构建一个全新的数据仓库系统。该系统被称为 Snowflake弹性数据仓库,或“Snowflake”。与许多其他基于云计算的数据管理系统的不同之处在于,Snowflake 并不是建立在Hadoop、PostgreSQL 或其他类似技术之上。其处理引擎和其他大部分组件都是从头开始开发的。Snowflake 的关键特性如下:
-
纯软件即服务(SaaS)体验 用户无需购买机器、聘请数据库管理员或安装软件。用户的数据要么已经在云端,要么他们上传(或邮寄[14])。然后,他们可以立即使用Snowflake的图形界面或标准化接口(如ODBC)来操作和查询数据。与其他基于云的关系型数据库服务(DBaas)不同,Snowflake的服务范围涵盖整个用户体验。用户没有调整旋钮,没有物理设计,也没有存储整理任务。
-
关系型 Snowflake 具有对 ANSI SQL 和 ACID 事务的全面支持。大多数用户能够通过很少或没有更改来迁移现有的工作负载。
-
半结构化Snowflake提供内置函数和SQL扩展,用于遍历、扁平化和嵌套半结构化数据,并支持流行的格式,如JSON和Avro。自动元数据发现和列存储使对无模式、半结构化数据的操作几乎与普通关系型数据一样快,无需用户付出任何努力。
-
存算分离、平滑地进行扩展,不会影响数据可用性或并发查询性能。
-
高可用,Snowflake可以容忍节点、集群甚至整个数据中心的故障。在软件或硬件升级期间不会发生停机。
-
持久化,为防止意外数据丢失而设计的,具有额外的安全保护措施:克隆、撤销删除以及跨区域备份。
-
低成本 Snowflake 非常高效且计算资源丰富,所有表数据都经过压缩。 用户只需为其实际使用的存储和计算资源付费。
-
数据可靠性,包括临时文件和网络流量都得到了端到端加密。没有用户数据会被暴露给云平台。此外,基于角色的访问控制使用户能够在 SQL 层级上对访问进行细粒度的控制。
解读:
这一段可以说是snowflake整个设计思想的核心点,这里从云数据仓库提供服务的模式、对于标准SQL兼容性、存算分离架构、基于云所带来的容错能力(跨可用区)、数据安全措施、成本效益这几个方面考虑设计,其实从技术角度来看都没有问题,唯一的在于成本效益这块,在这个段落里是说表被压缩,并且存储和计算资源付费,但是在后面的内容中,整个成本计算方式是按照处理性能的登记来划分的。
Snowflake 目前运行在亚马逊云(Amazon Web Services,AWS)上,但未来我们可能会将其移植到其他云计算平台上。截至本文撰写时,Snowflake 每天处理数百万条查询,并为来自不同领域的众多大型和小型组织提供服务。
本文的结构如下。
第 2 节解释了 Snowflake 的关键设计选择:存储与计算分离。
第 3 节介绍了由此产生的多集群共享数据架构。
第 4 节突出了不同的特点:连续可用性、半结构化和无模式数据、时间旅行和克隆,以及端到端的安全性。
第 5 节讨论了相关工作。
第 6 节总结了本文的工作成果并展望了正在进行中的研究。
二、存储与计算
Shared-nothing 架构已经成为高性能数据仓库系统的主要体系结构,主要原因有两个:可扩展性和商用硬件。在Shared-nothing 架构中,每个查询处理节点都有自己的本地磁盘。表是跨节点水平分区的,每个节点只负责本地磁盘上的数据行。这种设计非常适合星型模式查询,因为只需要很少的带宽就可以将小(广播)维度表与大(分区)事实表连接起来。由于对共享数据结构或硬件资源的竞争很小,因此不需要昂贵的定制硬件
在Shared-nothing 架构中,每个节点都有相同的职责,并且运行在相同的硬件上。这种方法导致了优雅的软件,易于理解。然而,Shared-nothing架构有一个重要的缺点:它紧密地耦合计算资源和存储资源,在某些情况下会导致问题。
解读:
Shared-nothing架构是一种分布式系统中常用的架构模式,它将每个节点上的资源都视为独立的,不会共享任何资源。这种架构的优点是可以提高系统的可扩展性和容错性,但是也会增加通信成本和数据复制的工作量。
Hadoop等大数据相关服务通常是基于Shared-nothing架构设计的。这种架构中,每个节点都有自己的本地存储器和计算资源,不同的节点之间不会共享数据和硬件资源。这样可以提高系统的可扩展性和容错性,并且能够更好地适应大规模分布式环境下的数据处理需求。
而Shared-everything架构则是将所有节点上的资源都共享在一起,包括硬件资源和软件资源。这种架构的优点是可以减少通信成本和数据复制的工作量,但是会降低系统的可扩展性和容错性。
异构工作负载 硬件配置是一致的,但工作负载通常不是。 一个对批量加载(高I/O带宽、轻计算)最理想的系统配置 对于复杂的查询(低IO带宽、重计算)来说却并不适用,反之亦然。 因此,硬件配置需要以较低的平均利用率作为代价。
成员变更 如果节点集合发生变化,无论是由于节点故障还是用户选择调整系统大小,都需要重新洗牌大量数据。 由于相同的节点负责数据洗牌和查询处理,因此可以观察到显着的性能影响,限制了可扩展性和可用性。
在线升级 尽管使用复制可以降低小规模成员更改的影响,但软件和硬件升级最终会影响系统中的每个节点。在理论上,一个接一个地升级节点而不让系统中断是可能的,但由于所有东西都紧密结合且期望一致,这变得非常困难。
在本地环境,这些问题通常是可以容忍的。工作量可能异构,但当只有一个很小的、固定的节点池可以运行时,人们几乎无能为力。很少对节点进行升级,也很少发生节点故障或系统调整大小的情况。
云计算的情况则完全不同。像亚马逊EC2这样的平台有很多不同类型的节点[4]。利用它们只需要把数据带到正确的节点类型即可。与此同时,节点故障更加频繁,性能差异也会很大,即使是在相同类型的节点之间也是如此[45]。因此,成员资格的变化并不是例外,而是常态。最后,实现在线升级和弹性扩展有很强的动力。在线升级可以大大缩短软件开发周期,提高可用性。 弹性伸缩进一步提高了可用性,并允许用户匹配其当前需求与资源消耗。
出于这些原因和其他原因,Snowflake 将存储与计算分离。这两个方面由两个松散耦合、独立可扩展的服务处理。计算通过 Snowflake 的(专有)共享无关引擎提供。存储通过 Amazon S3 提供,但原则上任何类型的 blob 存储库都足够了。为了减少计算节点和存储节点之间的网络流量,每个计算节点都在本地磁盘上缓存一些表数据。
这种解决方案的另一个好处是,本地磁盘空间没有用于复制整个基础数据集,这可能非常大并且大多是冷(很少访问)。相反,本地磁盘专门用于临时数据和缓存,两者都是热的(建议使用SSD等高性能存储设备)。因此,一旦缓存变暖,性能就会接近甚至超过纯共享无关系统。我们称这种新颖的体系结构为多集群、共享数据架构。
解读:
在大数据技术中,存储和计算是耦合在一起的,并且在节点扩容时要求硬件配置相同(管控平台可以设置节点组),那这时候就会造成要么存储资源浪费、要么计算资源浪费,使得服务器资源使用不均衡。其次,在数据节点缩容的时候,数据会进行副本之间的同步和迁移,对于整个系统稳定性来说是有一定风险的。
对于云上集群来说,当节点故障的时候,大数据场景下也会出发数据迁移和恢复,无论是节点故障、磁盘故障还是网络故障,所以,对于大数据场景存算一体下的极致的弹性伸缩能力,是基本无法做到的。
所以,Snowflake通过将存储和计算分离这种松耦合的架构,来实现更高效率的弹性和资源利用问题。
三、架构设计
Snowflake 是为大型企业而设计的服务。除了提供高可用性和互操作性外,企业就绪意味着高可用性。为此,Snowflake 是一个由高度容错且可独立扩展的服务组成的面向服务的架构。这些服务通过 RESTful 接口进行通信,并分为三个架构层:
解读:
Snowflake 是为大型企业而设计的服务,从定位上就是为大企业而服务,小企业或者小数据量的公司可能都不足以开通资源的费用呢。
数据存储层 使用 Amazon S3 存储表数据和查询结果。
虚拟仓库 系统的“肌肉”。这一层负责在被称为虚拟仓库的弹性虚拟机集群中执行查询。
云服务 系统的大脑。这一层是一组服务,用于管理虚拟仓库、查询、交易以及与此相关的所有元数据:数据库模式、访问控制信息、加密密钥、使用统计数据等。
解读:
从整个架构上分为三层架构,分别为数据存储、数据仓库、云服务,存储层使用对象存储S3来存储数据,在数据仓库层主要做查询解析、资源管控、集群管理等等,云服务主要用来做一些元数据相关的维护。
图 1 描述了snowflake架构的三个分层以及它们的主要组件。
3.1数据存储
选择 Amazon Web Services (AWS) 作为 Snowflake 的初始平台,原因有二。首先,AWS 是云平台市场上最成熟的产品。其次(也是与第一点相关),AWS 提供了最大的潜在用户群。
接下来的选择是在使用S3还是基于HDFS或类似的东西开发自己的存储服务之间进行选择。 我们花了一些时间来尝试S3,发现尽管它的性能可能会有所不同,但其可用性、高可用性和强大的持久性保证很难被打破。因此,我们决定不再开发自己的存储服务,而是将精力投入到虚拟仓库层的本地缓存和倾斜恢复技术中。
解读:
从当下时期来说,貌似使用对象存储来做这种存算分离架构的技术基本成熟,但是回望2012年那时候基于HDFS分布式文件存储技术是如日中天,对象存储想对还没有那么领先,而且从性能、操作上来说不如文件系统,所以说,这个选择应该也是经历多了长时间的讨论和抉择的,毕竟基于对象存储的选型来说,对于K/V这种存储类型,是不适用于大数据场景的,事实也证明在大数据计算场景下的LIST和RENAME是有极大的瓶颈问题的。所以说,如何做和计算和存储之间的数据存储层是整个存算分离技术的核心关键点之一。
与本地存储相比,S3 的访问延迟自然要高得多,每个 I/O 请求都会产生更高的 CPU 负担,尤其是如果使用了 HTTPS 连接。但更重要的是,S3 是一个 blob 存储库,一个相对简单的基于HTTP(S) 的PUT/GET/DELETE 接口。对象即文件,只能被完全(重新)写入。甚至不能向文件末尾追加数据。实际上,在PUT 请求中必须提前声明文件的确切大小。但是,S3 支持针对文件的部分(ranger)的GET 请求。
这些特性对 Snowflake 的表文件格式和并发控制方案产生了很大影响(参见第 3.3.2 节)。表被水平拆分为大而不可变的文件,相当于传统数据库系统中的块或页。在每个文件中,每个属性或列的值都被分组并进行高度压缩,这是文献[2]中称为 Pax 或混合列存储的一种众所周知的方法。每个表文件都有一个标题,除了其他元数据外,它还包含文件中每列的偏移量。由于 S3 允许对文件的部分进行 GET 请求,查询只需要下载文件头以及它们需要的那些列。
Snowflake 不仅使用 S3 存储表数据,还使用 S3 存储查询运算符(例如大规模连接)生成的临时数据。当本地磁盘空间不足时,它会将临时数据写入 S3 以存储大量查询结果。 将临时数据写入 S3 可让系统计算任意大小的查询而不会出现内存或磁盘不足错误。 将查询结果存储在 S3 中可以启用新的客户端交互形式并简化查询处理,因为它消除了传统数据库系统中需要的服务端游标。
元数据,如目录项、包含哪些S3文件的表、统计信息、锁、事务日志等都存储在可扩展的事务性键值存储中,它是云服务层的一部分。
3.2虚拟仓库
虚拟仓库层由 EC2 实例组成。每个这样的集群都通过一个称为虚拟仓库 (VW) 的抽象呈现给单个用户。构成 VW 的单个 EC2 实例称为工作节点。 用户永远不会直接与工作节点交互。实际上,用户不知道也不关心哪些工作节点构成了 VW 或者有多少个工作节点。取而代之的是,VW 是以抽象的“T 恤尺码”形式提供的,从 X 小到 XX 大不等。这种抽象使我们能够独立于底层云平台来发展服务和定价。
解读:
虚拟数据仓库可以理解为是一种逻辑上的数据仓库集合,在当年2012年的时候,容器技术还没有发展起来,所以当时设计还是以虚拟机节点来做为计算层,这里还有一个关键的两点,其实在上面也简单提到了就是成本效益问题。
-
使用者(用户)是不知道底层到底有多少个VM节点的,用户只是感知到数据库连接地址,然后进行读写数据即可。
-
关于计费计量方面,一般Pass平台是按照CPU/Memory,有的按照CU来计量单位,1CU大概表示1C4G的空间,但是在这里是通过更加抽象的逻辑单位来表示的,比如X、XL、XXL、XXXL等等,不同的XX能够处理的数据量、计算量也不同,通常来说是逐步扩大的。
最后一句话,也提到了,这种定价模式是方便于云数据仓库这种SaaS产品独立于云平台来发展和定价。一般云厂商定价的规则是包年包月、按量付费的逻辑,计量的单位是以节点规格来收费的,另外加上一些服务费用,相对更加透明一些。
但是Snowflake的收费模式,就没有那么透明,很难解释清楚定价逻辑和规则,所以从成本效益上可能会有减少,也可能会有增多,这要具体看使用者的业务处理方式。
3.2.1弹性和隔离
虚拟机(VM)是一种纯粹的计算资源。它们可以根据需要在任何时间创建、销毁或调整大小。创建或销毁 VM 不会影响数据库的状态。用户在没有查询时关闭所有 VM 是完全合法的(并且受到鼓励)。这种灵活性使用户能够根据使用需求动态匹配计算资源,而无需考虑数据量。
解读:
这段其实是很有料的,不知道读者朋友是否对于云计算有所了解,一般云上主机(虚拟机)是有弹性策略的,但是对于数据仓库、大数据这种场景,主机资源通常是7*24小时开机状态,因为从付费模式上就决定了关机或者不关机没有成本上的缩减。
所以,除非是走退订的逻辑,将主机进行释放,当需要的时候在重新购买然后开机部署使用,这种模式需要使用者具备一定的技术能力和门槛,国内云厂商(注意是国内)通常并没有针对大数据场景做这种标准化的处理模式。而且这种模式对与IASS的弹出性能有一定的要求,不过幸运的一点在于,当下基于Kubernetes的容器化技术的弹性伸缩性能要更快一些,如果使用一些高性能的容器管控服务的话,秒级别可以弹出几十甚至几百个Pod都是可能的,当然这里要依赖很多容器化内核的技术,比如镜像预拉取(缓存)、调度服务、容器运行时、配置管理等等。
每个单独的查询只运行在一个VW上。工作节点不会在多个VW之间共享,从而为查询提供了强大的性能隔离。(话虽如此,我们认为工作节点共享是未来工作的一个重要领域,因为它将为性能隔离不是大问题的用例提供更高的利用率和更低的成本。)
解读:
我理解这里的隔离技术指的就是当下Kubernetes的worker和Pod之间的关系,显然,当下的容器化技术就是提升了资源利用率,并且,提供了强大的资源隔离策略,成本也相比虚拟机有了一定程度的降低。
注意:这篇论文是2012年提出的,容器化技术大概是在2014年才开始。
当提交一个新查询时,每个 VW 工作节点(如果优化器检测到小查询,则为子集)都会产生一个新的工作进程。 每个工作进程只存在一个查询的时间长度内。 即使作为更新语句的一部分,单独的工作进程也永远不会对外部可见的影响,由于表文件是不可变的,参见第3.3.2节。因此,工作节点的故障很容易被控制,并通过重试来解决。Snowflake目前不支持部分重试,因此非常大的、长时间运行的查询是一个需要关注和未来工作的重点。
解读:
分布式计算引擎大多是MPP(并行计算架构),对于单个Task的失败、重试会直接影响整个作业的最终结果成功失败、运行效率的核心因素,先从两个方面来说吧。
-
不可变文件天然优势,如果使用Scala写Spark作业的话,会发现里面很多算子和函数都是不可变的,不可变的好处就在于不用担心数据的变化,而重新刷新所有已经计算完成的数据集,所以重试、重跑的效率更高一些。
-
大任务大数据量很容易出现数据热点问题,这是所有MPP架构都会遇到的问题,例如Shuffle数据、数据倾斜都会导致这种问题的出现,所以各种计算引擎都在做数据倾斜方面的优化,但是倾斜并不是一个技术上能够完全避免的问题,从使用角度(业务)上来说也需要一定的设计和优化。
每个用户可以同时运行多个虚拟机,而每个虚拟机又可以并发执行多个查询。每台虚拟机都可以访问相同的共享表,无需实际复制数据。
共享、无限存储意味着用户可以分享和整合他们所有的数据,这是数据仓库的核心原则之一。同时,用户还可以从私有的计算资源中受益,避免不同工作负载和组织单位之间的干扰——这也是数据集市的原因之一。这种弹性和隔离使得一些新的用例策略成为可能。Snowflake用户通常会为来自不同组织单位的查询拥有多个虚拟仓库(VW),这些查询通常会持续运行,并会定期启动按需VW,例如用于批量加载。
解读:
先说从模式上来讲,上面提到对于使用者来说就是购买了XXL的资源,对于存储来说是基于对象存储来存储的,对象存储的好处就是不需要关心实际存储用量大小,可以说一种无限大的存储资源池,想要写多少数据都可以,并且支持不同等级的冷热分层策略。
其次,是说计算资源隔离问题,我理解当时的隔离技术可能并不是很成熟,更多的可能是未来的一种对于计算资源隔离的“设想”,但是从目前的实际来看,这种是能够实现的, 资源隔离首先要有强大的资源调度引擎,可以进行内核级别的资源管控,同时对于碎片化的资源(例如:内存)能够充分的利用上。
与弹性相关的另一个重要观察是,通常可以以大致相同的价格实现更好的性能。例如,在一个具有四个节点的系统上需要 15 小时的数据加载任务可能只需要在拥有32个节点的系统上花费两小时 , 由于每小时计算成本,总体成本非常相似——然而用户体验却大不相同。因此我们相信 VW 的伸缩性是 Snow-Flake 架构的最大优势之一,并且它表明需要一种新的设计来利用云的独特功能。
解读:
这其实就是多角度看待问题的一种方式,是选择多节点快速跑完还是选择少节点多时间来跑完,并没有好坏之分,这里突出的重点在于Snowflake的极致弹性伸缩能力,就是说你可以选择快速把资源打满,然后快速跑完任务。这是产品本身能够达到的效果,换句话说给了用户更多选择的空间,体验度上更好一些。毕竟一些紧急的作业是非常需要这种特性支持的。
3.2.2本地缓存与文件窃取
解读:
这一节重点解决的应该就是数据存储处理问题,上面有提到对象存储是有大数据量计算瓶颈问题的, 所以在大数据场景下,必然会有很强大的缓存能力才能够做到既高效又经济的作业执行。
每个工作节点都维护本地磁盘上的表数据缓存。缓存是一组表文件,即过去由该节点访问过的 S3 对象。具体来说,缓存包含文件头和文件中的每一列,因为查询只会下载所需的列。
解读:
对象存储的优化方案就是尽量减少大批量的访问,这样对于访问过的信息(元数据)可以记录在本地缓存中,这样当下次再进行访问时,就可以通过本地缓存的元数据信息(比如Key的位置),直接到GET请求到对应的KEY信息。就不用单独的LIST的方式挨个扫描一遍了,这是对于S3等对象的其中一种处理方式。
当下来说,很多开源的系统也支持直接链接对象存储元数据缓存和数据缓存,例如:JuiceFS、Alluxio都可以支持挂载/链接对象存储来实现高效的数据读写。
缓存在工作节点期间存在,并在并发和后续工作进程(即查询)之间共享。它只看到一个文件和列请求流,并遵循一个简单的最近最少使用(least-recently-used, LRU)替换策略,忽略单个查询。这个简单的方案工作得非常好,但我们可能会在将来对其进行改进,以更好地匹配不同的工作负载。
为了提高命中率并避免在VW的工作节点上对单个表文件进行冗余缓存,查询优化器使用表文件名上的一致散列将输入文件集分配给工作节点。因此,访问同一表文件的后续或并发查询将在同一工作节点上执行此操作。
解读:
在大多数的存算分离架构下的系统来说,基本上都会有一致性Hash算法的应用(例如:Apache Pulsar的Bundle概念),这种算法本质上来说就是提供了存算分离场景下的任务均衡调度能力,能够充分的将任务进行打散,并且根据节点数量的大小可以平滑的进行Hash重调度。
一致散列算法是一种用于分布式系统中的负载均衡算法,数据集映射到一个环上,每个节点被分配到环上的某个位置,然后根据数据集中的元素所对应的环上的位置来确定哪个节点负责处理该元素。这种算法的优点是可以实现平滑的扩容和缩容,因为只需要添加或删除少量节点即可重新平衡负载,而不需要重新分配整个数据集。此外,一致散列还可以减少网络流量和数据复制的数量,提高系统的可靠性和可用性。
Snowflake中的一致性哈希是惰性的。当工作节点集合发生变化时(由于节点故障或VW调整),不会立即进行数据洗牌。相反,snowflake依赖LRU缓存淘汰策略(最近最少使用)策略来最终重新放置缓存内容。这种解决方案将替换缓存内容的成本分摊到多个查询中,从而比急切的缓存或纯shared-nothing系统具有更好的可用性,后者需要立即在节点之间shuffle大量表数据。它还简化了系统,因为没有“降级”模式。
解读:
通常来说缓存不是无限大的,能够提供缓存的大小是有限的,所以基于本地缓存的策略一般是通过时间周期(TTL)或者LRU算法(最近最少使用)来实现。
除了缓存之外,数据倾斜在云数据仓库中尤为重要。由于虚拟化问题或网络争用,某些节点的执行速度可能比其他节点慢得多。在其他地方,Snowflake在扫描级别处理这个问题。每当一个工作进程扫描完它的输入文件集,它就会从它的对等进程请求额外的文件,我们称之为文件窃取技术。如果对等节点发现当这样的请求到达时,它的输入文件集中还有许多文件,它通过在当前查询的持续时间和范围内转移剩余一个文件的所有权来应答请求。然后请求者直接从S3下载文件,而不是从对等节点。这种设计确保了文件窃取不会因为在离散节点上增加额外的负载而使事情变得更糟。
解读:
这里说到的数据倾斜就是在上面提到MPP架构里面的痛点,本段落里给出的可能是因为节点故障或者网络通信问题,其实还有很多很多因素,那在对于数据倾斜问题上,snowflake的处理方式是:
A和B两个进程处理的可能是同一个Stage的数据集,A先处理完成了, 但是B卡顿了一只没有处理完成,那么A会通过RPC请求到B中,B会把一部分数据的信息给到A,然后A根据这些元数据信息拉取相关数据进行处理,那么B就不需要处理这部分数据里了。
3.2.3执行引擎
如果另一个系统可以使用10个这样的节点在相同的时间内执行查询,那么能够对超过1000个节点进行查询几乎没有价值。因此,虽然可扩展性很重要,但每个节点的效率同样重要。我们希望为用户提供市场上任何数据库即服务(Database-as-a-Service)产品中性能/价格比最佳的产品,所以我们决定实现自己的最先进的SQL执行引擎。我们构建的引擎是基于列、向量化和push-based。
解读:
基于向量化、列式存储的计算引擎是当下很多大数据存储/分析系统近几年才开始做的,比如Doris、Starrocks、Spark、CK等向量化能力也是近几年开始实现的,但是Snowflake在十几年前就开始通过向量化、列存储来构建自己的计算引擎了。
列存储和执行通常被认为比行存储和执行更适用于分析工作负载,因为它更有效地利用了CPU缓存和SIMD指令,并提供了更多轻量级压缩的能力
向量化执行意味着,与 MapReduce 等方法相比,Snowflake 避免了中间结果的物化。相反,数据以pipeline方式处理,在列存储格式中按数千行一组进行批量处理。这种由 Vectorwise(最初为 MonetDB/X100[15])开创的方法节省了 I/O 并极大地提高了缓存效率。
基于Push-based的执行是指关系运算符将它们的结果推送给下游运算符,而不是等待这些运算符拉取数据(经典的火山模型)。 基于推送的执行提高了缓存效率,因为它从紧密循环中删除了控制流逻辑。它还使Snowflake能够有效地处理DAG形状的计划,而不仅仅是树形结构,从而为共享和pipeline中间结果创造了额外的机会。
解读:
基于Push-based的执行逻辑,在很多计算引擎运算中都有用到,如果对于spark shuffle处理机制比较熟悉的话,对push-based会更容易理解。在Spark shuffle中通过将数据集划分为多个分区并对每个分区进行排序,然后在不同的节点之间交换这些分区的数据,最后对所有分区的数据进行合并,以生成最终的结果。具体来说,就是当一个TaskA需要访问另一个TaskB的输出时,该TaskA会向另一个TaskB发送一个请求,并且TaskB会在接收到请求后立即将其输出发送回请求方,而不是等到下一个轮询周期才发送。这样做的好处是可以减少网络通信的开销并提高任务之间的协作效率。
同时,传统查询处理中的许多开销在Snowflake中不存在的。特别是,在执行过程中不需要事务管理。就引擎而言,查询是针对一组固定的不可变文件执行的。此外,没有缓冲池。大多数查询会扫描大量数据。将内存用于表缓冲而不是操作是一个不好的权衡。然而,Snowflake允许所有主要操作符(join、group by、sort)溢出到磁盘并在主内存耗尽时递归。我们发现,纯主内存引擎虽然更精简、更快,但过于严格,无法处理所有有趣的工作负载。分析工作负载可能具有非常大的连接或聚合。
解读:
简单可以理解为中间shuffle数据结果落盘,内存的容量比磁盘要少很多,所以大多数的计算引擎在计算时会将shuffle数据进行落盘处理,这个在当下大多数分布式计算引擎中都有相关实现。
3.3云服务
虚拟仓库是短暂的、特定于用户的资源。相比之下,云服务层是高度多租户的。该层的每个服务——访问控制、查询优化器、事务管理器等——都是长期存在的,并且在许多用户之间共享。多租户改善了利用率,减少了管理开销,比传统架构中每个用户都拥有完全私有系统实例的情况下实现更好的规模经济。
解读:
对于云数据仓库而言,是立于云的基础设施之上的,对于图一的架构图而言,中间的VW数据仓库其实是一种无状态的计算资源,是和用于进行绑定的,但是在这些资源之上,对于租户管理、查询器优化、事务管理是snowflake平台化的一种能力。
每个服务都有副本以实现高可用性和可扩展性。因此,单个服务节点的故障不会导致数据丢失或可用性的丧失,一些正在运行的查询可能会失败(并被重新执行)。
3.3.1查询管理与优化
用户发出的所有查询都通过云服务层。在这里,处理所有查询生命周期的早期阶段:解析、对象识别、访问控制和计划优化。
解读:
这里的云服务层更多的是部署在云上的服务,这些服务主要作用就是做查询解析的工作,生成查询计划以及查询计划优化等等。
Snowflake 的查询优化器遵循典型的 Cascade (级联式)风格方法[28],采用自顶向下的基于成本的优化。所有用于优化的统计信息都会在数据加载和更新时自动维护。由于 Snowflake 不使用索引(参见第 3.3.3 节),因此计划搜索空间比某些其他系统要小。通过将许多决策推迟到执行时间,例如连接的数据分布类型,可以进一步缩小计划空间。这种设计减少了优化器做出的糟糕决策数量,在以微小的性能损失为代价的同时提高了鲁棒性。它还使系统更易于使用(性能变得更加可预测),这符合Snowflake对服务体验的整体关注度。
解读:
这个是对于内部查询引擎所使用的技术相关。当作了解即可,毕竟我们也不是做查询器的。
自顶向下的搜索过程中,整个搜索空间会形成一个Operator Tree, Cascades首先将整个Operator Tree按节点拷贝到一个Memo的数据结构中,每个Operator放在一个Group。对于有子节点的Operator来说,将原本对Operator的直接引用,变成对Group的引用。
一旦优化器完成,生成的执行计划就会分发给查询中参与的所有工作节点。随着查询的执行,云服务会持续跟踪查询的状态以收集性能计数器并检测节点故障。所有查询信息和统计都用于审计和性能分析。用户可以通过Snowflake图形用户界面监视和分析过去的和正在进行的查询。
3.3.2并发控制
如前所述,Snowflake 的并发控制完全由云服务层处理。Snowflake 是为分析工作负载而设计的,这些工作负载往往是由大量读取、批量插入或点滴插入,以及批量更新。与该工作负载空间中的大多数系统一样,我们决定通过快照隔离实现ACID 事务。
在SI下,事务读取的所有内容都看到了一个一致的数据库快照,该快照是事务开始时的状态。通常情况下,SI是建立在多版本并发控制(MVCC)之上的,这意味着每个更改的数据库对象的副本都会被保留一段时间。
解读:
这里的SI指的是(Snapshot Isolation),这里的快照隔离技术是数据库里面用于实现ACID事务的基本原则,简单来讲,就是通过在每个事务开始的时候创建一个视图,这个视图包含了当前事务开始之间所有已经提交事务的信息,并且在此之后所有未提交的事务不会影响当前的视图,就避免了脏读(Dirty Read)、不可重复读(Non-Repeatable Read)和幻读(Phantom Read)等问题,同时也满足了事务的隔离性和持久性要求,当事务结束并提交时,所有对数据的修改都会被永久保存下来,保证了数据的一致性和可靠性
由于表文件是不可变的,因此使用S3进行存储的直接结果是MVCC是一种自然的选择。文件的更改只能通过用包含更改的不同文件替换来完成。这意味着对表执行写操作(插入、更新、删除、合并)会产生一个新版本的表,通过相对于先前的表版本添加或删除整个文件来实现。文件的添加和删除在元数据中进行跟踪(在全局键值存储中),以一种允许计算属于特定表版本的文件集的方式。
解读:
S3做为对象存储是不支持文件变更的,所以会发现在很多数据湖组件中(iceberg、hudi)都有版本变更的概念,每次在修改一个文件操作时,其实是基于一种Copy On Write的思想来重新生成了一个新的文件,然后替换掉了之前文件。
这是数据湖组件选择使用对象存储作为数据存储方式所必然要做的选择,也就是MVCC(多版本控制)
除了SI之外,Snowflake还使用这些快照来实现时间旅行和数据库对象的高效克隆。有关详细信息,请参阅第4.4节。
解读:
时间旅行的概念如果现在有接触到数据湖技术的话,这个应该是比较清楚的,简单来讲,就是Snowflake使用快照技术可以在保留一定时间内的历史版本的同时,快速恢复到过去的某个时间点或者创建一个完整的备份副本(一种跨越时间窗口的数据恢复能力)。
3.3.3剪枝
解读:
剪枝的实现在OLAP架构中非常的常用,通俗来讲就是对查询条件进行过滤,避免不必要的计算和扫描,从而提高查询性能的一种技术。
其原理是利用多维数据的特点,在查询时根据当前维度和度量值的状态,预测出下一步需要计算的数据,并且只计算必要的部分,而不是全部计算。这样可以大大减少计算时间和内存消耗,提高查询速度和效率。具体实现方法包括前缀剪枝、后缀剪枝、中间状态剪枝等多种策略。
将访问权限限制在与给定查询相关的数据上是查询处理中最重要的方面之一。通常来讲,通过使用B+树或类似的数据结构来限制数据库中的数据访问。虽然这种方法对于事务处理非常有效,但对于像Snowflake这样的系统,它会引发多个问题。首先,它严重依赖于随机访问,这是由于存储介质(S3)和数据格式(压缩文件)的问题。其次,维护索引会显著增加数据量和数据加载时间。最后,用户需要明确地创建索引,这将与Snowflake的纯服务方法非常不符。即使在调优的帮助下,维护索引也可能是一个复杂、昂贵和风险高的过程。
最近,一种新的技术在大规模数据处理中变得流行:基于最小最大值的剪枝,也称为小量聚合、区域映射和跳读。在这种情况下,系统维护给定数据块(记录集、文件、块等)的数据分布信息,特别是该块中的最小值和最大值。根据查询谓词,这些值可以用来确定给定查询是否需要给定的数据块。例如,假设文件f1和f2包含列x的值分别为3..5和4..6。那么,如果查询有一个谓词WHERE x>= 6,则我们知道只需要访问f2。与传统索引不同,这种元数据通常比实际数据小几个数量级,导致存储开销小并且访问速度快。
剪枝很好地符合snowflake设计原则:它不依赖于用户输入;它能够扩展;并且易于维护。此外,对于大量数据的顺序访问,它的效果很好,并且在加载、查询优化和执行时间方面几乎没有开销。
Snowflake 会为每个实例保留与剪枝相关的元数据。单个表文件。元数据不仅包括普通的关联列,还包括半结构化数据中自动检测到的一些列,如第 4.3.2 节所述。在优化过程中,会使用查询谓词来检查元数据,以减少(或剪枝)输入文件集以执行查询。优化程序不仅针对简单的基值谓词进行剪枝,还针对更复杂的表达式进行剪枝,例如WEEKDAY(orderdate)IN(6、7)。
除了静态剪枝之外,Snowflake 还在执行过程中进行动态剪枝。例如,在作为哈希连接处理的一部分时,Snowflake 会收集构建侧记录中连接键分布的统计信息。然后,这些信息会被推送到探查侧,并用于过滤,以及可能跳过探查侧上的整个文件。除此之外,还有其他众所周知的技术,如 Bloom Join [40]。
四、特色功能和亮点
Snowflake 提供了许多预期从关系型数据仓库获得的功能:全面的 SQL 支持、ACID 事务、标准接口、稳定性与安全性、客户支持,以及、当然、强大的性能和可扩展性。此外,它还引入了一些其他功能,这些功能在相关系统中很少见到,或者从未见过。本节介绍一些我们认为是技术差异化的功能。
解读:
目前这些功能和特性基本上像hudi、iceberg的数据湖组件都支持的,而且基于SaaS化的商业化企业也有很多。
所以本节从当下来看,并没有什么特别的地方,主要就是Web的可视化访问,支持Ad-hoc功能,可视化的数据库表管理、用户权限控制(基于数据库的)、监控、查询用量等等。
4.1纯SaaS化体验
Snowflake 支持标准数据库接口(JDBC、ODBC、Python PEP-0249),并与诸如Tableau、Informatica或Looker等第三方工具和服务集成。然而,它还提供了仅使用网络浏览器与系统交互的可能性。web 界面似乎是一件小事,但它很快证明了它是关键差异因素。Web 界面使从任何位置和环境访问 Snowflake 非常容易,并大大减少了启动和使用系统的复杂性。由于大量数据已经存储在云中,它使得许多用户可以指向他们的数据并查询,而无需下载任何软件。
不出所料,UI 不仅支持 SQL 操作,还提供了数据库目录、用户和系统管理、监控、使用信息等。我们不断扩展 UI 功能,专注于在线协作、用户反馈和支持等方面的工作。
但我们在易用性和用户体验上的关注,并不局限于用户界面;它延伸到系统架构的方方面面。没有失败模式,也没有调优旋钮、物理设计或存储优化任务。这一切都是关于数据和查询。
4.2持续可用性
过去,数据仓库解决方案是深藏不露的后端系统,与世隔绝。在这样的环境中,计划内(软件升级或管理任务)和非计划内(故障)停机时间通常不会对运营产生重大影响。但随着数据分析越来越多地成为业务任务的关键部分,任何数据仓库都必须保持持续可用性。这一趋势反映了现代SaaS系统的期望,它们大多都是始终运行、面向客户的应用程序,并且没有(计划内的)停机时间。
“Snowflake提供符合这些期望的持续可用性。在这方面的两个主要技术特点是故障容错和在线升级。
4.2.1故障恢复
图 2 展示了 Snowflake 架构中各层对节点故障的容忍程度。目前,Snowflake 的数据存储层使用的是 Amazon S3,S3 在多个数据中心(称为“可用区”或 AZ)之间进行复制。在 AZ 之间的复制允许 S3 承受整个 AZ 故障,并保证 99.99%的数据可访问性和 99.999999999% 的持久性。与 S3 架构相匹配,Snowflake 的元数据存储也是分布式且在多个 AZ 之间进行复制。如果一个节点出现故障,其他节点可以继续处理工作而不会给终端用户带来很大影响。云服务层的剩余服务由多个 AZ 中的无状态节点组成,负载均衡器在它们之间分配用户请求。因此,单个节点故障甚至整个 AZ 故障都不会对系统产生整体影响,只是可能会导致当前连接到故障节点上的用户查询失败。这些用户将在下一个查询时被重定向到不同的节点。
解读:
这块可以稍加的解释一下,如果使用云计算比较熟悉的话,会了解云是区分不同Region和不同AZ的(availability zones),Region往往指的是地域,比如北京地域就是北京region、杭州一般就是杭州region、上海就是上海region,然后在不同的region下面区分不同的可用区(简称为AZ),可用区也可以理解为是在不同的region下的不同机房,如果北京可能有万国机房、UCloud机房、亦庄电信机房等等,那么这些就是不同region下的不同可用区的分区。
容错性为什么一定要考虑使用不同的AZ呢?不同AZ也就是要多机房部署的概念,这样当某个机房出现问题的时候,另外一个机房所部署的服务就可以来承接业务,不至于业务出现中断。
一般而言,云上的服务都是多AZ来部署的,比如RDS、Redis等等都是支持跨AZ来部署,所以从段落里重点标记的内容也说明了这一点。
相比之下,虚拟仓库(VW)不分布在多个区域。出于性能考虑而做出此选择。分布式查询执行需要高网络吞吐量,而同一区域内的网络吞吐量显著更高。如果在查询执行过程中其中一个工作节点出现故障,查询会失败,但会自动重新运行,要么立即替换节点,要么暂时减少节点数量。为了加快节点替换速度,Snowflake 维护了一个备用节点的小池子。(这些节点也用于快速提供 VW)。
解读:
上面提到数据存储和元数据是跨AZ存储的,但是VW是部署在单AZ的,为什么?文中给出的是出于性能考虑,分布式查询需要高网络吞吐,而跨AZ之间并不是同一个机房内网通信,需要通过专线进行链接。所以网络延迟相对会比较高(具体要看不同AZ之间距离)。
那么,VW容错怎么来保证呢?首先VW的功能是什么?上面有提到负责查询计划解析、执行等,它是一种纯粹的计算资源,计算资源是一种无状态的,即便是失败了重试之后,也可以在另外一个AZ中进行重新执行。
所以,从整个架构上来看,SQL作业首先会进入到云服务中,云服务会根据元数据信息将作业调度到就近的机房中。
如果整个AZ不可用,那么在该AZ中运行的所有查询都会失败,用户需要主动在另一个AZ中重新配置vw。由于全AZ故障确实是真正灾难性的、非常罕见的事件,我们今天接受这种部分系统不可用的情况,但希望在未来解决这个问题。
4.2.2在线升级
Snowflake 不仅在故障发生时提供持续可用性,而且在软件升级期间也是如此。系统设计允许多个版本的各种服务并存部署,包括云服务组件和虚拟仓库。所有服务都是无状态的,这使得这一点成为可能。所有的硬状态都保存在一个事务性的键值存储中,并且可以通过一个映射层来处理元数据版本控制和模式演变。每当我们更改元数据模式时,我们都会确保与旧版本兼容。
解读:
下面对于在线升级讲的比较清楚了,而且常用的在线升级方式就是多版本共存,新旧感知和业务逐步切换的方式
为了执行软件升级,Snowflake 首先并行部署新版本的服务与旧版本。然后逐步切换用户帐户到新版本,在此期间,每个用户的任何新查询都会被重定向到新版本。所有针对旧版本正在运行的查询都可以完成。一旦所有的查询和用户都完成了对旧版本的使用,该版本的所有服务都将终止和退役。
图3显示了正在进行升级过程的快照。有两个版本的Snowflake并排运行,分别是版本1(浅色)和版本2(深色)。Cloud Services 的一个实例有两个版本,每个版本控制两个虚拟仓库 (VW),每个版本也有两个版本。负载均衡器将传入呼叫路由到适当版本的Cloud Services。单个版本的Cloud Services 只与匹配版本的VW通信。
如前所述,云服务的两个版本共享相同的元数据存储库。此外,不同版本的虚拟机可以共享相同的 worker 节点及其各自的缓存。因此,在升级后无需重新填充缓存。整个过程对用户来说是透明的,不会发生停机或性能下降的情况。
在线升级对我们的发展速度产生了巨大的影响,也影响了我们如何处理 Snowflake 中的关键错误。截至撰写本文时,我们每周都会对所有服务进行一次升级。这意味着我们每周都会发布新功能并改进现有功能。为了确保升级过程顺利,升级和降级都在 Snowflake 的特殊预生产版本中持续进行测试。在这些罕见的情况下,如果我们在生产版中发现了关键错误(不一定是在升级期间),我们可以快速降级到之前的版本,或者实施修复并通过计划外升级来完成。这个过程并不像听起来那么可怕,因为我们一直在测试和锻炼升级/降级机制。它现在已经高度自动化并且经过强化。
4.3半结构化数据和无模式数据
解读:
半结构化数据是指具有部分结构的数据,通常包含标签或字段名,但不完全符合传统关系型数据库中的表格式结构。而无模式数据则是指没有预定义的数据结构,可以是任意形式的数据,如文本、图像、音频等。在 Snowflake 中,半结构化数据可以通过 VARIANT 类型进行存储和处理,而无模式数据则需要通过外部工具或程序进行导入和转换。
很多NoSQL 数据库有个共同点,那就是它们都没有模式。若要在关系型数据库中存储数据,首先必须定义“模式”,也就是用一种预定义结构向数据库说明:要有哪些表格,表中有哪些列,每一列都存放何种类型的数据。必须先定义好模式,然后才能存放数据。
相比之下,NoSQL 数据库的数据存储就比较随意了。“键值数据库”可以把任何数据存放在一个“键”的名下。“文档数据库”实际上也如此,因为它对所存储的文档结构没有限制。在列族数据库中,任意列里面都可以随意存放数据。你可以在图数据库中新增边,也可以随意向节点和边中添加属性。
Snowflake 在标准 SQL 类型系统的基础上增加了三种半结构化数据类型:VARIANT、ARRAY 和 OBJECT。VARIANT 类型的值可以存储任何原生 SQL 类型(如 DATE、VARCHAR 等)的值,以及可变长度的 ARRAY 数组,以及类似 JavaScript 的 OBJECT 对象,它是从字符串到 VARIANT 值的映射。后者在文献中也被称为文档,从而产生了文档存储的概念
数组和对象只是变体类型的限制。内部表示法是一样的:自我描述、紧凑的二进制序列化,支持快速的键值对查找,以及高效的类型测试、比较和哈希。因此,可以像使用其他列一样,将变体列用作连接键、分组键和排序键。
VARIANT 类型允许 Snowflake 以一种与传统 ETL(提取 - 转换 - 加载)相反的方式进行操作,即 ELT(提取 - 加载 - 转换)。不需要指定文档模式或对加载数据进行转换。用户可以直接将输入数据从 JSON、Avro 或 XML 格式加载到 VARIANT 列中;Snowflake 处理解析和类型推断(参见第 4.3.3 节)。这种方法在文献中被恰当地称为“延迟模式”,它通过解耦信息生产者和信息消费者以及任何中介来实现模式演变。相比之下,在传统的 ETL 管道中,如果数据模式有任何更改,则需要组织内的多个部门协调,这可能需要数月才能完成。
ELT 和 Snowflake 的另一个优势是,如果需要进行转换,可以使用并行 SQL 数据库的全部功能来实现,包括连接、排序、聚合、复杂谓词等操作,这些在传统的 ETL 工具链中通常缺失或效率低下。在这方面,Snowflake 还支持具有完整 JavaScript 语法和变体数据类型集成的用户定义过程(UDF)。对过程 UDF 的支持进一步增加了可以推入 Snowflake 的 ETL 任务数量。
4.3.1后关系操作(Post-relational Operations)
文档上最重要的操作是从数据元素中提取,通过字段名(对于 对象)或偏移量(对于 数组)。Snowflake 提供了在功能 SQL 表示法 和 JavaScript 风格路径语法 中执行提取的操作。内部编码使得提取非常高效。子元素只是父元素中的一个指针;不需要复制。通常会将提取结果转换为标准 SQL 类型的 VARIANT 值。再次强调,这种编码方式使这些转换变得非常高效。
第二种常见的操作是扁平化,即把嵌套文档转换为多行。Snowflake 使用 SQL 侧视图来表示扁平化操作。这种扁平化可以递归进行,从而完全将文档的层次结构转换为一个适合 SQL 处理的关系表。与扁平化相反的操作是聚合。为了实现这一点,Snowflake 引入了一些新的聚合和分析函数,如 ARRAY_AGG 和 OBJECT_AGG。
解读:
Post-relational Operations 是一种针对半结构化数据的操作方法,它比传统的基于关系模型的数据库操作更为灵活和适应性强。具体来说,Post-relational Operations 包括以下几个方面:
-
提取数据元素:通过路径表达式或者 JavaScript 类似的语法来提取半结构化数据中的特定字段或者子元素。
-
扁平化:将嵌套的数据结构转化为平面表,使得数据更容易被查询和分析。
-
聚合:对半结构化数据进行聚合操作,例如 ARRAY_AGG 和 OBJECT_AGG 等函数,以便更好地进行数据分析和统计。
4.3.2列存储与处理
将半结构化数据使用序列化(二进制)表示,整合到关系型数据库中是一种常规的设计选择。不幸的是,行式表示法使得存储和处理此类数据的效率低于列式关系数据,这也是将半结构化数据转换为普通关系数据的原因。
Cloudera Impala 21 和Google Dremel 34 已经证明了半结构化数据的列式存储是可能且有益的。然而,Impala 和 Dremel(以及其外部化的 BigQuery 44)要求用户为列式存储提供完整的表模式。为了实现无模式序列化表示的灵活性和列式关系数据库的性能,Snowflake 提出了一种用于类型推断和列式存储的新自动化方法。
如第 3.1 节所述,Snowflake 将数据存储为混合列式格式。当存储半结构化数据时,系统会自动对单个表文件中的文档集合进行统计分析,以自动推断类型并确定哪些(带类型的)路径经常出现。然后从文档中删除相应的列,并使用与原生关系型数据相同的压缩列式格式单独存储。对于这些列,Snowflake 还计算了用于剪枝的显式聚合值(参见第 3.3.3 节),就像普通的关系型数据一样。
在扫描过程中,可以将各种列重新组装为单个VARIANT列。然而,大多数查询只对原始文档中的一组列感兴趣。在这种情况下,Snowflake 将投影和转换表达式推入到扫描操作符中,以便仅访问必要的列,并直接转换为目标 SQL 类型。
上述优化在每个表文件中独立执行,即使在架构演变期间也能实现高效的存储和提取。然而,这确实带来了查询优化方面的挑战,特别是剪枝。假设一个查询有一个基于路径表达式的谓词,并且我们想使用剪枝来限制要扫描的文件集。路径及其对应的列可能出现在大多数文件中,但只有足够的频繁出现才需要元数据出现在一些文件中。保守的做法是对所有没有适当元数据的文件进行扫描。Snowflake 通过计算文档中存在的所有路径(不是值!)上的布隆过滤器来改进此解决方案。这些布隆过滤器与其它文件元数据一起保存,并在剪枝过程中由查询优化器探测。不需要给定查询所需的路径的表文件可以安全地跳过。
4.3.3乐观转换
由于一些原生的 SQL 类型,尤其是日期/时间值,在常见的外部格式(如 JSON 或 XML)中表示为字符串,因此这些值需要在写入时(插入或更新期间)或读取时(查询期间)从字符串转换为其实际类型。如果没有类型化的模式或等效的提示,这些字符串转换需要在读取时进行,这比写入时一次转换效率低,在读取主导的工作负载中。无类型的另一个问题是缺乏适当的元数据来进行剪枝,这对于日期尤其重要。(分析工作负载经常对日期列具有范围谓词。)
但是,如果在写入时应用,则自动转换可能会丢失信息。例如,一个字段可能包含数字产品标识符,但实际上它可能不是一个数字,而是一个带有显著前导零的字符串。类似地,看起来像日期的内容实际上可能是短信的内容。Snowflake 通过执行乐观的数据转换来解决这个问题,并将转换结果与原始字符串(除非存在完全可逆的转换)分开存储在不同的列中。如果稍后查询需要原始字符串,则可以轻松检索或重建。由于未使用的列不