数据是组织策划关键业务决策所需的信息和洞察的主要资产。无论是用于分析特定产品的年度销售趋势还是预测未来的市场机会,数据都塑造了组织成功的方向。此外,如今数据不仅仅是一种美好的附加条件,而且是一种必需,不仅是为了在市场中获胜,而且是为了在其中竞争。随着对信息的巨大需求,人们已经付出了巨大努力,积累了组织内各种系统生成的数据,以获取洞察。
与此同时,运营和分析系统生成数据的速度飙升。尽管更多的数据为企业提供了做出更明智决策的机会,但也迫切需要一个平台来存储和分析所有这些数据,以便用于构建分析产品,如商业智能 (BI) 报告和机器学习 (ML) 模型,以支持决策制定。湖仓架构,我们将在本章详细介绍,将我们存储数据的方式与我们处理数据的方式分开,以获得更大的灵活性。本章将从实际角度介绍数据平台的历史和演变,并呈现湖仓架构与Apache Iceberg开放表格格式的优势。
我们是怎么到达这里的?简史
就存储和处理系统而言,关系数据库管理系统(RDBMSs)长期以来一直是组织记录所有事务数据的标准选择。例如,假设你经营一家运输公司,想要记录客户的新预订信息。在这种情况下,每个新预订将是RDBMS中的一行数据。用于此目的的RDBMS支持一种特定的数据处理类别,称为在线事务处理(OLTP)。一些优化了OLTP的RDBMS的例子包括PostgreSQL、MySQL和Microsoft SQL Server。这些系统被设计和优化,以便您能够与一行或几行数据进行非常快速的交互,是支持企业日常运营的良好选择。
但是,假设你想要了解上个季度所有新预订所带来的平均利润。在这种情况下,使用OLTP优化的RDBMS中存储的数据一旦数据足够大,就会导致性能问题。造成这种情况的一些原因包括以下几点:
- 事务性系统专注于向表中插入、更新和读取一小部分行数据,因此将数据存储在基于行的格式中是理想的。然而,分析系统通常专注于对特定列进行汇总或处理表中的所有行,使列式结构更有优势。
- 在同一基础设施上运行事务性和分析工作负载可能会导致资源竞争。
- 事务性工作负载从将数据规范化为几个相关表中受益,这些表在需要时进行连接,而分析工作负载可能在将数据非规范化为同一张表中以避免大规模连接操作时表现更好。
现在想象一下,你的组织有大量的运营系统产生大量的数据,而你的分析团队希望构建依赖于这些不同数据源(即应用程序数据库)数据聚合的仪表板。不幸的是,OLTP系统并不适用于涉及大量历史记录的复杂聚合查询。这些工作负载称为在线分析处理(OLAP)工作负载。为了解决这个限制,您需要一种针对OLAP工作负载进行优化的不同类型的系统。正是这种需求促成了数据湖仓架构的发展。
为OLAP工作负载设计的系统的基本组成部分
为OLAP工作负载设计的系统由一系列技术组件组成,这些组件使得支持现代分析工作负载成为可能,正如图1-1所展示,并在接下来的小节中进行了描述。
存储
为了分析来自各种来源的历史数据,您需要一个系统,可以存储大量至重要数量的数据。因此,存储是您在能够处理大型数据集的分析查询的系统中所需的第一个组件。存储有许多选项,包括直接附加存储(DAS)上的本地文件系统;您操作的一组节点上的分布式文件系统,如Hadoop分布式文件系统(HDFS);以及云提供商提供的对象存储服务,如Amazon Simple Storage Service(Amazon S3)。 关于存储类型,您可以使用行导向数据库或列导向数据库,或者在某些系统中混合使用两者。近年来,列导向数据库得到了广泛的采用,因为它们在处理大量数据时被证明更有效。
文件格式
为了存储目的,您的原始数据需要以特定的文件格式组织。您选择的文件格式影响诸如文件的压缩、数据结构和给定工作负载的性能等方面的因素。 文件格式通常分为三个高级类别:结构化(CSV)、半结构化(JSON)和非结构化(文本文件)。在结构化和半结构化类别中,文件格式可以是行导向或列导向(列式)。行导向文件格式将给定行的所有列存储在一起,而列导向文件格式将给定列的所有行存储在一起。行导向文件格式的两个常见例子是逗号分隔值(CSV)和Apache Avro。列导向文件格式的例子包括Apache Parquet和Apache ORC。 根据用例,某些文件格式可能比其他文件格式更有优势。例如,如果您一次处理的记录数量较少,则行导向文件格式通常更好。相比之下,如果您一次处理的记录数量较大,则列导向文件格式通常更好。
表格式
表格式是支持在大量数据上进行聚合查询的系统的另一个关键组成部分。表格式充当文件格式之上的元数据层,并负责指定数据文件在存储中的布局方式。 表格式的最终目标是抽象出物理数据结构的复杂性,并促进诸如数据操纵语言(DML)操作(例如执行插入、更新和删除操作)和更改表模式的功能。现代表格式还带来了执行DML操作所需的原子性和一致性保证。
存储引擎
存储引擎是实际执行按照表格式指定的形式布置数据的系统,并使所有文件和数据结构与新数据保持同步的系统。存储引擎处理一些关键任务,例如对数据的物理优化、索引维护以及清理旧数据。
目录
在处理来自各种来源且规模较大的数据时,迅速识别可能需要进行分析的数据非常重要。目录通过利用元数据来识别数据集来解决此问题。目录是计算引擎和用户可以查找关于表的存在以及其他信息(如表名称、表模式以及表数据在存储系统中的位置)的中心位置。一些目录是系统内部的,并且只能通过该系统的引擎直接交互;这些目录的示例包括Postgres和Snowflake。其他目录,如Hive和Project Nessie,对任何系统都是开放的。请注意,这些元数据目录与用于人类数据发现的目录不同,例如Colibra、Atlan和Dremio软件内部目录。
计算引擎
计算引擎是处理存储系统中持久化的大量数据所需的最后一个组件。这样的系统中计算引擎的作用是运行用户工作负载以处理数据。根据数据量、计算负载和工作负载的类型,您可以使用一个或多个计算引擎来完成此任务。当处理大型数据集和/或有大量计算要求时,您可能需要使用一种称为大规模并行处理(MPP)的处理范式中的分布式计算引擎。基于MPP的计算引擎的几个示例包括Apache Spark、Snowflake和Dremio。
将一切融合在一起
传统上,针对OLAP工作负载,这些技术组件都被紧密耦合到一个称为数据仓库的单一系统中。数据仓库允许组织存储来自各种来源的数据,并在数据之上运行分析工作负载。在接下来的章节中,我们将详细讨论数据仓库的能力,技术组件如何集成以及使用这种系统的利弊。
数据仓库
数据仓库或OLAP数据库是一个集中式存储库,支持存储从各种来源摄取的大量数据,如运营系统、应用程序数据库和日志。图1-2展示了数据仓库技术组件的架构概述。
数据仓库拥有单一系统中的所有技术组件。换句话说,所有数据都存储在其专有的文件和表格式中,存储在其专有的存储系统中。然后,这些数据由数据仓库的存储引擎专门管理,在其目录中注册,并且只能通过其计算引擎由用户或分析引擎访问。
简史
直到2015年左右,大多数数据仓库在相同节点上紧密耦合存储和计算组件,因为大多数都是设计和在本地运行的。然而,这导致了许多问题。随着数据集的容量以加速的速度增长,以及工作负载的数量和强度(即在仓库上运行的计算任务)增加,扩展性成为一个重大问题。具体来说,根据任务独立增加计算和存储资源的方式不存在。如果您的存储需求增长速度比计算需求更快,那也无所谓。您仍然需要支付额外的计算成本,即使您并不需要它。
这导致了下一代数据仓库的建立,重点是云。随着云原生计算的出现,这些数据仓库开始在2015年左右引起人们的关注,允许您分离计算和存储组件,并将这些资源按照您的任务进行扩展。它们甚至允许您在不使用计算时关闭计算,并且不会丢失存储。
数据仓库的优缺点
尽管数据仓库,无论是在本地还是基于云,都使企业能够快速理解其所有历史数据,但仍存在一些领域仍然存在问题。表1-1列出了数据仓库的优缺点。
| 优点 | 缺点 |
|---|---|
| 作为真实数据的唯一来源,允许存储和查询来自各种来源的数据 | 将数据锁定在只有仓库的计算引擎可以使用的特定供应商系统中 |
| 支持查询大量历史数据,使得分析工作负载能够快速运行 | 在存储和计算方面昂贵;随着工作负载的增加,成本难以管理 |
| 提供有效的数据治理政策,以确保数据可用、可用性和符合安全策略 | 主要支持结构化数据 |
| 为您组织数据布局,确保其经过优化以进行查询 | 不支持组织原生运行高级分析工作负载,如ML |
| 确保写入表的数据符合技术模式 |
数据仓库充当组织存储来自众多来源的所有数据的集中式存储库,允许数据使用者(如分析师和BI工程师)轻松快速地从单一来源访问数据,以开始他们的分析。此外,支撑数据仓库的技术组件使您能够访问大量数据,同时支持在其之上运行BI工作负载。
尽管数据仓库在数据民主化方面起到了关键作用,并允许企业从各种数据源中获得历史洞见,但它们主要受限于关系型工作负载。例如,回到之前的运输公司示例,假设您想要从历史数据中推断出下一个季度的总销售额。在这种情况下,您需要使用历史数据构建一个预测模型。然而,您不能通过数据仓库原生实现这种功能,因为计算引擎和其他技术组件不是为基于ML的任务而设计的。因此,您主要可行的选择是将数据从仓库移动或导出到支持ML工作负载的其他平台。这意味着您将有多个副本的数据,并且为每个数据移动创建管道可能会导致关键问题,例如数据漂移和模型衰减,当管道不正确或不一致地移动数据时。
在数据仓库上运行高级分析工作负载的另一个障碍是,数据仓库仅支持结构化数据。但是,其他类型的数据,如半结构化和非结构化数据(JSON、图像、文本等)的快速生成和可用性,已经使ML模型能够揭示出有趣的洞见。对于我们的示例,这可能是了解上个季度所有新预订评论的情感。这最终将影响组织做出未来导向性决策的能力。
数据仓库还存在特定的设计挑战。回到图1-2,您可以看到数据仓库中的所有六个技术组件都紧密耦合在一起。在理解这意味着什么之前,要观察的一个重要事项是文件和表格式都是特定数据仓库的内部。这种设计模式导致了封闭的数据架构形式。这意味着实际数据只能使用数据仓库的计算引擎访问,该引擎专门设计用于与仓库的表格和文件格式交互。这种类型的架构让组织对锁定的数据感到极大的担忧。随着工作负载的增加和随着时间的推移向仓库摄取的大量数据,您将受限于特定的平台。这意味着您的分析工作负载(如BI和您计划引入的任何未来工具)只能在特定的数据仓库上运行。这也阻止了您迁移到另一个可以满足您特定要求的数据平台。
此外,将数据存储在数据仓库中并使用计算引擎处理数据与显著的成本因素相关联。随着您的环境中工作负载数量的增加,这种成本只会随时间增加,从而调用更多的计算资源。除了货币成本外,还有其他开销,例如需要工程团队构建和管理大量管道来从运营系统移动数据,以及数据使用者延迟的洞察时间。这些挑战促使组织寻求替代数据平台,这些平台允许数据处于其控制之下,并以开放的文件格式存储,从而允许BI和ML等下游应用与大大降低的成本并行运行。这导致了数据湖的出现。
数据湖
虽然数据仓库为在结构化数据上运行分析提供了一种机制,但它们仍存在一些问题:
- 数据仓库只能存储结构化数据。
- 数据仓库的存储通常比本地 Hadoop 集群或云对象存储更昂贵。
- 传统的本地数据仓库中的存储和计算通常混合在一起,因此无法分别扩展。随着存储增加,计算成本也随之增加,无论您是否需要计算能力。
解决这些问题需要一种替代的存储解决方案,这种解决方案更便宜,并且可以存储所有数据,而无需符合固定的模式。这种替代方案就是数据湖。
简史
最初,您会使用 Hadoop,一个开源的分布式计算框架,以及其 HDFS 文件系统组件,跨廉价计算机集群存储和处理大量结构化和非结构化数据集。但仅仅能够存储所有这些数据是不够的。您也会希望对其进行分析。
Hadoop 生态系统包括 MapReduce,这是一个分析框架,您可以在其中使用 Java 编写分析作业,并在 Hadoop 集群上运行它们。编写 MapReduce 作业冗长而复杂,而且许多分析人员更习惯于编写 SQL 而不是 Java,因此创建了 Hive,以将 SQL 语句转换为 MapReduce 作业。
为了编写 SQL,需要一种机制来区分存储中哪些文件是特定数据集或表的一部分。这导致了 Hive 表格式的诞生,该格式将目录及其内部的文件识别为一个表。
随着时间的推移,人们开始不再使用 Hadoop 集群,转而使用云对象存储(例如 Amazon S3、Minio、Azure Blob Storage),因为它更易于管理且使用成本更低。 MapReduce 也因其他分布式查询引擎(如 Apache Spark、Presto 和 Dremio)的兴起而逐渐退出使用。但是,留存下来的是 Hive 表格式,它成为了识别存储中的文件并对其进行分析的标准。但是,云存储需要更多的网络成本来访问这些文件,而 Hive 格式架构未能预料到这一点,这导致了由于 Hive 对表的文件夹结构的依赖而导致的过多的网络调用。
数据湖与数据仓库的一个区别在于能够利用不同的计算引擎来处理不同的工作负载。这是重要的,因为从来没有一种银弹计算引擎适用于每种工作负载,并且可以独立于存储扩展计算。这只是计算性质的固有特性,因为总会有权衡,您决定权衡什么决定了一个给定系统适用于什么以及不太适合什么。
需要注意的是,在数据湖中,实际上没有任何服务能够满足存储引擎功能的需求。通常,计算引擎决定如何写入数据,然后通常不会再次访问和优化数据,除非整个表或分区被重新编写,这通常是在特定情况下进行的。图1-3描述了数据湖组件之间的交互。
数据湖的优缺点
当然,没有一种架构模式是完美的,数据湖也不例外。尽管数据湖具有许多优点,但它们也有一些局限性。以下是优点:
优点:
成本较低 在数据湖中存储数据并执行查询的成本要比在数据仓库中低得多。这使得数据湖特别适用于使数据分析能够触及更广泛的数据,而这些数据的优先级还不足以证明数据仓库的成本,从而降低了成本。
以开放格式存储数据 在数据湖中,您可以以任何文件格式存储数据,而在数据仓库中,您无法控制数据的存储方式,通常会采用专为特定数据仓库构建的专有格式。这使您对数据拥有更多的控制权,并且可以在支持这些开放格式的更多工具中使用数据。
处理非结构化数据 数据仓库无法处理非结构化数据,如传感器数据、电子邮件附件和日志文件,因此,如果您想对非结构化数据运行分析,那么数据湖就是唯一的选择。
缺点:
性能 由于数据湖的每个组件都是解耦的,因此许多紧密耦合系统中存在的优化都不存在,例如索引和ACID(原子性、一致性、隔离性、持久性)保证。虽然它们可以重新创建,但需要大量的工作和工程来组装组件(存储、文件格式、表格式、引擎),以使性能与数据仓库相媲美。这使得数据湖在对性能和时间要求较高的高优先级数据分析方面变得不受欢迎。
需要大量配置 如前所述,要创建您选择的组件与您期望从数据仓库中获得的优化程度更紧密的耦合,需要进行大量的工程工作。这将导致需要大量的数据工程师来配置所有这些工具,这也可能成本高昂。
缺乏ACID事务 数据湖的一个显著缺点是缺乏传统关系数据库中常见的内置ACID事务保证。在数据湖中,数据通常以读取时架构的方式进行摄取,这意味着模式验证和一致性检查发生在数据处理过程中而不是在摄取时。这可能对需要强大事务完整性的应用程序造成挑战,例如金融系统或处理敏感数据的应用程序。在数据湖中实现类似的事务保证通常涉及实施复杂的数据处理管道和协调机制,增加了对关键用例所需的工程工作。虽然数据湖在可伸缩性和灵活性方面表现出色,但当严格的ACID兼容性是主要要求时,它可能不是理想的选择。
我应该在数据湖还是数据仓库上运行分析?
虽然数据湖为您的结构化和非结构化数据提供了一个很好的着陆场所,但仍存在一些缺陷。在运行ETL(提取、转换和加载)将数据落入数据湖后,通常在运行分析时有两种选择。 例如,您可以设置额外的ETL管道来创建一个经过筛选的数据子集的副本,用于高优先级的分析,并将其存储在数据仓库中,以获得数据仓库的性能和灵活性。 然而,这会导致一些问题:
- 额外的成本,用于额外的ETL工作中的计算以及在数据仓库中存储数据的副本,而您已经在数据仓库中存储了这些数据,而存储成本通常更高。
- 数据的额外副本,可能需要为不同的业务线填充数据集市,甚至在分析人员创建数据子集的物理副本以加速仪表板时还会有更多副本,导致数据副本的网络难以管控、跟踪和同步。
另外,您还可以使用支持数据湖工作负载的查询引擎,例如Dremio、Presto、Apache Spark、Trino和Apache Impala,在数据湖上执行查询。这些引擎通常非常适合只读工作负载。但是,由于Hive表格式的限制,在尝试从数据湖安全地更新数据时,它们会遇到复杂性问题。
正如您所看到的,数据湖和数据仓库各自具有独特的优点和局限性。这迫使我们需要开发一种新的架构,既能提供它们的优点,又能最小化它们的缺点,这种架构被称为数据湖仓。
数据湖仓
虽然使用数据仓库为我们带来了性能和易用性,但在数据湖上进行的分析为我们带来了更低的成本、使用开放格式的灵活性、使用非结构化数据的能力等等。对于技术进步和创新的渴望推动了巨大的进步,最终演变成了我们现在所知道的数据湖仓。
数据湖仓架构将存储和计算与数据湖分开,并引入了允许更多类似数据仓库功能(ACID事务、更好的性能、一致性等)的机制。支持这种功能的是数据湖表格格式,它消除了之前与Hive表格格式相关的所有问题。您在数据湖仓中存储数据的位置与在数据湖中存储数据的位置相同,您使用的查询引擎与在数据湖中使用的查询引擎相同,您的数据存储格式与在数据湖中存储的格式相同。真正将您的世界从“只读”数据转变为“我的数据世界的中心”的是表格格式提供的元数据/抽象层,使引擎和存储之间能够更智能地交互(见图1-4)。
表格格式在文件存储之上创建了一个抽象层,使得在直接在数据湖存储上处理数据时能够获得更好的一致性、性能和ACID保证,从而导致了几个价值主张:
- 更少的副本 = 更少的漂移
有了ACID保证和更好的性能,您现在可以将通常保存在数据仓库中的工作负载(例如更新和其他数据操作)转移到数据湖仓中,从而降低成本和数据移动。如果将数据移到数据湖仓,您可以拥有更简化的架构,副本更少。更少的副本意味着更低的存储成本,从将数据移动到数据仓库的计算成本,更少的漂移(数据模型在相同数据的不同版本中发生变化/破坏),以及更好的数据管理,以保持符合法规和内部控制。
- 更快的查询 = 更快的见解
最终目标始终是通过我们的数据获得高质量的见解来获得业务价值。其他所有步骤都是为了达到这个目标。如果您可以更快地进行查询,那意味着您可以更快地获得见解。数据湖仓通过在查询引擎(基于成本的优化器、缓存)、表格格式(使用元数据进行更好的文件跳过和查询规划)和文件格式(排序和压缩)中进行优化,实现了比数据湖和可比较的数据仓库更快的查询。
- 历史数据快照 = 不会产生影响的错误
数据湖仓表格格式维护历史数据快照,使得可以查询和恢复表格到其以前的快照。您可以处理您的数据,而不必担心错误会导致数小时的审核、修复,然后是回填。
- 经济实惠的架构 = 商业价值
提高利润有两种方法:增加收入和降低成本。数据湖仓不仅可以帮助您获得驱动收入增长的业务见解,还可以帮助您降低成本。这意味着您可以通过避免数据的重复来减少存储成本,避免进行额外的ETL工作来移动数据,并且相对于典型的数据仓库费率,您使用的存储和计算的价格更低。
- 开放架构 = 安心
数据湖仓建立在开放格式之上,例如Apache Iceberg作为表格格式和Apache Parquet作为文件格式。许多工具可以读取和写入这些格式,这使您可以避免供应商锁定。供应商锁定会导致成本上升和工具被锁定,其中您的数据存储在工具无法访问的格式中。通过使用开放格式,您可以放心,知道您的数据不会被困在一套狭窄的工具中。
总结一下,通过先前讨论的开放标准的现代创新,可以在严格运用数据湖上实现最佳的所有世界,这种架构模式就是数据湖仓。使所有这些成为可能的关键组件是表格格式,它使得引擎在处理以前根本不存在的数据时具有了保证和改进的性能。现在让我们将讨论转向Apache Iceberg表格格式。
什么是表格格式?
表格格式是一种将数据集文件结构化以呈现为统一的“表格”的方法。从用户的角度来看,它可以被定义为回答“这个表格中有什么数据?”的问题。
这个简单的回答使得多个个人、团队和工具可以同时与表格中的数据进行交互,无论是从中读取还是写入。表格格式的主要目的是为用户和工具提供对表格的抽象,使它们更容易以高效的方式与底层数据进行交互。
表格格式自关系数据库管理系统(RDBMS)的诞生以来就存在,比如 System R、Multics 和 Oracle,它们首次实现了 Edgar Codd 的关系模型,尽管当时并没有使用“表格格式”这个术语。在这些系统中,用户可以将一组数据称为表格,数据库引擎负责管理数据集在磁盘上的字节布局,以文件的形式存在,同时处理诸如事务之类的复杂性。
在这些 RDBMS 中,与数据的所有交互,如读取和写入,都由数据库的存储引擎管理。没有其他引擎可以直接与文件交互,否则会有破坏系统的风险。数据存储的具体细节被抽象化,用户默认平台知道特定表格的数据位于何处以及如何访问它。
然而,在今天的大数据世界中,依赖单一封闭引擎来管理对底层数据的所有访问已不再实际。您的数据需要访问多种针对不同用例(如BI或ML)进行优化的计算引擎。
在数据湖中,您所有的数据都以文件的形式存储在某种存储解决方案中(例如,Amazon S3、Azure Data Lake Storage [ADLS]、Google Cloud Storage [GCS]),因此一个单一的表格可能由数十、数百、数千甚至数百万个存储在该存储中的单独文件组成。当使用我们喜爱的分析工具进行SQL查询或在诸如Java、Scala、Python和Rust等语言中编写临时脚本时,我们不希望不断地定义哪些文件属于表格,哪些文件不属于表格。这不仅会很繁琐,而且很可能会导致在数据的不同使用中出现一致性问题。
因此,解决方案是为数据湖创建一种标准方法来理解“这个表格中有什么数据”,如图1-5所示。
Hive:原始的表格格式
在运行 Hadoop 数据湖上的分析时,使用的是 MapReduce 框架,需要用户编写复杂而繁琐的 Java 作业,这对许多分析人员来说并不可访问。Facebook 感受到了这种情况的痛苦,于 2009 年开发了一个称为 Hive 的框架。Hive 为在 Hadoop 上进行分析提供了一个关键的好处:可以直接编写 SQL 而不是 MapReduce 作业。
Hive 框架会接收 SQL 语句,然后将其转换为可以执行的 MapReduce 作业。为了编写 SQL 语句,必须有一种机制来理解存储在 Hadoop 存储中的数据代表了哪个唯一的表格,于是诞生了 Hive 表格格式和用于跟踪这些表格的 Hive Metastore。
Hive 表格格式采用了将表格定义为指定目录(或对象存储的前缀)中的所有文件的方法。这些表格的分区将是子目录。这些定义表格的目录路径由称为 Hive Metastore 的服务跟踪,查询引擎可以访问它以了解数据适用于其查询的位置。如图 1-6 所示。
Hive表格格式具有以下几个优点:
- 它使得查询模式更加高效,比全表扫描更快,因此诸如分区(根据分区键划分数据)和桶化(一种基于哈希函数均匀分布值的分区或聚类/排序方法)等技术使得可以避免扫描每个文件以加快查询速度。
- 它是文件格式无关的,因此随着时间的推移,数据社区可以开发出更好的文件格式,例如 Apache Parquet,并在其Hive表格中使用它们。它还不需要在将数据可用于Hive表之前进行转换(例如,Avro,CSV/TSV)。
- 通过在Hive Metastore中列出的目录进行原子交换,可以对表中的单个分区进行全或无(原子)更改。 随着时间的推移,这成为了事实上的标准,与大多数数据工具一起工作,并提供了对“这个表中有什么数据?”的统一答案。
虽然这些优点很显著,但随着时间的推移,也暴露出了许多限制:
- 文件级别的更改效率低,因为没有像Hive Metastore那样原子交换文件的机制,你基本上只能在分区级别进行交换,以原子方式更新单个文件。
- 虽然可以原子交换一个分区,但没有一种机制可以原子方式更新多个分区作为一个事务。这导致最终用户在更新多个分区的事务之间看到的数据不一致。
- 实际上没有很好的机制来实现并发的同时更新,尤其是使用Hive本身之外的工具。
- 对文件和目录进行列举是耗时的,并减慢了查询速度。必须读取和列举可能不需要在结果查询中扫描的文件和目录,这是有代价的。
- 分区列通常是从其他列派生的,例如从时间戳派生一个月份列。分区只有在按分区列过滤时才有用,而将过滤器放在时间戳列上的人可能不会直觉地知道也要在派生的月份列上过滤,导致没有充分利用分区而进行全表扫描。
- 表统计信息将通过异步作业收集,通常导致状态表统计信息,如果有统计信息的话。这使得查询引擎难以进一步优化查询。
- 由于对象存储经常限制对同一前缀的请求(将对象存储前缀视为类似于文件目录),对具有大量文件的单个分区的表进行查询(使得所有文件都在一个前缀中)可能会导致性能问题。 数据集和用例的规模越大,这些问题就会变得越加突出。这导致需要一个新的解决方案,因此创建了更新的表格格式。
现代数据湖表格格式
为了解决Hive表格格式的局限性,一种新一代的表格格式应运而生,采用了不同的方法来解决Hive的问题。 现代表格格式的创建者意识到导致Hive表格格式挑战的缺陷是,表的定义是基于目录内容而不是个别数据文件的。现代表格格式(如Apache Iceberg、Apache Hudi和Delta Lake)都采用了这种将表定义为文件的规范列表的方法,为引擎提供了元数据,告知哪些文件组成表,而不是哪些目录。这种更细粒度的“什么是表”的定义方法打开了支持ACID事务、时间旅行等功能的大门。 现代表格格式都旨在相对于Hive表格格式带来一系列核心优势:
- 它们允许进行ACID事务,这是安全的事务,要么完全完成,要么取消。在传统格式(如Hive表格格式)中,许多事务无法提供这些保证。
- 当存在多个写入者时,它们能够提供安全的事务处理。如果两个或更多写入者向表写入数据,就会有机制确保第二个完成写入的写入者意识到并考虑其他写入者所做的工作,以保持数据的一致性。
- 它们提供更好的表统计信息和元数据收集,使查询引擎能够更有效地规划扫描,从而需要扫描的文件更少。
让我们探讨一下Apache Iceberg是什么以及它是如何产生的。
什么是Apache Iceberg?
Apache Iceberg是由Netflix的Ryan Blue和Daniel Weeks于2017年创建的一种表格格式。它的出现是为了克服性能、一致性以及Hive表格格式之前所述的许多挑战。2018年,该项目开源,并被捐赠给了Apache软件基金会,在那里许多其他组织开始参与其中,包括苹果、Dremio、AWS、腾讯、LinkedIn和Stripe。此后,许多其他组织也对该项目做出了贡献。
Apache Iceberg的产生
Netflix在创建最终成为Apache Iceberg格式的过程中得出结论,Hive格式的许多问题源于一个简单但根本的缺陷:每个表都被跟踪为目录和子目录,限制了提供一致性保证、更好的并发性以及通常在数据仓库中可用的几个功能所必需的细粒度。 基于此,Netflix着手创建一个新的表格格式,并设定了几个目标: 一致性 如果对表进行了多个分区的更新,终端用户不应该体验到他们所查看的数据的不一致。表跨多个分区的更新应该快速而原子地进行,以确保数据对终端用户是一致的。他们只能看到更新之前或之后的数据,而不是中间的数据。
性能 :由于Hive的文件/目录列表瓶颈,查询规划在实际执行查询之前需要花费过长的时间。表应该提供元数据并避免过多的文件列表,以便查询规划不仅可以更快地完成,而且由于只扫描必要的文件来满足查询,结果计划也可以更快地执行。
易于使用 :为了获得诸如分区等技术的好处,终端用户不应该知道表的物理结构。表应该能够基于自然直观的查询为用户提供分区的好处,并且不依赖于对已经过滤的列进行额外的分区列过滤(例如,当您已经根据时间戳进行了过滤时,过滤月份列)。
可演进性: 更新Hive表格的模式可能导致不安全的事务,并且更新表的分区方案将导致需要重新编写整个表。表应该能够安全地演变其模式和分区方案,而无需重新编写。
可扩展性: 所有前述目标应该能够在Netflix数据的PB级别规模下完成。
因此,该团队开始创建Iceberg格式,该格式的重点是将表定义为文件的规范列表,而不是跟踪表为目录和子目录的列表。Apache Iceberg项目是一种规范,或者说是如何跨多个文件编写定义数据湖表的元数据的标准。为了支持这一标准的采用,Apache Iceberg有许多支持库,可以帮助个人使用该格式或使计算引擎实现支持。除了这些库之外,该项目还为Apache Spark和Apache Flink等开源计算引擎创建了实现。
Apache Iceberg旨在让现有的工具采用这一标准,并设计为利用现有的流行存储解决方案和计算引擎,希望现有的选项能够支持使用该标准。这种方法的目的是让现有数据工具生态系统构建出对Apache Iceberg表的支持,并让Iceberg成为引擎识别和处理数据湖表的标准。目标是使Apache Iceberg在生态系统中变得如此普遍,以至于它成为许多用户不必考虑的另一个实现细节。他们只知道他们正在使用表,而无论他们使用哪个工具与表进行交互,他们都不需要考虑这一点。这已经成为现实,因为许多工具让终端用户如此轻松地与Apache Iceberg表一起工作,以至于他们不需要了解底层的Iceberg格式。最终,借助自动化的表优化和摄取工具,即使是数据工程师等更技术性的用户也不必过多考虑底层格式,他们将能够像在处理数据仓库时那样处理他们的数据湖存储,而无需直接处理存储层。
Apache Iceberg架构
Apache Iceberg使用元数据树来跟踪表的分区、排序、模式随时间的变化等,引擎可以利用这些元数据以远远低于传统数据湖模式所需时间的速度来规划查询。图1-7展示了这个元数据树。
这些元数据树将表的元数据分解为四个组件:
- 清单文件(Manifest file):包含每个数据文件的位置/路径以及关键的元数据,允许创建更高效的执行计划。
- 清单列表(Manifest list):定义表的单个快照的文件列表,以及这些清单的统计信息,可用于创建更高效的执行计划。
- 元数据文件(Metadata file):定义表的结构,包括其模式、分区方案和快照清单。
- 目录(Catalog):跟踪表的位置(类似于Hive Metastore),但不是包含表名 -> 目录集合的映射,而是包含表名 -> 表的最新元数据文件位置的映射。多个工具,包括Hive Metastore,都可以用作目录,我们专门在第五章讨论了这个主题。
这些文件的每一个都将在第二章中更详细地介绍。
Apache Iceberg的关键特性
Apache Iceberg的独特架构使其能够实现越来越多的功能,不仅解决了与Hive相关的挑战,而且为数据湖和数据湖仓库的工作负载提供了全新的功能。在本节中,我们提供了Apache Iceberg的关键功能的高级概述。我们将在后面的章节中更深入地讨论这些功能。
ACID事务
Apache Iceberg使用乐观并发控制来实现ACID保证,即使你有多个读取器和写入器处理事务。乐观并发假设事务不会发生冲突,仅在必要时检查冲突,旨在最小化锁定并提高性能。这样,你就可以在数据湖仓库上运行事务,要么提交,要么失败,没有其他情况。悲观并发模型使用锁来防止事务之间的冲突,假设冲突可能发生,在本文撰写时,Apache Iceberg尚不支持这种模型,但可能会在未来推出。
并发保证由目录处理,因为目录通常是具有内置ACID保证的机制。这就是允许对Iceberg表进行原子事务并提供正确性保证的原因。如果没有这一点,两个不同的系统可能会发生冲突的更新,导致数据丢失。
分区演变
在Apache Iceberg出现之前,处理需要更改表的物理优化是一个大问题。太多时候,当你的分区需要更改时,你唯一的选择就是重写整个表,在大规模情况下,这可能非常昂贵。另一种选择是只使用现有的分区方案,牺牲更好的分区方案可能提供的性能改进。
使用Apache Iceberg,你可以随时更新表的分区方式,而无需重写表及其所有数据。由于分区与元数据有关,所以对于对表结构进行此更改所需的操作是快速且便宜的。
图1-8描述了一个最初按月分区,然后向前按日分区演变的表。先前写入的数据保留在月份分区中,而新数据写入到日分区中,在查询中,引擎根据应用于每个分区的分区方案制定计划。
隐藏分区
有时候用户并不知道表是如何物理分区的,实际上,他们也不需要知道。通常,一个表是根据某个时间戳字段进行分区的,而用户希望根据该字段进行查询(例如,获取过去90天的每日平均收入)。对于用户来说,最直观的方法是包含一个筛选条件 event_timestamp >= DATE_SUB(CURRENT_DATE, INTERVAL 90 DAY)。然而,这将导致全表扫描,因为实际上表是根据称为 event_year、event_month 和 event_day 的不同字段进行分区的。这是因为在时间戳上进行分区会导致非常小的分区,因为值通常是以秒、毫秒或更小的粒度存储的。
Apache Iceberg解决了这个问题的方式是如何处理分区。在Iceberg中,分区分为两部分:列,应该基于该列进行物理分区;以及对该值的可选转换,包括bucket、truncate、year、month、day和hour等函数。应用转换的能力消除了为分区创建新列的必要性。这导致更直观的查询受益于分区,因为消费者在查询额外的分区列时不需要添加额外的过滤谓词。
在图1-9中,假设该表使用了按天分区。图中描述的查询在Hive中将导致全表扫描,因为可能为分区创建了另一个“day”列,而在Iceberg中,元数据将跟踪“CURRENT_DATE的转换值”作为分区,因此在按 CURRENT_DATE 进行筛选时将使用分区(我们将在本书后面更详细地讨论这个问题)。
行级表操作
您可以优化表的行级更新模式,采用两种形式之一:写时复制(COW)或读时合并(MOR)。当使用COW时,对于给定数据文件中的任何行的更改,即使其中的单个记录被更新,整个文件也会被重写(在新文件中进行行级更改)。当使用MOR时,对于任何行级更新,只会写入一个新文件,其中包含对受影响行的更改,并在读取时进行了协调。这提供了灵活性,可以加速繁重的更新和删除工作负载。
时间旅行
Apache Iceberg提供了不可变的快照,因此可以访问表在历史状态下的信息,允许您在过去的某个时间点上运行查询,或者通常称为时间旅行。这可以帮助您处理一些情况,例如进行季度末报告,无需将表的数据重复复制到其他位置,或者在某个特定时间点上重现ML模型的输出。如图1-10所示。
版本回滚
Iceberg的快照隔离不仅允许您按原样查询数据,还可以将表的当前状态恢复到任何以前的快照。因此,撤消错误就像回滚一样简单(见图1-11)。
模式演化
表格是会变化的,无论是添加/删除列、重命名列还是更改列的数据类型。无论您的表格需要如何演化,Apache Iceberg都为您提供了强大的模式演化功能,例如,随着列中的值变得更大,将int列更新为long列。
总结
在本章中,您了解到Apache Iceberg是一个数据湖表格格式,旨在改进Hive表格存在不足的许多方面。通过与文件的物理结构解耦以及其多层次的元数据树,Iceberg能够提供Hive事务、ACID保证、模式演化、分区演化以及其他几个功能,从而实现数据湖仓。Apache Iceberg项目通过构建规范和支持库来实现这一目标,让现有的数据工具能够支持这种开放的表格格式。
在第二章中,我们将深入探讨Apache Iceberg的架构,从而使所有这些成为可能。