了解 Direct Lake 语义模型的存储
本文介绍 Direct Lake 存储概念。 它描述 Delta 表和 Parquet 文件。 它还介绍了如何针对 Direct Lake 语义模型优化 Delta 表,以及如何维护这些表来帮助提供可靠、快速的查询性能。
Delta 表
OneLake 中存在增量表。 它们将基于文件的数据组织成行和列,可用于Microsoft Fabric 计算引擎,例如笔记本、Kusto 和 lakehouse 和仓库。 可以使用数据分析表达式(DAX)、多维表达式(MDX)、T-SQL(Transact-SQL)、Spark SQL 甚至 Python 查询 Delta 表。
注意
Delta(或 Delta Lake)是开源存储格式。 这意味着 Fabric 还可以查询其他工具和供应商创建的 Delta 表。
Delta 表将其数据存储在 Parquet 文件中,这些文件通常存储在 Direct Lake 语义模型用于加载数据的 Lakehouse 中。 但是,Parquet 文件也可以存储在外部。 可以使用 OneLake 快捷方式来引用 外部 Parquet 文件,该快捷方式指向特定的存储位置,例如 Azure Data Lake Storage (ADLS) Gen2、Amazon S3 存储帐户或 Dataverse。 几乎所有情况下,计算引擎通过查询 Delta 表来访问 Parquet 文件。 但是,通常 Direct Lake 语义模型使用称为 转码的过程直接从 OneLake 中优化的 Parquet 文件加载列数据。
数据版本控制
增量表包含一个或多个 Parquet 文件。 这些文件附带一组基于 JSON 的链接文件,用于跟踪与 Delta 表关联的每个 Parquet 文件的顺序和性质。
请务必了解基础 Parquet 文件本质上是增量文件。 因此,名称 Delta 作为对增量数据修改的引用。 每次对 Delta 表执行写入操作(例如插入、更新或删除数据时),都会创建表示数据修改为 版本的新 Parquet 文件。 因此 ,Parquet 文件是不可变的,这意味着它们永远不会修改。 因此,可以在 Delta 表的一组 Parquet 文件中多次复制数据。 Delta 框架依赖于链接文件来确定生成正确的查询结果需要哪些物理 Parquet 文件。
请考虑本文用于解释不同数据修改操作的 Delta 表的简单示例。 该表有两列,并存储三行。
ProductID | StockOnHand |
---|---|
A | 1 |
B | 2 |
C | 3 |
Delta 表数据存储在包含所有数据的单个 Parquet 文件中,并且有一个链接文件包含有关插入数据时间(追加)的元数据。
- Parquet 文件 1:
- ProductID:A、B、C
- StockOnHand:1、2、3
- 链接文件 1:
- 包含创建时的
Parquet file 1
时间戳,以及追加数据的记录。
- 包含创建时的
插入操作
考虑插入操作发生时会发生什么情况:插入手头价值4
为库存的产品C
的新行。 此操作会导致创建新的 Parquet 文件和链接文件,因此现在有两个 Parquet 文件和两个链接文件。
- Parquet 文件 1:
- ProductID:A、B、C
- StockOnHand:1、2、3
- Parquet 文件 2:
- ProductID:D
- StockOnHand:4
- 链接文件 1:
- 包含创建时的
Parquet file 1
时间戳,以及追加数据的记录。
- 包含创建时的
- 链接文件 2:
- 包含创建时的
Parquet file 2
时间戳,以及追加数据的记录。
- 包含创建时的
此时,Delta 表的查询将返回以下结果。 结果源自多个 Parquet 文件并不重要。
ProductID | StockOnHand |
---|---|
A | 1 |
B | 2 |
C | 3 |
D | 4 |
每次后续插入操作都会创建新的 Parquet 文件和链接文件。 这意味着每一次插入操作都会增加 Parquet 文件和链接文件的数量。
更新操作
现在,请考虑在更新操作发生时发生的情况:产品 D
行的手上值已更改为 10
。 此操作会导致创建新的 Parquet 文件和链接文件,因此现在有三个 Parquet 文件和三个链接文件。
- Parquet 文件 1:
- ProductID:A、B、C
- StockOnHand:1、2、3
- Parquet 文件 2:
- ProductID:D
- StockOnHand:4
- Parquet 文件 3:
- ProductID:C
- StockOnHand:10
- 链接文件 1:
- 包含创建时的
Parquet file 1
时间戳,以及追加数据的记录。
- 包含创建时的
- 链接文件 2:
- 包含创建时的
Parquet file 2
时间戳,以及追加数据的记录。
- 包含创建时的
- 链接文件 3:
- 包含创建时的
Parquet file 3
时间戳,以及更新数据的记录。
- 包含创建时的
此时,Delta 表的查询将返回以下结果。
ProductID | StockOnHand |
---|---|
A | 1 |
B | 2 |
C | 10 |
D | 4 |
产品 C
的数据现在存在于多个 Parquet 文件中。 但是,对 Delta 表的查询将链接文件组合在一起,以确定应使用哪些数据来提供正确的结果。
删除操作
现在,请考虑在发生删除操作时发生的情况:删除产品的 B
行。 此操作会导致新的 Parquet 文件和链接文件,因此现在有四个 Parquet 文件和四个链接文件。
- Parquet 文件 1:
- ProductID:A、B、C
- StockOnHand:1、2、3
- Parquet 文件 2:
- ProductID:D
- StockOnHand:4
- Parquet 文件 3:
- ProductID:C
- StockOnHand:10
- Parquet 文件 4:
- ProductID:A、C、D
- StockOnHand:1、10、4
- 链接文件 1:
- 包含创建时的
Parquet file 1
时间戳,以及追加数据的记录。
- 包含创建时的
- 链接文件 2:
- 包含创建时的
Parquet file 2
时间戳,以及追加数据的记录。
- 包含创建时的
- 链接文件 3:
- 包含创建时的
Parquet file 3
时间戳,以及更新数据的记录。
- 包含创建时的
- 链接文件 4:
- 包含创建时的
Parquet file 4
时间戳,以及删除数据的记录。
- 包含创建时的
请注意, Parquet file 4
不再包含产品 B
的数据,但它确实包含表中所有其他行的数据。
此时,Delta 表的查询将返回以下结果。
ProductID | StockOnHand |
---|---|
A | 1 |
C | 10 |
D | 4 |
注意
此示例很简单,因为它涉及一个小表,只涉及一些操作,并且只涉及少量修改。 遇到许多写入操作且包含多行数据的大型表将为每个版本生成多个 Parquet 文件。
重要
根据定义 Delta 表的方式和数据修改操作的频率,可能会导致许多 Parquet 文件。 请注意,每个 Fabric 容量许可证都有 防护措施。 如果 Delta 表的 Parquet 文件数超过 SKU 的限制,查询将 回退到 DirectQuery,这可能会导致查询性能降低。
若要管理 Parquet 文件的数量,请参阅 本文后面的 Delta 表维护 。
Delta 按时间顺序查看
链接文件支持从早期时间点开始查询数据。 此功能称为 增量时间旅行。 以前的时间点可以是时间戳或版本。
请考虑以下查询示例。
SELECT * FROM Inventory TIMESTAMP AS OF '2024-04-28T09:15:00.000Z';
SELECT * FROM Inventory AS OF VERSION 2;
提示
还可以使用 @
简写语法将时间戳或版本指定为表名称的一部分来查询表。 时间戳必须采用 yyyyMMddHHmmssSSS
格式。 你可以通过在版本前附加一个 v
在 @
后指定版本。
下面是使用速记语法重写的前面的查询示例。
SELECT * FROM Inventory@20240428091500000;
SELECT * FROM Inventory@v2;
重要
可通过时间旅行访问的表版本由事务日志文件的保留阈值和 VACUUM 操作的频率和指定的保留(稍后在 Delta 表维护 部分中介绍)的组合决定。 如果使用默认值每天运行 VACUUM,则 7 天的数据将可用于时间旅行。
框架
框架 是一种 Direct Lake 操作,用于设置 Delta 表的版本,该表应用于将数据加载到语义模型列中。 同样重要的是,版本还确定加载数据时应排除的内容。
框架操作将每个 Delta 表的时间戳/版本标记到语义模型表中。 从这一点开始,当语义模型需要从 Delta 表加载数据时,与最新框架操作关联的时间戳/版本用于确定要加载的数据。 由于将忽略最新的帧操作(直到下一个帧操作),Delta 表发生的任何后续数据修改。
重要
由于框架语义模型引用特定的 Delta 表版本,因此源必须确保它保持 Delta 表版本,直到新版本的框架完成。 否则,当需要模型访问 Delta 表文件时,用户会遇到错误,并且已被生成者工作负荷清空或删除。
有关框架的详细信息,请参阅 Direct Lake 概述。
表分区
可以对增量表进行分区,以便将行的子集一起存储在一组 Parquet 文件中。 分区可以加快查询速度以及写入操作。
考虑一个 Delta 表,该表在两年内具有十亿行的销售数据。 尽管可以将所有数据存储在一组 Parquet 文件中,但对于此数据卷,读取和写入操作并非最佳。 相反,可以通过跨多个 Parquet 文件系列分布数十亿行数据来提高性能。
设置表分区时必须定义分区键。 分区键确定要存储在哪个序列中的行。 对于 Delta 表,可以根据指定列(或列)的不同值(如日期表的月/年列)定义分区键。 在这种情况下,两年的数据将分布在 24 个分区(2 年 x 12 个月)。
构造计算引擎不知道表分区。 插入新的分区键值时,会自动创建新分区。 在 OneLake 中,你将为每个唯一分区键值找到一个子文件夹,每个子文件夹存储其自己的 Parquet 文件和链接文件集。 必须至少存在一个 Parquet 文件和一个链接文件,但每个子文件夹中的实际文件数可能会有所不同。 随着数据修改操作的进行,每个分区维护其自己的 Parquet 文件和链接文件集,以跟踪给定时间戳或版本返回的内容。
如果将已分区 Delta 表的查询筛选为最近三个月的销售数据,则可以快速识别需要访问的 Parquet 文件和链接文件的子集。 这样就可以完全跳过许多 Parquet 文件,从而提高读取性能。
但是,不筛选分区键的查询可能并不总是性能更好。 当 Delta 表将所有数据存储在单个大型 Parquet 文件中,并且存在文件或行组碎片时,就会出现这种情况。 尽管可以在多个群集节点之间并行化从多个 Parquet 文件进行数据检索,但许多小型 Parquet 文件可能会对文件 I/O 产生不利影响,因此查询性能会受到影响。 因此,最好避免在大多数情况下对 Delta 表进行分区,除非写入操作或提取、转换和加载(ETL)进程会明显受益于它。
分区也有利于插入、更新和删除操作,因为文件活动仅在与修改或删除行的分区键匹配的子文件夹中发生。 例如,如果将一批数据插入到分区的 Delta 表中,则会评估数据以确定批处理中存在哪些分区键值。 然后,数据仅定向到分区的相关文件夹。
了解 Delta 表使用分区的方式有助于设计最佳的 ETL 方案,以减少更新大型 Delta 表时所需的写入操作。 写入性能通过减少必须创建的任何新 Parquet 文件的数量和大小来提高。 对于按月/年分区的大型 Delta 表,如上一示例中所述,新数据只会将新的 Parquet 文件添加到最新分区。 以前的日历月的子文件夹保持不变。 如果必须修改以前的日历月的任何数据,则只有相关分区文件夹接收新的分区和链接文件。
重要
如果 Delta 表的主要用途是用作语义模型(其次,其他查询工作负荷)的数据源,则通常最好避免将列加载优化 到内存中的分区。
对于 Direct Lake 语义模型或 SQL 分析终结点,优化 Delta 表分区的最佳方式是让 Fabric 自动管理 Delta 表的每个版本的 Parquet 文件。 将管理留给 Fabric 应该通过并行化产生较高的查询性能,但它可能不一定提供最佳的写入性能。
如果必须针对写入操作进行优化,请考虑使用分区根据分区键优化对 Delta 表的写入操作。 但是,请注意,对 Delta 表进行分区可能会对读取性能产生负面影响。 出于此原因,我们建议你仔细测试读取和写入性能,也许通过创建具有不同配置的同一 Delta 表的多个副本来比较计时。
警告
如果在高基数列上分区,则可能会导致 Parquet 文件过多。 请注意,每个 Fabric 容量许可证都有 防护措施。 如果 Delta 表的 Parquet 文件数超过 SKU 的限制,查询将 回退到 DirectQuery,这可能会导致查询性能降低。
Parquet 文件
Delta 表的基础存储是一个或多个 Parquet 文件。 Parquet 文件格式通常用于 写入一次读取多 应用程序。 每次通过插入、更新或删除操作修改 Delta 表中的数据时,都会创建新的 Parquet 文件。
注意
可以使用工具(如 OneLake 文件资源管理器)访问与 Delta 表关联的 Parquet 文件。 文件可以像移动任何其他文件一样轻松地下载、复制或移动到其他目标。 但是,它是 Parquet 文件和基于 JSON 的链接文件的组合,这些链接文件允许计算引擎以 Delta 表的形式针对文件发出查询。
Parquet 文件格式
Parquet 文件的内部格式不同于其他常见数据存储格式,例如 CSV、TSV、XMLA 和 JSON。 这些格式按行组织数据,而 Parquet 按列组织数据。 此外,Parquet 文件格式与这些格式不同,因为它将数据行组织成一个或多个 行组。
Power BI 语义模型的内部数据结构基于列,这意味着 Parquet 文件与 Power BI 共享了很多共同点。 这种相似性意味着 Direct Lake 语义模型可以有效地将数据从 Parquet 文件直接加载到内存中。 事实上,可以在数秒内加载非常大的数据量。 将此功能与必须检索块或源数据的导入语义模型的刷新进行对比,然后处理、编码、存储,然后将其加载到内存中。 导入语义模型刷新操作还可以消耗大量的计算(内存和 CPU),并花费相当长的时间来完成。 但是,使用 Delta 表,大多数准备适合直接加载到语义模型的数据时,都会生成 Parquet 文件。
Parquet 文件存储数据的方式
请考虑以下示例数据集。
日期 | ProductID | StockOnHand |
---|---|---|
2024-09-16 | A | 10 |
2024-09-16 | B | 11 |
2024-09-17 | A | 13 |
… |
以 Parquet 文件格式存储时,从概念上讲,此数据集可能类似于以下文本。
Header:
RowGroup1:
Date: 2024-09-16, 2024-09-16, 2024-09-17…
ProductID: A, B, A…
StockOnHand: 10, 11, 13…
RowGroup2:
…
Footer:
数据通过替换通用值的字典键和应用 运行长度编码(RLE)来压缩数据。 RLE 努力将一系列相同的值压缩为较小的表示形式。 在以下示例中,在标头中创建数值键到值的字典映射,并且使用较小的键值代替数据值。
Header:
Dictionary: [
(1, 2024-09-16), (2, 2024-09-17),
(3, A), (4, B),
(5, 10), (6, 11), (7, 13)
…
]
RowGroup1:
Date: 1, 1, 2…
ProductID: 3, 4, 3…
StockOnHand: 5, 6, 7…
Footer:
当 Direct Lake 语义模型需要数据来计算按分组ProductID
的StockOnHand
列的总和时,只需要与这两列关联的字典和数据。 在包含许多列的大型文件中,可以跳过 Parquet 文件的大量部分,以帮助加快读取过程。
注意
Parquet 文件的内容不可读,因此它不适合在文本编辑器中打开。 但是,有许多开放源代码工具可以打开和显示 Parquet 文件的内容。 这些工具还可以让你检查元数据,例如文件中包含的行数和行组数。
V 顺序
Fabric 支持名为 V-Order 的其他优化。 V 顺序是 Parquet 文件格式的写入时优化。 应用 V 顺序后,它会生成更小、更快速的文件进行读取。 此优化尤其适用于 Direct Lake 语义模型,因为它为快速加载内存准备数据,因此对容量资源的需求更少。 它还会导致查询性能更快,因为需要扫描的内存更少。
由 Fabric 项(如 数据管道、 数据流和 笔记本 )创建和加载的增量表会自动应用 V 顺序。 但是,上传到 Fabric Lakehouse 或快捷方式引用的 Parquet 文件可能未应用此优化。 虽然仍可以读取非优化的 Parquet 文件,但读取性能可能不如应用了 V 顺序的等效 Parquet 文件那么快。
注意
应用了 V 顺序的 Parquet 文件仍符合开源 Parquet 文件格式。 因此,非 Fabric 工具可以读取它们。
有关详细信息,请参阅 Delta Lake 表优化和 V-Order。
增量表优化
本部分介绍用于优化语义模型的 Delta 表的各种主题。
数据量
虽然 Delta 表可以增长以存储非常大的数据, 但 Fabric 容量防护措施 会对查询它们的语义模型施加限制。 如果超出这些限制,查询将 回退到 DirectQuery,这可能会导致查询性能变慢。
因此,请考虑通过提高其粒度(存储汇总的数据)、减少维度或存储较少的历史记录来限制大型 事实数据表 的行计数。
此外,请确保 应用 V 顺序 ,因为它会导致文件更小且更快进行读取。
列数据类型
努力减少每个 Delta 表的每一列中基数(唯一值的数目)。 这是因为所有列都是使用 哈希编码压缩和存储的。 哈希编码需要 V 顺序优化才能将数字标识符分配给列中包含的每个唯一值。 它是存储的数字标识符,在存储和查询期间需要哈希查找。
使用 近似数值数据类型 (如 float 和 real),请考虑舍入值并使用较低的精度。
不必要的列
与任何数据表一样,Delta 表应仅存储所需的列。 在本文的上下文中,这意味着语义模型需要,但可能有其他分析工作负载查询 Delta 表。
除了支持模型关系的列外,增量表还应包括语义模型所需的列,用于筛选、分组、排序和汇总。 虽然不必要的列不会影响语义模型查询性能(因为它们不会加载到内存中),但它们会导致更大的存储大小,因此需要更多的计算资源来加载和维护。
由于 Direct Lake 语义模型不支持计算列,因此应在 Delta 表中具体化此类列。 请注意,此设计方法是导入和 DirectQuery 语义模型的反模式。 例如,如果你有 FirstName
列和 LastName
列,并且需要一列 FullName
,则向 Delta 表中插入行时具体化此列的值。
请考虑某些语义模型摘要可能依赖于多个列。 例如,若要计算销售额,模型中的度量值对两列的乘积求和Price
: Quantity
如果这两列都不单独使用,那么将销售计算具体化为单个列比将其组件值存储在单独的列中更有效。
行组大小
在内部,Parquet 文件将数据行组织到每个文件中的多个行组中。 例如,包含 30,000 行的 Parquet 文件可能会将它们分为三行组,每个行包含 10,000 行。
行组中的行数会影响 Direct Lake 读取数据的速度。 由于 I/O 过多,具有较少行的行组数可能会对将列数据加载到语义模型中产生负面影响。
通常,不建议更改默认行组大小。 但是,可以考虑更改大型 Delta 表的行组大小。 请务必仔细测试读取和写入性能,也许通过创建具有不同配置的同一 Delta 表的多个副本来比较计时。
重要
请注意,每个 Fabric 容量许可证都有 防护措施。 如果 Delta 表的行组数超过 SKU 的限制,查询将 回退到 DirectQuery,这可能会导致查询性能降低。
Delta 表维护
随着时间的推移,随着写入操作的发生,Delta 表版本会累积。 最终,你可能会达到对读取性能产生负面影响的明显点。 更糟的是,如果每个表的 Parquet 文件数或每个表的行组数超过 容量的防护措施,查询将 回退到 DirectQuery,这可能会导致查询性能降低。 因此,请务必定期维护 Delta 表。
OPTIMIZE
可以使用 OPTIMIZE 优化 Delta 表,将较小的文件合并为较大的文件。 还可以将 WHERE
子句设置为仅针对与给定分区谓词匹配的筛选行子集。 仅支持涉及分区键的筛选器。 该 OPTIMIZE
命令还可以应用 V 顺序来压缩和重写 Parquet 文件。
建议定期对大型经常更新的 Delta 表运行此命令,也许在 ETL 过程完成时每天都会运行此命令。 平衡更好的查询性能与优化表所需的资源使用成本之间的权衡。
VACUUM
可以使用 VACUUM 删除不再引用的文件和/或早于设置的保留阈值。 请注意设置适当的保留期,否则可能会失去时间回到早于标记到语义模型表中的帧的版本的能力。
重要
由于框架语义模型引用特定的 Delta 表版本,因此源必须确保它保持 Delta 表版本,直到新版本的框架完成。 否则,当需要模型访问 Delta 表文件时,用户会遇到错误,并且已被生成者工作负荷清空或删除。
REORG TABLE
可以使用 REORG TABLE 重新组织 Delta 表,方法是重写文件以清除软删除的数据,例如在使用 ALTER TABLE DROP COLUMN 删除列时。
自动执行表维护
若要自动执行表维护操作,可以使用 Lakehouse API。 有关详细信息,请参阅 使用 Microsoft Fabric REST API 管理 Lakehouse。
提示
还可以在 Fabric 门户中使用 Lakehouse 表维护功能 来简化 Delta 表的管理。