性能优化

最近更新时间: 2026-03-13 09:03:00

Apache Iceberg 是一种表格格式,旨在简化数据湖管理并增强工作负载性能。不同的用例可能会优先考虑不同的方面,例如成本、读取性能、写入性能或数据保留,因此 Iceberg 提供了配置选项来管理这些权衡。本指南为优化和微调 Iceberg 工作负载以满足您的需求提供了深入的见解。

主题

  • 一般最佳实践
  • 优化读取性能
  • 优化写入性能
  • 优化存储
  • 数据湖表维护

一般最佳实践

无论您的用例如何,当您在 TBDS 上使用 Apache Iceberg 时,我们建议您遵循以下一般的最佳实践。

使用 Iceberg 格式版本 2

默认情况下,TBDS 使用 Iceberg 格式版本 2(Iceberg Format Version 2)。当您在 TBDS 上使用 Spark 创建Iceberg 表时,请按照 Iceberg 文档中所述指定格式版本。

使用 Zstandard (ZSTD) 压缩

Iceberg 的默认压缩编解码器是 gzip(GZIP),可以通过表属性进行修改。我们建议使用 ZSTD 压缩编解码器,因为它在 GZIP 和 Snappy 之间取得了平衡,并且在不影响压缩比的情况下提供了良好的读写性能。此外,ZSTD 允许根据需要调整压缩级别。
Snappy 可能提供最佳的整体读取和写入性能,但其压缩率低于 GZIP 和 ZSTD。如果您优先考虑性能,即使这意味着在存储集群中存储更大的数据量,Snappy 也是一个不错的选择。

优化读取性能

本节讨论了可以独立于引擎调整的表属性,以优化读取性能。

分区

与 Hive 表类似,Iceberg 使用分区作为索引的主要层,以避免读取不必要的元数据文件和数据文件。列统计数据也被视为索引的辅助层,以进一步改进查询计划,从而缩短总体执行时间。

对您的数据进行分区

要减少查询 Iceberg 表时扫描的数据量,请选择与预期读取模式一致的平衡分区策略:

  • 确定查询中经常使用的列:这些是理想的分区候选对象。例如,如果您通常查询特定日期的数据,分区列的自然示例就是日期列。
  • 选择低基数分区列:避免创建过多的分区。过多的分区会增加表中的文件数量,从而对查询性能产生负面影响。根据经验,"分区过多"可以定义为大多数分区中的数据大小小于设定值的 2-5 倍的情况。
    注意:如果您通常通过对高基数列(例如,可以包含数千个值的列)使用过滤器进行查询,请使用带有存储桶转换的 Iceberg 隐藏分区功能,如下一节所述。

使用隐藏分区

如果您的查询通常根据表列的派生项进行筛选,请使用隐藏分区,而不是显式创建新列作为分区。隐藏分区允许您通过转换函数即时创建分区,从而避免在表中增加额外的列。
例如,在具有时间戳列的数据集中(例如,2023-01-01 09:00:00),可以使用分区转换从时间戳中提取日期部分并即时创建这些分区,而不是使用解析后的日期创建新列(例如,2023-01-01)。
隐藏分区最常见的用例包括:

  • 当数据包含时间戳列时,按日期或时间进行分区。Iceberg 提供多种变换来提取时间戳的日期或时间部分。
  • 对高基数列进行分区时,使用 Iceberg 的存储桶转换,通过在分区列上使用哈希函数,将多个分区值组合成更少的隐藏(存储桶)分区。
    有关所有可用分区转换的概述,请参阅 Iceberg 文档中的分区转换部分。

使用分区演变

当现有的分区策略不是最佳时,请使用 Iceberg 的分区演变(partition evolution)。例如,如果您选择的每小时分区结果太小(每个分区只有几兆字节),可以考虑切换到每日或每月分区。
分区演变的另一个有效用途是,当数据量发生变化并且当前的分区策略随着时间的推移而变得不那么有效时。
有关如何演变分区的说明,请参阅 Iceberg 文档中的 ALTER TABLE SQL 扩展。

调整文件大小

优化查询性能包括最大限度地减少表中的小文件数量。为了获得良好的查询性能,我们通常建议保留大于 100 MB 的 Parquet 和 ORC 文件。
文件大小也会影响 Iceberg 表的查询计划。随着表中文件数量的增加,元数据文件的大小也随之增加。较大的元数据文件可能会导致查询计划变慢。因此,当表大小增加时,请增加文件大小以控制元数据的指数级扩展。

设置目标文件和行组的大小

Iceberg 提供了以下用于调整数据文件布局的关键配置参数。我们建议使用这些参数来设置目标文件大小和行组(row group)或行标(stripe)大小。

参数 默认值 评论
write.target-filesize-bytes 512MB 该参数指定 Iceberg 将创建的最大文件大小。但是,写入某些文件的大小可能小于此限制。
write.parquet.rowgroup-size-bytes 128MB Parquet 和 ORC 都将数据分块存储,因此引擎可以避免在某些操作中读取整个文件。
write.orc.stripe-size-bytes 64MB
write.distribution-mode 无,适用于 Iceberg 1.1 及更低版本 Iceberg 要求 Spark 在写入存储之前在其任务之间对数据进行排序。

根据表格大小调整参数

根据您的预期表格大小,请遵循以下一般准则:

  • 小表(最多几千兆字节):将目标文件大小减至 128 MB。还可以减小行组或条带大小(例如,减至 8 MB 或 16 MB)。
  • 中型到大型表(从几千兆字节到几百千兆字节不等):默认值是这些表的良好起点。如果查询选择性很强,请调整行组或条带大小(例如,调整到 16 MB)。
  • 非常大的表(数百 GB 或 TB):将目标文件大小增加到 1024 MB 或更多。如果查询通常会提取大量数据,请考虑增加行组或条带大小。
    为确保写入 Iceberg 表的 Spark 应用程序创建大小合适的文件,请将 write.distribution-mode 属性设置为hash 或 range。有关这些模式之间差异的详细说明,请参阅 Iceberg 文档中的写入分发模式部分。
    这些是一般准则。我们建议您运行测试以确定最适合您的特定表和工作负载的值。

定期进行维护

上述配置设置了写入任务可以创建的最大文件大小,但不能保证文件会达到该大小。为确保文件大小合适,请定期运行维护,将小文件合并成较大的文件。有关运行维护的详细指导,请参阅本指南后面的 Iceberg 维护部分。

优化写入性能

本节讨论了可以调整的表属性,以优化 Iceberg 表的写入性能,独立于引擎。

设置表格分配模式

Iceberg 提供多种写入分配模式,这些模式定义了写入数据在 Spark 任务中的分布方式。有关可用模式的概述,请参阅 Iceberg 文档中的写入分发模式部分。
对于优先考虑写入速度的用例,尤其是在流式工作负载中,建议将 write.distribution-mode 设置为 none。这样可以确保 Iceberg 不会请求额外的 Spark 洗牌(shuffle),并且可以在 Spark 任务中可用时写入数据。
注意:将写入分配模式设置为 none 往往会生成大量小文件,从而降低读取性能。我们建议定期进行压缩,将这些小文件整合到大小合适的文件中,以提高查询性能。

选择正确的更新策略

如果您的用例可以接受较慢的写入操作,可以使用 copy-on-write 策略来优化读取性能。这是 Iceberg 的默认策略。
Copy-on-write 可以提高读取性能,因为文件是以读取优化的方式直接写入存储的。但是,与 merge-on-read 相比,每次写入操作花费的时间更长,消耗的计算资源也更多。这在读取和写入延迟之间进行了典型的权衡。通常,copy-on-write 非常适合大多数更新并集中在同一个表分区中的用例(例如,用于每日批量加载)。
Copy-on-write 配置(write.update.mode、write.delete.mode 和 write.merge.mode)可以在表级别进行设置,也可以在应用程序端独立设置。

使用 Merge-on-read 策略

当您的用例可以接受对最新数据执行较慢的读取操作时,请使用 merge-on-read 策略来优化写入性能。在使用merge-on-read 策略时,Iceberg 会将更新和删除内容作为单独的小文件写入存储器。读取表格时,读取器必须将这些更改与基础文件合并,以返回最新的数据视图。这会导致读取操作的性能下降,但会加快更新和删除的写入速度。通常,merge-on-read 适用于具有更新的流式处理工作负载或更新较少且分布在多个表分区中的任务。
使用 merge-on-read 需要定期进行维护,以防止读取性能随着时间的推移而下降。 数据湖维护可将最新和删除与现有数据文件进行协调,以创建一组新的数据文件,从而消除读取性能的损失。

选择正确的文件格式

Iceberg 支持以 Parquet、ORC 和 Avro 格式写入数据。默认格式是 Parquet(Parquet)。Parquet 和 ORC 是列式格式,提供卓越的读取性能,但通常写入速度较慢。这代表了读取和写入性能之间的典型权衡。
如果写入速度对您的用例很重要,例如在流媒体工作负载中,建议在编写器的选项中设置为 Avro 格式,以Avro 格式写入。由于 Avro 是一种基于行的格式,因此它以较慢的读取性能为代价提供更快的写入速度。
要提高读取性能,请定期进行数据湖维护,将小 Avro 文件合并并转换为较大的 Parquet 文件。维护过程的结果取决于 write.format.default 表格设置。Iceberg 的默认格式是 Parquet,因此,如果您用 Avro 编写然后运行维护,Iceberg 会将 Avro 文件转换为 Parquet 文件。

示例配置

CREATE TABLE IF NOT EXISTS glue_catalog.<DB_NAME>.<TABLE_NAME> (
  Col_1 float,
  -- 其他列 ...
  ts timestamp
)
USING iceberg
PARTITIONED BY (days(ts))
OPTIONS (
  'format-version'='2',
  'write.format.default'='parquet'
)
query = df \
  .writeStream \
  .format("iceberg") \
  .option("write-format", "avro") \
  .outputMode("append") \
  .trigger(processingTime='60 seconds') \
  .option("path", f"glue_catalog.{DB_NAME}.{TABLE_NAME}") \
  .option("checkpointLocation", f"s3://{BUCKET_NAME}/checkpoints/iceberg/") \
  .start()

优化存储

更新或删除 Iceberg 表中的数据会增加数据的副本数量,如下图所示。运行压缩也是如此:它增加了存储中的数据副本数量。这是因为 Iceberg 将所有表的底层文件视为不可变的。
按照本节中的最佳实践来管理存储成本。

存档或删除历史快照

对于向 Iceberg 表提交的每笔事务(插入、更新、合并、压缩),都会创建该表的新版本或快照。随着时间的推移,存储集群中的版本数量和元数据文件数量会不断增加。
快照隔离、表回滚和时空旅行查询等功能需要保留表的快照。但是,存储成本会随着您保留的版本数量的增加而增加。

设计模式

解决方案 使用案例
删除旧快照 使用 TBDS 中的 Amoro 删除旧的快照。这种方法会删除不再需要的快照以降低存储成本。您可以根据数据保留要求配置应保留多少快照或保留多长时间。
设置保留策略 在 Iceberg 中使用标签标记特定的快照并定义保留策略。例如,您可以每月保留一个快照,为期一年,然后在 TBDS 上删除剩余的未加标签的中间快照。这有助于遵守业务或法律要求,同时降低存储成本。

删除孤立文件

在某些情况下,Iceberg 应用程序可能会在提交事务之前失败。这会将数据文件留在存储集群中。由于没有提交,这些文件不会与任何表相关联,因此需要异步清理。
您可以在 TBDS 上使用 Spark 运行 remove_orphan_files 程序。此操作会产生计算成本,并且必须单独进行计划。

数据湖表维护

Iceberg 包含的功能允许您在向表写入数据后执行表维护操作。一些维护操作侧重于优化元数据文件,而另一些维护操作则增强了数据在文件中的聚集方式,以便查询引擎可以有效地找到响应用户请求所需的信息。本节重点介绍与压缩相关的优化。

Iceberg 表维护

在 TBDS 中,您可以使用 Amoro 来执行以下任务:

  • 将小文件合并成大文件:通常超过 100 MB。这种技术被称为文件重写。
  • 将删除文件与数据文件合并:删除文件由使用 merge-on-read 方法的更新或删除生成。
  • (重新)根据查询模式对数据进行排序:可以在没有任何排序顺序的情况下写入数据,也可以使用适合写入和更新的排序顺序写入数据。
  • 使用空间填充曲线对数据进行聚类:优化不同的查询模式,尤其是 Z 顺序排序。

调整压缩行为

以下关键属性控制 Amoro 如何选择数据文件进行压缩:

属性 默认值 描述
self-optimizing.enabled true 是否启用自动压缩优化
self-optimizing.target-size 134217728(128MB) 压缩后目标文件大小
self-optimizing.max-file-count 10000 单次压缩处理的最大文件数量
self-optimizing.fragment-ratio 8 碎片文件大小阈值比例。目标大小除以此比例得到实际碎片文件大小阈值
self-optimizing.minor.trigger.file-count 12 触发小型压缩的最小文件数量(碎片文件数量和等值删除文件数量之和)
self-optimizing.minor.trigger.interval 3600000(1小时) 触发小型压缩的时间间隔(毫秒)
self-optimizing.major.trigger.duplicate-ratio 0.1 触发大型压缩的段文件重复数据比例
self-optimizing.min-target-size-ratio 0.75 小于目标大小的段文件阈值比例。低于此阈值的段文件将被考虑重写
self-optimizing.quota 0.1 压缩可使用的 CPU 资源配额
self-optimizing.execute.num-retries 5 压缩失败后的重试次数

调整 Spark 集群大小以运行压缩

以下示例使用 TBDS Spark 引擎

示例 Spark 配置

spark.dynamicAllocation.enabled = FALSE
spark.executor.memory = 20 GB
spark.executor.instances = 5
要加快压缩速度,可以通过增加并行压缩的文件组数量来横向扩展。例如:

  • 手动缩放(例如,按系数 4)
  • 设置 MAX_CONCURRENT_FILE_GROUP_REWRITES=4
  • 设置 spark.executor.instances=20
  • 设置 spark.dynamicAllocation.enabled = FALSE
    或者,使用动态缩放:
  • 设置 spark.dynamicAllocation.enabled=TRUE

数据湖维护的建议

使用案例 建议
按计划运行维护 可以使用 TBDS 内置的 Amoro 组件按计划执行数据湖维护
压缩大量小文件 如果您希望压缩大量小文件,请使用 TBDS Spark 引擎执行
对数据进行排序的维护 使用 TBDS Spark 引擎执行,因为排序是一项昂贵的操作,可能需要将数据重新读写。
使用 Z 顺序排序对数据进行聚类的维护 使用 TBDS Spark 引擎执行,因为 Z 顺序排序是一项非常昂贵的操作,可能需要将数据泄漏到磁盘。

总结

通过遵循本指南中的最佳实践,您可以有效地优化 Apache Iceberg 在 TBDS 上的工作负载,提升性能并管理存储成本。无论是优化读取性能、写入性能还是存储管理,Iceberg 提供了灵活的配置选项来满足不同的业务需求。定期进行维护操作,确保您的数据湖保持高效和可扩展。