优化 Delta 表

已完成

Spark 是一个并行处理框架,数据存储在一个或多个工作器节点上。 此外,Parquet 文件是不可变的,每次更新或删除都会写入新文件。 此过程可能会导致 Spark 将数据存储在大量小文件中,这被称为“小文件问题”。这意味着对大量数据的查询可能会运行缓慢,甚至无法完成。

OptimizeWrite 函数

OptimizeWrite 是 Delta Lake 的一项功能,可减少写入文件时的文件数。 它不写入许多小文件,而是写入较少的较大文件。 这有助于防止“小文件问题”并确保性能不会降级。

显示了“优化写入”如何写入更少的大型文件的示意图。

在 Microsoft Fabric 中,OptimizeWrite 会默认启用。 你可以在 Spark 会话级别启用或禁用它:

# Disable Optimize Write at the Spark session level
spark.conf.set("spark.microsoft.delta.optimizeWrite.enabled", False)

# Enable Optimize Write at the Spark session level
spark.conf.set("spark.microsoft.delta.optimizeWrite.enabled", True)

print(spark.conf.get("spark.microsoft.delta.optimizeWrite.enabled"))

注意

还可以在“表属性”中针对每个写入命令设置 OptimizeWrite

优化

优化是一项表维护功能,可将小型 Parquet 文件合并为较少的大型文件。 加载大型表后,你可以运行优化来获得:

  • 较少的较大文件
  • 更好的压缩
  • 跨节点高效数据分布

显示了“优化”如何合并 Parquet 文件的示意图。

若要运行“优化”,请执行以下操作:

  1. 在“湖屋资源管理器”中,选择表名称旁边的 ... 菜单,然后选择“维护”。
  2. 选择“运行优化命令”。
  3. (可选)选择“应用 V 顺序以最大化 Fabric 中的读取速度”。
  4. 选择“立即运行”。

V 顺序函数

运行优化时,你还可以选择运行 V 顺序,这是专为 Fabric 中的 Parquet 文件格式设置的。 V 顺序可实现闪电般快速的读取,具有与内存中数据类似的访问速度。 它还提高了成本效率,因为它减少了读取过程中的网络、磁盘和 CPU 资源。

V 顺序在 Microsoft Fabric 中默认启用,并且在写入数据时应用。 它产生大约 15% 的小开销,使写入速度稍慢。 但是,V 顺序使得可以从 Microsoft Fabric 计算引擎(例如 Power BI、SQL、Spark 等)进行更快的读取。

在 Microsoft Fabric 中,Power BI 和 SQL 引擎使用 Microsoft Verti-Scan 技术,该技术充分利用 V 顺序优化来加快读取速度。 Spark 和其他引擎不使用 VertiScan 技术,但仍然受益于 V 顺序优化,读取速度可提高约 10%,有时甚至高达 50%。

V 顺序的工作原理是对 Parquet 文件应用特殊排序、行组分布、字典编码和压缩。 它 100% 兼容开源 Parquet 格式,所有 Parquet 引擎都可以读取它。

V 顺序对写入密集型场景可能无益,例如其中的数据只会被读取一两次的临时数据存储。 在这些情况下,禁用 V 顺序可能会降低用于数据引入的总体处理时间。

通过运行 OPTIMIZE 命令,使用表维护功能将 V 顺序应用于各个表。

选中了 V 顺序的表维护的屏幕截图

清空

可以使用 VACUUM 命令移除旧的数据文件。

每个更新或删除操作完成后,都会创建一个新的 Parquet 文件,并在事务日志中创建一个条目。 旧的 Parquet 文件将被保留以实现时间旅行,这意味着 Parquet 文件会随着时间的推移而积累。

VACUUM 命令会移除旧的 Parquet 数据文件,但不会移除事务日志。 运行 VACUUM 时,时间旅行不能回溯到保留期之前。

显示了 vacuum 的工作原理的示意图。

运行 VACUUM 将永久删除事务日志中当前未引用且超过指定保留期的数据文件。 根据以下因素选择保留期:

  • 数据保留要求
  • 数据大小和存储成本
  • 数据更改频率
  • 法规要求

默认保留期为 7 天(168 小时),并且系统会阻止使用更短的保留期。

你可以临时运行 VACUUM,也可以使用 Fabric 笔记本进行计划。

使用表维护功能对各个表运行 VACUUM:

  1. 在“湖屋资源管理器”中,选择表名称旁边的 ... 菜单,然后选择“维护”。
  2. 选择“使用保留期阈值运行 VACUUM 命令”并设置保留期阈值。
  3. 选择“立即运行”。

显示了表维护选项的屏幕截图。

还可以在笔记本中以 SQL 命令的形式运行 VACUUM

%%sql
VACUUM lakehouse2.products RETAIN 168 HOURS;

VACUUM 提交到 Delta 事务日志,因此你可以在 DESCRIBE HISTORY 中查看以前的运行。

%%sql
DESCRIBE HISTORY lakehouse2.products;

对 Delta 表进行分区

Delta Lake 允许将数据组织到分区中。 这可以通过实现“数据跳过”来提高性能,数据跳过通过基于对象的元数据跳过不相关的数据对象来提高性能。

请考虑存储大量销售数据的情况。 可以按年份对销售数据进行分区。 分区存储在名为“year=2021”、“year=2022”等的子文件夹中。如果你只想报告 2024 年的销售数据,那么可以跳过其他年份的分区,这可以提高读取性能。

但是,对少量数据进行分区可能会降低性能,因为它会增加文件数,并可能加剧“小文件问题”。

在以下情况下请使用分区:

  • 你的数据量非常大。
  • 表可以拆分为几个大型分区。

在以下情况下不要使用分区:

  • 数据量较小。
  • 分区列具有较高的基数,因为这会创建大量分区。
  • 分区列将导致多个级别。

显示了按一列或多列进行分区的示意图。

分区是固定的数据布局,不能适应不同的查询模式。 考虑如何使用分区时,请考虑数据的使用方式及其粒度。

在此示例中,包含产品数据的 DataFrame 按类别进行分区:

df.write.format("delta").partitionBy("Category").saveAsTable("partitioned_products", path="abfs_path/partitioned_products")

在 Lakehouse 资源管理器中,你可以看到数据是分区表。

  • 该表有一个文件夹,称为“partitioned_products”。
  • 每个类别都有子文件夹,例如“Category=Bike Racks”,等等。

Lakehouse 资源管理器和按类别分区的产品文件的屏幕截图。

我们可以使用 SQL 创建类似的分区表:

%%sql
CREATE TABLE partitioned_products (
    ProductID INTEGER,
    ProductName STRING,
    Category STRING,
    ListPrice DOUBLE
)
PARTITIONED BY (Category);