Skip to content

云数据仓库——Snowflake

阅读材料

本章我们会讨论Snowflake, 一种专为云端设计的数据库

The Snowflake Elastic Data Warehouse, SIGMOD'16

边阅读边思考

  • 为了云化,SnowFlake用了什么方法?
  • 如何做到弹性伸缩?
  • SnowFlake有一个共享盘设计。有什么好处? 有什么不足?

回顾

Spark和弹性分布式数据集(RDDs)

  • 利用分布式内存,同时提供高效的容错机制,避免显式存储数据更新。相反,通过记录转换过程(lineage)来实现容错.
    • 在传统的分布式系统中,为了容错,系统会定期保存数据的快照或更新日志。而 Spark 则采用了不同的方法:它不直接保存数据的更新,而是记录数据如何从初始状态一步步转换到当前状态的过程(即记录转换过程,称为“谱系”)。如果某个节点出现故障,Spark 可以通过重放这些转换来恢复数据。
  • 通过粗粒度的转换操作(例如,map、filter)进行操作
    • Spark 支持一些高层次的操作,称为“粗粒度的转换”,例如 map(将一个函数应用到数据集中的每个元素)和 filter(根据条件过滤数据集中的元素)。这些操作作用于整个数据集,而不是数据集中的单个元素,因此效率较高。
  • 允许用户控制数据持久性、分区和缓存
    • Spark 允许用户控制数据在内存中的持久性(即数据是否应该被保留在内存中以便后续操作),以及数据如何分区(即如何将数据分布到不同的节点上)。此外,用户还可以选择将数据缓存到内存中,以加速后续的访问。

RDDs

  • 只读,分区的记录集合
  • 从稳定存储中数据或者是其他RDD创建
  • 一系列转换操作定义了一个RDD
    • map、filter、flatmap、sample、groupbykey、reducebykey、join、union
  • Actions返回值或将数据导出到存储系统
    • 如count、collect,save, reduce, lookup

在没有action之前不需要实际运行代码,只读意味着我们可以利用推测性(重新)执行

  • 由于 RDD 是不可变的,如果某个节点失败,Spark 可以在其他节点上重新计算丢失的分区。这种推测性重新执行的能力使得 RDD 在处理大规模数据时具有很好的容错性

截屏2024-07-29 13.16.34

例子

python
lines = spark.textFile("hfds://..")
errors = line.filter(_.startsWith("ERROR"))
errors.count()

Base RDD和transformed RDD 可能永远不会存储在磁盘上

无共享架构回顾

  • 表在节点之间水平分区
  • 每个节点都有自己的本地存储
  • 每个节点只负责本地的表分区
  • 结构优雅,易于理解
  • 对star-schema查询扩展性好
  • 是云计算之前占主导地位的架构模式
    • 代表Teradata,Vertica。Netezza

无共享架构将计算和存储资源耦合在一起了,会有一些风险

  • 弹性问题
    • 调整计算集群规模需要重新分配大量数据
    • 不能简单地关闭未使用的计算资源,无法实现按使用量付费
  • 可用性受限
    • 成员变化(如故障、升级)会显著影响性能,可能导致停机。
  • 同质资源 vs. 异质工作负载
    • 大批量加载、报告生成、探索性分析等不同工作负载需要不同类型的资源配置: 无共享架构通常假设所有节点具有相同的资源配置(同质资源),但实际工作负载是多样化的。例如,大批量数据加载需要高吞吐量,报告生成需要高计算能力,探索性分析需要快速的数据访问。这种同质资源配置难以有效应对异质的工作负载,导致资源使用效率低下。

这个主题在整个课程定位

截屏2024-07-29 13.11.41

Snowflake大纲

  • “弹性数据仓库”(Elastic Data Warehouse),专为云环境量身定制
  • 利用及其可靠的云存储(S3)保证持久性
  • “共享磁盘”的设计模式
    • 得所有计算节点可以共享访问同一个存储池
  • 现代化、高效的查询执行器(query executor)

为什么要构建在云上?

  • 构建分布式系统的绝佳平台
    • 几乎无限且弹性的计算和存储
    • 按使用付费模式(具有强大的规模经济)
    • 随时随地高效访问
  • SAAS,云计算提供软件即服务
    • 无需复杂的 IT 组织和基础设施
    • 按使用付费模式
    • 极大简化的软件交付、更新和用户支持

Snowflake

多集群,共享数据架构特点

  • 无数据孤岛
    • 存储和计算资源分离
  • 原生支持结构化和半结构化数据
  • 无限的扩展性
  • 低成本
    • 按需付费
  • 即时克隆
    • 能够快速克隆数据,将生产环境与开发环境隔离开
  • 高度可用
    • 11个9的数据持久性,4个9的可用性

截屏2024-07-29 20.41.00

整体架构

截屏2024-07-29 20.45.49

所有数据集中在一个地方

  • 所有数据都存储在一个集中位置,便于管理和访问。

独立扩展

  • 存储和计算可以独立扩展,根据需要进行调整。

无需卸载/重新加载即可关闭计算

  • 可以关闭计算资源而无需卸载或重新加载数据,提升灵活性和效率。

每个虚拟仓库都可以访问所有数据

  • 所有虚拟仓库均可访问全部数据,支持多用户和多任务处理。

数据存储层

存储表数据和查询结果

  • 表是不可变的微分区集合

使用分层存储底层为 Amazon S3

  • 对象存储(键值对),提供 HTTP(S) PUT/GET/DELETE 接口

  • 高可用性,3 倍复制,极高的耐用性(11 个 9)

与本地磁盘相比的一些重要差异

  • 相对于本地磁盘,在单个节点上的延迟和带宽较差。
  • 不支持就地更新,对象必须完整写入。
  • 高度并发处理能力。

表文件
  • Snowflake 使用混合列式(PAX/Hybrid columnar)存储
  • 表被水平分区成微分区(~16MB)
    • 更新操作会添加或删除整个文件
    • 每列的值分组在一起并压缩
    • 查询只读取文件头+所需的列

截屏2024-07-29 21.01.02

表聚类
  • 表可以按特定键进行聚类
  • 通过键属性的范围对记录进行分区,使每个微分区(大多数情况下)包含连续的属性范围
    • 聚类通过键属性的范围对记录进行分区,使得每个微分区通常包含一个连续的属性范围。
  • 聚类是惰性的,而不是急切的
  • 系统会自动执行重新聚类操作

截屏2024-07-29 21.05.19

截屏2024-07-29 21.05.51

截屏2024-07-29 21.06.08

系统会自动执行重新聚类,将337重新聚合


块跳过("修剪") vs. 索引

Snowflake没有索引——表聚类如何优化查询呢?

表聚类通过在每个分区中维护最小值和最大值,可以在查询时跳过不相关的分区,只读取那些满足查询条件的分区。这种方法被称为“块跳过”或“修剪”

截屏2024-07-29 21.12.20

系统单独存储块元数据以实现此功能。

为什么不用B+树 + 聚类呢?


中间数据
  • 分级存储也用于临时数据和查询结果
    • 分级存储允许执行任意大小的查询,而不必担心磁盘空间不足的问题。
    • 新的客户端交互形式
      • Snowflake 不使用服务器端游标,而是允许客户端直接获取和重用之前的查询结果
      • 客户端可以检索并重用之前查询的结果,避免重复计算,提高效率
  • 元数据存储在事务性Key-Value存储中(不是 S3)
    • 哪个表包含哪些 S3 对象
    • 优化器统计信息、锁表、事务日志等
    • 属于云服务层的一部分

虚拟仓库层

  • EC2实例集群——“工作节点”
  • 纯计算资源
    • 按需创建、销毁、扩缩容
    • 用户可以同时运行多个仓库,提高并行能力
    • 每个仓库都可以访问数据但性能隔离
    • 没有任务时可关闭所有仓库
  • T-shirt的选项从XS到4XL
    • 用户不知道使用哪种类型或多少EC2实例
    • 服务和定价独立于云平台
工作节点
  • 工作进程是临时的且幂等的
    • 当查询到达时,工作节点会派生新的工作进程
    • 不直接修改微分区,而是将微分区的移除或添加排队
  • 每个工作节点维护本地表缓存
    • 缓存包含过去访问的表文件,即 S3 对象
    • 缓存中的数据可以被同一节点的其他进程共享,避免重复数据加载
    • 通过一致性哈希算法将数据分区分配给节点,并且在需要时可以确定性地从其他节点“窃取”数据,确保负载均衡。
数据亲和性和缓存
  • 在本地存储中缓存数据,并通过LRU管理
  • 通过一致性哈希实现工作节点和分区的亲和性

截屏2024-07-29 21.39.05

截屏2024-07-29 21.39.05

执行 SELECT MAX(a) FROM T

截屏2024-07-29 21.40.42

截屏2024-07-29 21.40.46

当加入一个worker3节点时

截屏2024-07-29 21.41.46

截屏2024-07-29 15.16.57

执行引擎
  • 列式存储 [MonetDB, C-Store 等]

    • 优化了 CPU 缓存的使用,利用 SIMD(单指令多数据)指令和数据压缩技术来提升性能。与行式存储不同,列式存储将相同列的数据存储在一起,便于进行列级别的压缩和操作。
  • 向量化 [Zukowski05]

    • 向量化处理是指操作批量处理几千行的数据,所有数据以列式格式存储。这种方法避免了中间结果的材质化,即不需要将中间计算结果显式地写入磁盘,从而提高了处理效率。
  • 推送式处理 [Neumann11 及其他]

    • 在推送式处理模型中,操作符将结果推送到下游操作符,而不是依赖于传统的 Volcano 迭代器。这种方法去除了紧密循环中的控制逻辑,并且非常适合有 DAG(有向无环图)结构的查询计划。
  • 无事务管理与缓冲池

    • 列式存储系统通常不涉及事务管理或缓冲池。尽管如此,大多数操作符(如连接、分组、排序)可能会溢出到磁盘并递归处理。

    • 查询隔离

      • 查询在处理时是事务性隔离的,即与并发更新隔离,保证查询结果的一致性。

论文阅读: Snowflake

The Snowflake Elastic Data Warehouse, SIGMOD'16

概要

公共云平台现在提供几乎无限的按需计算和存储资源。同时,软件即服务(SaaS)模型将企业级系统带给了以前因为成本和复杂性而无法承担这些系统的用户。然而,传统的数据仓库系统在这个新环境下面临挑战。首先,它们设计为固定资源,因此无法利用云的弹性。其次,它们依赖于复杂的ETL(抽取、转换、加载)流程和物理调优,与云的新型半结构化数据和不断变化的工作负载的灵活性和实时性要求不相符。

传统的数据仓库技术或大数据平台并不能很好地满足这些需求。因此,他们决定专门为云计算构建一个全新的数据仓库系统。 Snowflake运营而生,是一种多租户、事务性、安全性、高扩展性和弹性的,并且完全支持SQL的,提供对半结构化数据和无schema数据的扩展支持。

在这篇论文中,我们描述了 Snowflake 的设计及其新颖的多集群共享数据架构。论文重点介绍了 Snowflake 的一些关键特性:高度弹性和高可用性、半结构化和schema-less数据、时间旅行以及端到端安全

介绍

云计算的出现标志着从在本地服务器上交付和执行软件,转向由平台提供商(如 Amazon、Google 或 Microsoft)托管的共享数据中心和SAAS解决方案。但是传统数据仓库解决方案的静态(固定资源)设计,并不适合在云计算环境,除了平台发送变化,数据也发生变化,这些数据通常是schema-less或者是半结构化格式表达。

为应对这些缺点,部分数据仓储社区转向了诸如 Hadoop 或 Spark 等“大数据”平台,但它们仍然缺乏传统数据仓储技术的许多效率和功能。更重要的是,它们需要大量的工程来部署和使用。我们相信,有一大类使用案例和工作负载可以从云计算的经济性、弹性和服务特性中受益,但这些案例和工作负载并未被传统数据仓储技术或大数据平台很好地服务。因此,我们决定专门为云构建一个全新的数据仓储系统。这个系统被称为 Snowflake 弹性数据仓库。 主要有如下特征

  • 纯SAAS。 用户无需购买机器,雇佣DBA和安装软件
  • 关系型。 Snowflake完全支持ANSI SQL和ACID事务
  • 半结构化。 提供内置函数和SQL扩展,便于遍历、展开和嵌套半结构化数据,支持JSON,Avro等流行格式。
  • 弹性。存储和计算资源可以独立且无缝扩展,不影响数据可用性或并发查询性能
  • 高可用性。 Snowflake 能容忍节点、集群,甚至整个数据中心的故障。软件或硬件升级时不会有停机时间
  • 持久性。Snowflake 设计用于极端持久性,具备防止意外数据丢失的额外保障措施:克隆、撤销删除和跨区域备份
  • 成本效益。的计算效率极高,所有表数据都经过压缩。用户只需为实际使用的存储和计算资源付费。
  • 安全性。所有数据(包括临时文件和网络流量)都是端到端加密的。用户数据绝不会暴露给云平台。此外,基于角色的访问控制使用户能够在 SQL 级别上执行细粒度的访问控制。

传统的数据仓库解决方案是为处理来自内部数据源的可预测、较缓慢移动、易于分类的数据而设计的。它们依赖于复杂的数据转换和物理调优流程,以使数据适合于存储和分析。然而,当面对来自外部和多样化的数据源,如应用程序日志、网络应用程序、移动设备、社交媒体和传感器数据等,这些传统解决方案就显得力不从心。这些新型数据通常是非结构化或半结构化的,以及容量庞大和快速变化的,不适合传统数据仓库的处理方式。因此,传统的数据仓库解决方案面临着适应新型数据和工作负载的挑战。

下面将从几个部分讲解一下Snowflake的设计

  • Snowflake背后的关键设计: 存储和计算分离
  • 多集群、共享数据架构
  • 着重介绍下不同的特性: continuous availability, semi-structured and schema-less data, time travel and cloning, and end-to-end security连续可用行、半结构化、无scheme数据、时间

大纲

  • 正文第一节,会介绍Snowflake的关键设计,存储和计算分离
  • 第二节会介绍多集群共享数据架构的结果
  • 第三节会强调不同的特性:
    • 持续高可用
    • 半结构化、schema-less数据
    • time travel
    • 克隆
    • 端到端安全
  • 第四节会讨论相关工作
  • 第五节会讨论经验

一、存储VS计算

无共享架构在高性能数据仓库中的主流架构,原因有二:可伸缩性和商业硬件。 共享无关架构通过每个节点拥有自己的本地磁盘,表在节点间水平分区,每个节点仅负责在其本地磁盘上的行。这种设计在星型模式查询中表现良好,因为在将小(广播)维度表与大(分区)事实表连接时,所需的带宽很少。而且,由于对共享数据结构或硬件资源的争用很少,因此无需昂贵的定制硬件。

在完全无共享架构中,每个节点都有相同的职责并运行在相同的硬件上。这种方法产生了优雅的软件,易于理解。然而,纯共享无架构有一个重要的缺点:它紧密耦合了计算资源和存储资源,这在某些场景下会导致问题。

  • 异构工作负载:尽管硬件是同构的,但工作负载通常是不同的。系统配置适合大批量加载数据的情况(高I/O带宽,轻量级计算),可能不适合复杂查询的情况(低I/O带宽,重量级计算),反之亦然。
  • 成员变更:如果节点集合发生变化,无论是由于节点故障还是用户选择调整系统大小,都需要重新分配大量数据。由于同一组节点既负责数据重新分配,又负责查询处理,因此会显著影响性能,限制弹性和可用性
  • 在线升级: 成员变更可以通过复制技术在一定程度上缓解小规模的影响。但如果需要进行软件和硬件升级的最终会影响系统中的每个节点。原则上可以实现在线升级,即逐个节点进行升级而不造成系统停机,但由于一切都紧密耦合并且依赖相同的资源,这是一件非常困难的事。

在本地环境中,这些问题通常可以被容忍。尽管工作负载可能是异构的,但如果集群规模比较小,因此在资源调度和分配上没有太多选择或调整空间。节点的升级很少发生,节点故障和系统调整也很罕见。

在云环境中,情况则完全不同。像 Amazon EC2 这样的平台提供了许多不同类型的节点。利用这些节点只需要将数据传输到合适类型的节点上即可。同时,节点故障更为频繁,即使在同类型节点之间,性能差异也可能非常大。因此,成员变更不是例外,而是常态。最后,有很强的动机来实现在线升级和弹性扩展。在线升级显著缩短了软件开发周期并提高了可用性。弹性扩展进一步提高了可用性,使用户能够根据瞬时需求调整资源消耗。

由于上述原因或其他,Snowflake将存储和计算分开处理。这两个方面由两个松耦合的、可独立扩展的服务处理。计算部分由Snowflake的专有无共享引擎提供。这意味着计算节点之间没有共享状态,每个节点都有自己的计算资源,独立处理查询。这种架构允许Snowflake在需要时动态地扩展计算资源。存储部分由Amazon S3提供,虽然原则上任何类型的Blob存储都可以满足要求(如Azure Blob Storage,Google Cloud Storage)。为了减少计算节点和存储节点之间的网络流量,每个计算节点都在本地磁盘上缓存一些表数据。

这种解决方案的一个额外好处是本地磁盘空间不会用于复制整个基础数据,而是专用于临时数据和缓存,这些数据都是热数据(建议用 SSD)。因此,一旦缓存变得热,性能接近甚至超过纯共享无系统。我们称这种新颖的架构为多集群、共享数据架构(multi-cluster, shared-data architecture)。

二、架构

截屏2024-03-28 16.23.39

Snowflake 被设计为一个面向企业的服务,意味着高可用性。 为此采用了面向服务的架构,由高度容错和独立可扩展的服务组成。 这些服务通过 RESTful 接口进行通信。 分为三个架构层次:

  1. 数据存储层:该层使用Amazon S3存储表数据和查询结果。
  2. 虚拟仓库层:系统的“肌肉”。该层在弹性集群的虚拟机中处理查询执行,这些虚拟机被称为虚拟仓库
  3. 云服务层:系统的“大脑”。该层是管理虚拟仓库、查询、事务及其相关元数据的服务集合,包括数据库模式、访问控制信息、加密密钥、使用统计数据等。

2.1 数据存储层

之所以选择AWS S3由于两个目的,一个是AWS是最成熟的云平台之一,其次是拥有最多的潜在用户。

接下来的选择是使用 S3 还是开发基于 HDFS 或类似技术的存储服务,但是后来发现其拥有高可用性,强持久性等技术壁垒很难打破。

总得来说, 相比本地存储,S3 自然具有更高的访问延迟,并且每个 I/O 请求(特别是使用 HTTPS 连接时)都伴有更高的 CPU 开销。对象(即文件)只能完整地被(重新)写入,甚至无法在文件末尾追加数据。实际上,文件的确切大小需要在 PUT 请求中提前声明。然而,S3 支持 GET 请求文件的部分(范围)。

这些特性对 Snowflake 的表文件格式和并发控制方案产生了强烈的影响。表被水平划分成大的、不可变的文件,这些文件在传统数据库系统中相当于块或页面。在每个文件中,每个属性或列的值被分组在一起并进行重度压缩,这种方案在文献中称为 PAX 或混合列式。每个表文件都有一个头部,其中包含每列在文件中的偏移量等元数据。由于 S3 支持文件部分的 GET 请求,查询只需下载文件头部和感兴趣的列。

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

元数据(如目录对象、表由哪些 S3 文件组成、统计信息、锁、事务日志等)存储在一个可扩展的事务性键值存储中,这是云服务层的一部分。

2.2 虚拟仓库层

虚拟仓库层由 EC2 实例集群组成。每个这样的集群通过一种叫做虚拟仓库(VW)的抽象呈现给单个用户。构成 VW 的各个 EC2 实例被称为工作节点。用户从不直接与工作节点交互。事实上,用户并不知道或关心构成 VW 的具体工作节点数量或种类。

弹性和隔离

VW 是纯粹的计算资源。它们可以随时按需创建、销毁或调整大小。创建或销毁 VW 对数据库状态没有影响。用户关闭所有 VW 是完全合法(并且当没有查询时,被鼓励的),,这种弹性允许用户根据使用需求动态调整计算资源,而不受数据量的影响。

每个单独的查询只在一个 VW 上运行。工作节点不在 VW 之间共享(这样带来了强隔离性,但是未来可能会有改变,因为通过共享可以提升利用率)。当提交新查询时,各自 VW 中的每个工作节点会生成一个新的工作进程。每个工作进程只在其查询期间存在。即使是更新语句的一部分,工作进程本身也不会引起外部可见的影响,因为表文件是不可变的。因此,工作失败很容易被包含并通过重试常规解决。

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

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

另一个与弹性相关的重要观察是,通常可以在大致相同的价格下获得更好的性能。例如,一个在4个节点上需要15小时的数据加载可能在32个节点上仅需要2小时。由于计算时间按小时收费,总成本是非常相似的——但用户体验却有显著不同。因此,我们认为 VW 弹性是 Snowflake 架构的最大优势和差异之一,它表明需要一种新的设计来利用云的独特能力。

本地缓存和文件窃取

每个工作节点在本地磁盘上维护一个表数据缓存。缓存是已被节点访问过的表文件集合,即 S3 对象。更准确地说,缓存持有文件头和文件的各个列,因为查询只下载所需的列。缓存在工作节点的整个生命周期内存在,并在并发和后续的工作进程(即查询)之间共享。它只是看到一个文件和列请求流,并遵循一个简单的最近最少使用(LRU)替换策略,而对个别查询不关心。这种简单的方案效果出奇地好。

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

在工作节点集发生变化时(由于节点故障或 VW 调整大小),Snowflake 的一致性哈希是懒惰的——不会立即重组数据。相反,Snowflake 依赖 LRU 替换策略逐渐替换缓存内容。这种解决方案将替换缓存内容的成本摊分到多个查询上,从而比急切缓存或纯粹的共享无系统(需要立即在节点之间大量重组表数据)实现更好的可用性。同时,这也简化了系统,因为没有“降级”模式。

除了缓存,偏斜处理在云数据仓库中尤为重要。由于虚拟化问题或网络争用,有些节点可能会比其他节点执行得慢得多。Snowflake 在扫描层处理这个问题。当一个工作进程完成其输入文件集的扫描时,它会向其同行请求额外的文件,这种技术我们称之为文件窃取。如果一个同伴在收到这样的请求时发现其输入文件集中有许多剩余文件,它会通过在当前查询的范围和持续时间内转移一个剩余文件的所有权来响应请求。请求者然后直接从 S3 下载文件,而不是从其同伴下载。这个设计确保文件窃取不会通过给拖延节点增加额外负载使情况变得更糟。

执行引擎

如果另一个系统可以用 10 个节点在相同时间内完成查询,那么能够在 1000 个节点上执行查询就没有太大价值。因此,虽然可扩展性是首要的,但每个节点的效率同样重要。我们希望为用户提供市场上最佳的性价比,因此决定实施我们自己的最先进的 SQL 执行引擎。我们构建的引擎是列式、矢量化和推送式的。

列式存储和执行通常被认为比行式存储和执行更适合分析工作负载,因为它更有效地利用了 CPU 缓存和 SIMD 指令,并且有更多机会进行(轻量级)压缩。

矢量化执行意味着,与例如 MapReduce 相比,Snowflake 避免了中间结果的物化。相反,数据以管道方式处理,批量处理几千行列格式数据。这种方法由 VectorWise(最初是 MonetDB/X100)开创,节省了 I/O 并大大提高了缓存效率。

推送式执行指的是关系操作符将其结果推送到其下游操作符,而不是等待这些操作符拉取数据(经典的 Volcano 模型)。推送式执行提高了缓存效率,因为它从紧密循环中移除了控制流逻辑。它还使 Snowflake 能够有效地处理 DAG 形计划,而不仅仅是树形计划,从而创造了额外的共享和管道中间结果的机会。

同时,传统查询处理中许多开销在 Snowflake 中并不存在。尤其是,在执行期间不需要事务管理。就引擎而言,查询是在一组固定的不可变文件上执行的。此外,也没有缓冲池。大多数查询扫描大量数据。在这里,使用内存进行表缓冲与操作是不明智的权衡。然而,Snowflake 允许所有主要操作符(连接、分组、排序)在主内存耗尽时溢出到磁盘并递归。我们发现,纯内存引擎虽然更精简且可能更快,但对处理所有有趣的工作负载来说限制太大。分析工作负载可能包含极大的连接或聚合。

2.3 云服务层

虚拟仓库是临时的、用户特定的资源。相比之下,云服务层是高度多租户的。该层的每个服务——访问控制、查询优化器、事务管理器等——都是长期存在的,并且在许多用户之间共享。多租户提高了利用率并减少了管理开销,从而比传统架构中每个用户都有一个完全私有的系统实例更具有规模经济。

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

查询管理和优化

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

Snowflake的查询优化器遵循典型的Cascades风格方法【28】,采用自上而下的成本优化。所有用于优化的统计数据在数据加载和更新时自动维护。由于Snowflake不使用索引(参见第3.3.3节),计划搜索空间比其他一些系统要小。通过将许多决策推迟到执行时间,进一步减少了计划空间,例如连接的数据分布类型。这种设计减少了优化器做出的错误决策的数量,提高了鲁棒性,以小幅性能损失为代价。它还使系统更易于使用(性能变得更加可预测),这与Snowflake对服务体验的整体关注相一致。

一旦优化器完成,生成的执行计划将分发给所有参与查询的工作节点。随着查询的执行,云服务不断跟踪查询的状态以收集性能计数器并检测节点故障。所有查询信息和统计数据都存储以供审核和性能分析。用户可以通过Snowflake图形用户界面监视和分析过去和正在进行的查询。

并发控制

如前所述,并发控制完全由云服务层处理。Snowflake针对分析工作负载而设计,这些工作负载通常以大量读取、批量或渐进插入和批量更新为主。像大多数此类工作负载空间中的系统一样,我们决定通过快照隔离(SI)实现ACID事务【17】。

在SI下,事务的所有读取都看到事务开始时数据库的一致快照。按惯例,SI是在多版本并发控制(MVCC)之上实现的,这意味着每个更改的数据库对象的副本在一段时间内保留。考虑到表文件是不可变的这一事实,MVCC 是一个自然的选择,这是使用S3进行存储的直接后果。对文件的更改只能通过用包含更改的不同文件替换来进行【3】。因此,对表的写操作(插入、更新、删除、合并)通过相对于先前表版本添加和删除整个文件来生成表的新版本。文件的添加和删除在元数据(在全局键值存储中)中进行跟踪,以一种形式,使得可以非常高效地计算属于特定表版本的文件集。

除了用于SI,Snowflake还使用这些快照来实现时间旅行和高效克隆数据库对象

修剪

限制访问仅限于给定查询相关的数据是查询处理的最重要方面之一。历史上,通过使用索引(以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收集构建侧记录中连接键分布的统计信息。这些信息随后被推送到探测侧并用于过滤并可能跳过探测侧的整个文件。这是除了诸如布隆连接【40】等其他已知技术外的一种技术。

三、特性

Snowflake 提供了关系型数据仓库所期望的许多功能:全面的 SQL 支持、ACID 事务、标准接口、稳定性和安全性、客户支持,以及强大的性能和可扩展性。此外,它还引入了一些其他在相关系统中很少或从未见过的有价值的功能。本节介绍了我们认为技术上具有区分度的一些功能。

纯SAAS体验

Snowflake 支持标准数据库接口(JDBC、ODBC、Python PEP-0249),并与各种第三方工具和服务(如 Tableau、Informatica 或 Looker)兼容。然而,它还提供了仅通过 Web 浏览器与系统交互的可能性。虽然 Web UI 看似微不足道,但它迅速证明了自己是一个关键的差异化因素。Web UI 使得从任何位置和环境访问 Snowflake 变得非常简单,极大地减少了引导和使用系统的复杂性。由于大量数据已经在云中,这使得许多用户只需将 Snowflake 指向他们的数据并进行查询,而无需下载任何软件。

正如预期的那样,UI 不仅允许 SQL 操作,还提供对数据库目录、用户和系统管理、监控、使用信息等的访问。我们不断扩展 UI 功能,致力于在线协作、用户反馈和支持等方面。

但我们对易用性和服务体验的关注并不仅限于用户界面;它扩展到系统架构的每一个方面。系统没有失败模式(Failure Modes),没有调优旋钮(Tuning Knobs),没有物理设计(Physical Design),没有存储清理任务(Storage Grooming Tasks)。一切都是关于数据和查询。

持续可用性

过去,数据仓库解决方案是隐藏在后台系统中的,与大多数世界隔绝。在这种环境下,停机没有很大影响。但随着数据分析对越来越多,业务任务变得至关重要,持续可用性成为任何数据仓库的重要要求。这也是大多数 SaaS 系统的期望, 都是始终在线、面向客户的应用程序,没有停机。Snowflake 提供了满足这些期望的持续可用性。在这方面,两个主要的技术特性是故障弹性和在线升级。

故障弹性Fault Resilience

截屏2024-07-29 16.50.51

Snowflake 能够容忍架构各级别的单个和相关节点故障,如图2所示。Snowflake 当前的数据存储层是 S3,它在多个数据中心(在亚马逊术语中称为“可用区availability zones”或 AZs)之间进行了复制。跨 AZ 的复制允许 S3 处理完整的 AZ 故障,并保证 99.99% 的数据可用性和 99.999999999% 的持久性。与 S3 的架构相匹配,Snowflake 的元数据存储也是分布式和在多个 AZ 之间复制的。如果一个节点失败,其他节点可以接管活动,而对最终用户影响不大。云服务层的其余服务由多个 AZ 中的无状态节点组成,负载均衡器在它们之间分配用户请求。因此,单个节点故障或甚至整个 AZ 故障不会造成系统范围的影响,可能只是对当前连接到故障节点的用户的某些查询失败。这些用户将被重定向到不同的节点以执行他们的下一个查询。

相比之下,虚拟仓库(VWs)并不分布在 AZ 之间。这是出于性能原因。高网络吞吐量对分布式查询执行至关重要,而在同一 AZ 内的网络吞吐量显著更高。如果一个工作节点在查询执行期间失败,查询会失败但会被透明地重新执行,要么用立即替换的节点,要么用暂时减少的节点数。为了加快节点替换,Snowflake 维护了一个小的备用节点池。(这些节点也用于快速 VW 配置。)如果整个 AZ 变得不可用,那么所有在该 AZ 的 VW 上运行的查询都会失败,用户需要主动在不同的 AZ 中重新配置 VW。

在线升级 Online Upgrade

截屏2024-07-29 16.50.05

Snowflake 不仅在发生故障时提供持续可用性,在软件升级期间也提供持续可用性。系统设计允许各种服务版本并行(side-by-side)部署,包括云服务组件和虚拟仓库。这得益于所有服务实际上都是无状态的。所有硬状态都保存在一个事务性键值存储中,并通过一个映射层访问,该层负责元数据版本控制和模式演变。每当我们更改元数据模式时,我们都会确保与先前版本的向后兼容性。

为了执行软件升级,Snowflake 首先将新版本的服务部署在先前版本旁边。用户随后逐步切换到新版本,每当切换时,所有由相应用户发出的新查询都会被引导到新版本。所有正在执行的查询将继续在先前版本上完成。一旦所有查询和用户完成对先前版本的使用,所有该版本的服务将被终止并停用。

图3 显示了正在进行的升级过程的快照。这里运行了两个版本的 Snowflake,版本 1(浅色)和版本 2(深色)。有两个版本的云服务实例,控制两个虚拟仓库(VWs),每个 VW 有两个版本。负载均衡器将传入的调用定向到适当版本的云服务。一个版本的云服务只与匹配版本的 VW 通信。

如前所述,两个版本的云服务共享相同的元数据存储。此外,不同版本的 VW 能够共享相同的工作节点及其相应的缓存。因此,升级后不需要重新填充缓存。整个过程对用户是透明的,没有停机或性能下降

半结构化和无模式数据

Snowflake 扩展了标准 SQL 类型,引入了三种半结构化数据类型:VARIANTARRAYOBJECTVARIANT 类型的值可以存储任何原生 SQL 类型的值(如 DATEVARCHAR 等),以及可变长的 ARRAY 和类似 JavaScript 的 OBJECT,即从字符串到 VARIANT 值的映射。后者在文献中也称为文档(documents),形成了文档存储(如 MongoDB、Couchbase)的概念。

ARRAYOBJECTVARIANT 类型的子集。它们的内部表示相同:自描述的紧凑二进制序列化,支持快速的键值查找、高效的类型测试、比较和哈希。因此,VARIANT 列可以像其他列一样用作连接键、分组键和排序键。

VARIANT 类型使 Snowflake 能够以 ELT(提取-加载-转换)方式使用,而不是传统的 ETL(提取-转换-加载)方式。无需指定文档schema或在加载时执行转换。用户可以直接将 JSON、Avro 或 XML 格式的输入数据加载到 VARIANT 列中;Snowflake 处理解析和类型推断。这种方法在文献中称为“schema later”方法,它解耦了,信息生产者与信息消费者及任何中介。相比之下,传统 ETL 管道中的数据模式变更需要在组织内协调多个部门,这可能需要几个月才能执行。

ELT 和 Snowflake 的另一个优势是,如果后续需要转换,可以使用并行 SQL 数据库的全部功能进行处理,包括连接、排序、聚合、复杂谓词等操作,这些操作通常在传统 ETL 工具链中缺失或效率低下。在这方面,Snowflake 还具有程序化用户定义函数(UDFs),支持完整的 JavaScript 语法并与 VARIANT 数据类型集成。对程序化 UDFs 的支持进一步增加了可以推送到 Snowflake 的 ETL 任务数量。

后关系(Post-relational)操作

对文档的最重要操作是提取数据元素,可以按字段名称(对于 OBJECT)或按偏移量(对于 ARRAY)进行。Snowflake 提供了两种提取操作:功能 SQL 表达法和类似 JavaScript 的路径语法。内部编码使得提取非常高效。子元素只是父元素内部的一个指针;不需要复制。提取操作通常会将结果的 VARIANT 值转换为标准 SQL 类型。编码的高效性使得这些转换也非常高效。

第二种常见操作是扁平化,即将嵌套文档转换为多行。Snowflake 使用 SQL 横向视图来表示扁平化操作。扁平化可以是递归的,允许将文档的层次结构完全转换为一个适合 SQL 处理的关系表。与扁平化相对的操作是聚合。Snowflake 引入了一些新的聚合和分析函数,例如 ARRAY_AGGOBJECT_AGG,用于此目的。

列存储和处理

使用序列化(二进制)表示半结构化数据是一种常见的设计选择,用于将半结构化数据集成到关系数据库中。然而,行式表示法使得这种数据的存储和处理效率低于列式关系数据,这通常是将半结构化数据转换为普通关系数据的原因。

有学者已经证明了列式存储半结构化数据是可能的且有益的。然而,Impala 和 Dremel(及其外部化的 BigQuery)要求用户提供完整的表schema以实现列式存储。为了实现无模式序列化表示法的灵活性和列式关系数据库的性能,Snowflake 引入了一种新颖的自动化类型推断和列式存储方法。

如第 2.1 节所述,Snowflake 以混合列式格式(hybrid columnar format)存储数据。在存储半结构化数据时,系统会自动对单个表文件中的文档集合进行统计分析,以进行自动类型推断,并确定哪些(类型化的)路径是常见的。相应的列随后从文档中移除并单独存储,使用与原生关系数据相同的压缩列式格式。对于这些列,Snowflake 甚至计算了物化聚合(materialized aggregates)以用于修剪(参见第 2.3.3 节),就像普通关系数据一样。

在扫描过程中,各个列可以重新组合成一个 VARIANT 类型的单列。然而,大多数查询只关心原始文档的子集。在这些情况下,Snowflake 将投影和转换表达式下推到扫描操作符中,以便只访问必要的列,并直接转换为目标 SQL 类型。

上述优化对于每个表文件独立执行,这允许即使在模式演变下也能实现高效存储和提取。然而,这确实对查询优化,特别是修剪,带来了挑战。假设查询具有路径表达式的谓词,并且我们希望使用修剪来限制要扫描的文件集。路径和相应列可能在大多数文件中存在,但只有在一些文件中频繁到足以 warrant 元数据的程度。保守的解决方案是简单地扫描所有没有合适元数据的文件。Snowflake 通过对所有文档中的路径(而不是值)计算布隆过滤器来改进这一解决方案。这些布隆过滤器与其他文件元数据一起保存,并由查询优化器在修剪过程中进行探测。未包含查询所需路径的表文件可以安全地跳过。

乐观转换

由于一些原生 SQL 类型,特别是日期/时间值,在 JSON 或 XML 等常见外部格式中以字符串形式表示,因此这些值需要在写入时(插入或更新时)或读取时(查询期间)从字符串转换为其实际类型。在没有类型化模式或等效提示的情况下,这些字符串转换需要在读取时执行,而在以读取为主的工作负载中,这比在写入时执行转换效率低。另一个问题是未类型化数据缺乏适当的元数据用于修剪,这在日期列中尤为重要(分析工作负载通常对日期列有范围谓词)。

但在写入时应用自动转换可能会丢失信息。例如,包含数字产品标识符的字段实际上可能不是数字,而是带有显著前导零的字符串。同样,看起来像日期的内容实际上可能是文本消息的内容。Snowflake 通过执行乐观数据转换来解决这一问题,并将转换结果和原始字符串(如果存在完全可逆转换)保存在单独的列中。如果查询后需要原始字符串,可以轻松检索或重建。由于未使用的列不会加载和访问,任何双重存储对查询性能的影响是最小的。

性能

为了评估列式存储、乐观转换和半结构化数据修剪对查询性能的综合影响,我们进行了使用类似 TPC-H 数据和查询的一组实验。

我们创建了两种类型的数据库模式。首先是传统的关系 TPC-H 模式。其次是“无模式”数据库模式,其中每个表都包含一个 VARIANT 类型的单列。然后,我们生成了集群(排序的)SF100 和 SF1000 数据集(分别为 100 GB 和 1 TB),以 JSON 格式(即日期变成字符串)存储数据,并将数据加载到 Snowflake 中,使用关系型和无模式数据库模式。没有任何关于无模式数据的字段、类型和集群的提示提供给系统,也没有进行其他调优。然后,我们在无模式数据库上定义了几个视图,以便能够运行相应的测试。最后,我们在四个数据库上运行了所有 22 个 TPC-H 查询,使用了一个中等标准的仓库。图 4 展示了结果。数字是通过三次运行得到的,缓存已经预热。标准误差很小,因此从结果中省略了。

截屏2024-09-03 16.14.53

从结果中可以看出,除了两个查询(Q9 和 Q17 对 SF1000 的查询)外,无模式存储和查询处理的开销约为 10%。对于这两个查询,我们确定性能下降的原因是由于一个已知的独特值估计错误导致的子优化连接顺序。我们继续改进对半结构化数据的元数据收集和查询优化。

总的来说,对于具有相对稳定和简单模式的半结构化数据(即在实践中大多数机器生成的数据),查询性能几乎与传统关系数据相当,享受列式存储、列式执行和修剪的所有优势——而无需用户额外的努力

时间旅行和克隆

前面我们讨论了Snowflake如何在多版本并发控制(MVCC)之上实现快照隔离(SI)。对表的写操作(插入、更新、删除、合并)通过添加和移除整个文件来生成表的新版本。

当新版本移除文件时,这些文件会在配置的时间段内(目前最长可达90天)保留。文件保留允许Snowflake非常高效地读取表的早期版本,即对数据库进行时间旅行。用户可以使用方便的 AT 或 BEFORE 语法从 SQL 中访问此功能。时间戳可以是绝对的,相对于当前时间的相对时间,或者相对于先前语句的相对时间(由 ID 指代)。

sql
SELECT * FROM my_table AT(TIMESTAMP => 'Mon, 01 May 2015 16:20:00 -0700'::timestamp);
SELECT * FROM my_table AT(OFFSET => -60*5); -- 5 分钟前
SELECT * FROM my_table BEFORE(STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726');

甚至可以在一个查询中访问同一表的不同版本。

sql
SELECT new.key, new.value, old.value
FROM my_table new
JOIN my_table AT(OFFSET => -86400) old -- 1 天前
ON new.key = old.key
WHERE new.value <> old.value;

基于相同的底层元数据,Snowflake 引入了 UNDROP 关键字,可以快速恢复意外删除的表、模式或整个数据库。

sql
DROP DATABASE important_db; -- 哎呀!
UNDROP DATABASE important_db;

克隆操作仅复制源表的元数据。克隆后,两个表都引用相同的文件集,但此后两个表可以独立修改。克隆功能也支持整个模式或数据库,这使得快照变得非常高效。在执行大量更新之前,或在进行长期的探索性数据分析时,快照是一种很好的实践。CLONE 关键字甚至可以与 AT 和 BEFORE 结合使用,允许在事后进行这样的快照。

sql
CREATE DATABASE recovered_db CLONE important_db BEFORE(
STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726');

安全

Snowflake 旨在保护用户数据免受所有层级的攻击,包括云平台。为此,Snowflake 实施了两因素认证、(客户端侧)加密的数据导入和导出、安全的数据传输和存储,以及Role-base的访问控制(RBAC )。在任何时候,数据都在发送到网络、写入本地磁盘或共享存储(如 S3)之前被加密。因此,Snowflake 提供了完整的端到端数据加密和安全性。

密钥层次结构

截屏2024-07-29 20.09.50

Snowflake 使用强大的 AES 256 位加密,并采用根植于 AWS CloudHSM [12] 的层次化密钥模型。加密密钥会自动轮换和重新加密(“重新密钥化”),以确保密钥完成 NIST 800-57 密码学密钥管理生命周期的全部过程 [13]。加密和密钥管理对用户完全透明,无需配置或管理。

Snowflake 的密钥层次结构如图5所示,具有四个级别:根密钥、账户密钥、表密钥和文件密钥。每一层(父级)密钥加密(即包裹)下一级(子级)密钥。每个账户密钥对应一个用户账户,每个表密钥对应一个数据库表,每个文件密钥对应一个表文件。

层次化的密钥模型是一种良好的安全实践,因为它限制了每个密钥保护的数据量。每一层减少了下面密钥的范围,如图5所示。Snowflake 的层次化密钥模型确保了多租户架构中用户数据的隔离,因为每个用户账户都有一个单独的账户密钥。

密钥生命周期

除了限制每个密钥保护的数据量外,Snowflake 还限制了密钥的使用时间。加密密钥经历四个阶段:(1)预操作创建阶段,(2)操作阶段,其中密钥用于加密(发起者使用期)和解密(接收者使用期),(3)后操作阶段,其中密钥不再使用,以及(4)销毁阶段。阶段 1、3 和 4 的实现相对简单。阶段 2 需要限制发起者使用期和接收者使用期。只有在密钥不再加密任何需要的数据时,才能将其移动到阶段 3 和 4。Snowflake 通过密钥轮换和重新密钥化来限制发起者使用期和接收者使用期。

密钥轮换在定期间隔(例如,每月一次)创建新的密钥版本。在每个间隔后,创建新版本的密钥并“退役”旧版本。退役版本仍可用于解密数据。当包裹新的子密钥或写入表时,仅使用最新的、活跃版本的密钥来加密数据。

重新密钥化是使用新密钥重新加密旧数据的过程。在特定时间间隔(例如,一年)后,使用退役密钥加密的数据将使用活跃密钥重新加密。这种重新密钥化与密钥轮换是独立的。密钥轮换确保密钥从活跃状态(发起者使用)转移到退役状态(接收者使用),而重新密钥化确保密钥可以从退役状态转移到销毁状态。

图6 显示了单个表密钥的生命周期。假设密钥每月轮换一次,数据每年重新密钥化一次。表文件 1 和 2 于 2014 年 4 月创建,使用密钥 1 版本 1(k1v1)。2014 年 5 月,密钥 1 轮换到版本 2(k1v2),并使用 k1v2 创建了表文件 3。2014 年 6 月,密钥 1 轮换到版本 3(k1v3),并创建了两个新的表文件。从 2014 年 6 月起,表中不再进行插入或更新。2015 年 4 月,k1v1 满一年,需要销毁。创建了一个新密钥,密钥 2 版本 1(k2v1),并使用 k2v1 重新密钥化所有与 k1v1 关联的文件。2015 年 5 月,k1v2 也进行了相同的操作,表文件 3 使用 k2v2 重新密钥化。2015 年 6 月,表文件 4 和 5 使用 k2v3 重新密钥化。

类似的方案也在账户密钥与表密钥之间,根密钥与账户密钥之间实现。密钥层次结构的每一层都经历密钥轮换和重新密钥化,包括根密钥。账户密钥和根密钥的密钥轮换和重新密钥化不需要重新加密文件。只有直接下级的密钥需要重新加密。

然而,表密钥与文件密钥之间的关系不同。文件密钥不是由表密钥包裹的,而是从表密钥和(唯一的)文件名的组合中加密派生出来的。因此,每当表密钥更改时,所有相关的文件密钥都会更改,因此受影响的表文件需要重新加密。密钥派生的一个重要好处是它消除了创建、管理和传递单独文件密钥的需求。像 Snowflake 这样处理数十亿文件的系统,否则必须处理大量的文件密钥。

我们选择这种设计还因为 Snowflake 的存储与计算分离,使其可以在不影响用户工作负载的情况下执行重新加密。重新密钥化在后台进行,使用不同的工作节点与查询分开。文件重新密钥化后,Snowflake 原子地更新数据库表的元数据,以指向新加密的文件。所有正在进行的查询完成后,旧文件会被删除。

端到端安全

Snowflake 使用 AWS CloudHSM 作为生成、存储和使用密钥层次结构根密钥的防篡改、高度安全的方法。AWS CloudHSM 是一组连接到 AWS 虚拟私有集群的硬件安全模块(HSM)。根密钥永远不会离开 HSM 设备。所有使用根密钥的密码学操作都在 HSM 内部进行。因此,低级别的密钥在没有授权访问 HSM 设备的情况下无法被解开。HSM 还用于生成账户密钥和表密钥,包括在密钥轮换和重新密钥化过程中。我们将 AWS CloudHSM 配置为高可用配置,以最小化服务中断的可能性。

除了数据加密外,Snowflake 还通过以下方式保护用户数据:

  1. 通过对 S3 的访问策略隔离存储。
  2. 在用户账户内使用基于角色的访问控制,提供对数据库对象的细粒度访问控制。
  3. 加密的数据导入和导出,确保云提供商(如 Amazon)永远不会看到明文数据。
  4. 两因素认证和联合认证以确保安全访问控制。

总之,Snowflake 提供了一个以 AWS CloudHSM 为根基的层次化密钥模型,并使用密钥轮换和重新密钥化来确保加密密钥遵循标准化生命周期。密钥管理对用户完全透明,无需配置、管理或停机。它是一个全面的安全策略的一部分,使完整的端到端加密和安全成为可能。

四、相关工作

基于云的并行数据库系统。亚马逊拥有多种 DBaaS(数据库即服务)产品,其中 Amazon Redshift 是数据仓库产品之一。Redshift 发展自并行数据库系统 ParAccel,可以说是第一个真正提供为服务的数据仓库系统 [30]。Redshift 使用经典的无共享架构。因此,尽管具有可扩展性,但添加或删除计算资源时需要数据重新分布。相比之下,Snowflake 的多集群共享数据架构允许用户即时独立扩展、缩减或暂停计算,而无需移动数据——包括能够跨隔离的计算资源集成数据。此外,遵循纯服务原则,Snowflake 不需要用户进行物理调优、数据整理、手动收集表统计信息或表清理。尽管 Redshift 可以将半结构化数据(如 JSON)作为 VARCHAR 导入,Snowflake 对半结构化数据提供了原生支持,包括列存储等重要优化。

Google 的云平台提供了一项完全托管的查询服务,称为 BigQuery [44],这是 Dremel [34] 的公开实现。BigQuery 服务让用户可以在惊人的速度下对 terabytes 的数据运行查询,并在数千个节点上并行处理。Snowflake 的一个灵感来源是 BigQuery 对 JSON 和嵌套数据的支持,我们认为这对于现代分析平台是必要的。但虽然 BigQuery 提供了一种类似 SQL 的语言,它在 ANSI SQL 语法和语义上存在一些基本的偏差,使其在与 SQL 基础产品一起使用时有一定的困难。此外,BigQuery 表是追加式的,并且需要模式。相比之下,Snowflake 提供了完整的 DML(插入、更新、删除、合并)、ACID 事务,并且不需要为半结构化数据定义模式。

Microsoft SQL Data Warehouse(Azure SQL DW)是最近加入 Azure 云平台的服务,基于 SQL Server 及其 Analytics Platform System(APS)设备 [35, 37]。类似于 Snowflake,它将存储与计算分离。计算资源可以通过数据仓库单位(DWUs)进行扩展。然而,对于任何数据仓库,最大并发执行查询的数量被限制为 32 [47]。相比之下,Snowflake 允许通过虚拟仓库完全独立地扩展并发工作负载。Snowflake 用户也无需选择合适的分布键和其他管理任务。尽管 Azure SQL DW 支持通过 PolyBase [24] 对非关系数据进行查询,但它没有像 Snowflake 的 VARIANT 类型及相关优化那样对半结构化数据的内建支持。

文档存储和大数据。近年来,文档存储(如 MongoDB、Couchbase Server 和 Apache Cassandra )在应用程序开发人员中变得越来越流行,因为它们提供了可扩展性、简单性和模式灵活性。然而,这些系统简单的键值和 CRUD(创建、读取、更新和删除) API 带来的一个挑战是,难以表达更复杂的查询。作为回应,我们看到了几种类似 SQL 的查询语言的出现,例如 Couchbase 的 N1QL 或 Apache Cassandra 的 CQL。此外,许多“大数据”引擎现在支持对嵌套数据的查询,例如 Apache Hive、Apache Spark、Apache Drill、Cloudera Impala和 Facebook Presto。

我们认为这表明对无模式和半结构化数据进行复杂分析的真正需求,我们的半结构化数据支持受到这些系统的启发。通过使用模式推断、乐观转换和列存储,Snowflake 将这些系统的灵活性与关系型列式数据库的存储效率和执行速度结合在一起。

五、经验教训与展望

当 Snowflake 于 2012 年成立时,数据库领域完全专注于 Hadoop 上的 SQL,短时间内出现了十几种相关系统。当时,选择完全不同的方向,将“传统”数据仓库系统云化,似乎是一种逆势而行且风险很大的决定。经过三年的开发,我们对这一决策的正确性充满信心。Hadoop 并没有取代关系型数据库管理系统(RDBMS);它只是对其进行了补充。人们仍然希望有一个关系型数据库,但需要一个更高效、更灵活、更适合云环境的系统。

Snowflake 已经实现了我们对云端系统的期望,无论是对用户还是对开发者。多集群共享数据架构的弹性改变了用户处理数据任务的方式。SaaS 模型不仅使用户能够轻松尝试和采用该系统,还大大帮助了我们的开发和测试。通过单一的生产版本和在线升级,我们能够比在传统开发模型下更快地发布新功能、提供改进并解决问题。

尽管我们希望半结构化数据扩展能够发挥作用,但我们对其采用速度感到惊讶。我们发现了一个非常流行的模式,即组织使用 Hadoop 做两件事:存储 JSON,并将其转换为可以加载到 RDBMS 中的格式。通过提供一个可以高效存储和处理半结构化数据的系统——并在其上提供强大的 SQL 接口——我们发现 Snowflake 不仅替代了传统的数据库系统,还取代了 Hadoop 集群。

当然,这并不是一个毫无痛苦的旅程。尽管我们的团队拥有超过 100 年的数据库开发经验,我们在过程中还是犯了一些可以避免的错误,包括一些关系操作符的早期实现过于简单、未能在引擎中早期引入所有数据类型、资源管理的关注不够早、推迟对日期和时间功能的全面工作等。此外,我们持续关注避免调优参数带来了一系列工程挑战,最终促使了许多令人兴奋的技术解决方案。因此,今天,Snowflake 只有一个调优参数:用户希望的性能(以及愿意为之付费的金额)。

虽然 Snowflake 的性能已经非常具有竞争力,特别是在没有调优方面,但我们知道还有许多优化工作尚未完成。然而,令人意外的是,核心性能几乎从未成为我们用户的问题。原因在于,通过虚拟仓库提供的弹性计算可以提供偶尔所需的性能提升。这使我们将开发工作重点转向了系统的其他方面。

我们面临的最大技术挑战与 SaaS 和多租户方面有关。构建一个可以支持数百名用户并发使用的元数据层是一项非常具有挑战性和复杂的任务。处理各种类型的节点故障、网络故障和支持服务是一场永无止境的斗争。安全性一直是并将继续是一个重大议题:保护系统和用户数据免受外部攻击、用户自身的威胁以及我们内部用户的潜在风险。维护一个运行着数百个节点、每天处理数百万个查询的实时系统,同时带来极大的满足感,也需要高度集成的开发、运维和支持方法。

Snowflake 用户继续向系统提出越来越大和复杂的问题,影响着其发展。我们目前正在通过提供额外的元数据结构和数据重新组织任务来改进数据访问性能——重点是最小化或无需用户互动。我们继续改进和扩展核心查询处理功能,包括标准 SQL 和半关系扩展。我们计划进一步改善偏斜处理和负载均衡策略,其重要性随着用户工作负载的规模增加而增加。我们致力于简化用户的工作负载管理,使系统变得更加弹性。我们还在与外部系统的集成上进行工作,包括高频数据加载等问题。

Snowflake 面临的最大未来挑战是过渡到完全自服务模式,即用户可以在任何阶段独立注册和使用系统。这将带来大量的安全性、性能和支持挑战。我们期待着这些挑战。