

# 优化数据
<a name="performance-tuning-data-optimization-techniques"></a>

性能不仅取决于查询，还取决于数据集的组织方式及其使用的文件格式和压缩。

## 对您的数据进行分区
<a name="performance-tuning-partition-your-data"></a>

分区可将您的表格分成多个部分，并根据日期、国家或地区等属性将相关数据保存在一起。分区键充当虚拟列。您可以在创建表时定义分区键，并使用它们来筛选查询。对分区键列进行筛选时，只读取来自匹配分区的数据。例如，如果您的数据集按日期分区，并且您的查询的筛选条件仅与上周匹配，则只读取上周的数据。有关分区的更多信息，请参阅 [对您的数据进行分区](partitions.md)。

## 选择支持您的查询的分区键
<a name="performance-tuning-pick-partition-keys-that-will-support-your-queries"></a>

由于分区会对查询性能产生重大影响，因此在设计数据集和表时，请务必考虑如何谨慎分区。分区键过多会导致数据集碎片化，文件过多或过小。相反，分区键太少或根本没有分区会导致查询扫描的数据超出必要的范围。

### 避免针对罕见的查询进行优化
<a name="performance-tuning-avoid-optimizing-for-rare-queries"></a>

一个好的策略是针对最常见的查询进行优化，避免针对罕见的查询进行优化。例如，如果您的查询以天为单位的时间跨度，即使某些查询筛选到该级别，也不要按小时进行分区。如果您的数据具有精细的时间戳列，则按小时筛选的罕见查询可以使用时间戳列。即使罕见案例扫描的数据比必要的要多一点，但为了极少数情况而降低整体性能通常也不是一个很好的权衡方案。

要减少查询必须扫描的数据量，从而提高性能，请使用列式文件格式并对记录进行排序。与其按小时分区，不如按时间戳对记录进行排序。对于时间范围较短的查询，按时间戳排序几乎与按小时分区一样有效。此外，按时间戳排序通常不会损害以天为单位的时间窗口的查询性能。有关更多信息，请参阅 [使用列式文件格式](#performance-tuning-use-columnar-file-formats)。

请注意，如果所有分区键上都有谓词，则对包含数万个分区的表进行查询的性能会更好。这是为最常见的查询设计分区方案的另一个原因。有关更多信息，请参阅 [按等式查询分区](#performance-tuning-query-partitions-by-equality)。

## 使用分区投影
<a name="performance-tuning-use-partition-projection"></a>

分区投影是 Athena 的一项功能，它不是将分区信息存储在 AWS Glue Data Catalog 中，而是作为规则存储在 AWS Glue 中表的属性中。当 Athena 计划对配置了分区投影的表进行查询时，它会读取该表的分区投影规则。Athena 根据查询和规则计算要在内存中读取的分区，而不是在 AWS Glue Data Catalog 中查找分区。

除了简化分区管理外，分区投影还可以提高具有大量分区的数据集的性能。当查询包含范围而不是分区键的特定值时，分区越多，在目录中查找匹配的分区所需的时间就越长。使用分区投影，无需进入目录即可在内存中计算筛选器，而且速度可以快得多。

在某些情况下，分区投影可能会导致性能降低。例如，当表为“稀疏表”时。稀疏表不包含分区投影配置所描述的分区键值的每个排列的数据。对于稀疏表，通过查询计算得出的分区集和分区投影配置都会在 Amazon S3 上列出，即使它们没有数据。

使用分区投影时，请确保在所有分区键上都包含谓词。缩小可能值的范围，以避免不必要的 Amazon S3 列表。假设一个分区键的范围为一百万个值，而一个查询对该分区键没有任何筛选器。要运行查询，Athena 必须执行至少一百万个 Amazon S3 列表操作。无论您是使用分区投影还是在目录中存储分区信息，查询特定值时查询速度最快。有关更多信息，请参阅 [按等式查询分区](#performance-tuning-query-partitions-by-equality)。

在为分区投影配置表时，请确保您指定的范围合理。如果查询不包含分区键的谓词，则使用该键范围内的所有值。如果您的数据集是在特定日期创建的，请使用该日期作为任何日期范围的起点。使用 `NOW` 作为日期范围的结束日期。避免使用具有大量值的数值范围，并考虑改用[注入](partition-projection-dynamic-id-partitioning.md#partition-projection-injection)类型。

更多有关分区投影的信息，请参阅 [将分区投影与 Amazon Athena 结合使用](partition-projection.md)。

## 使用分区索引
<a name="performance-tuning-use-partition-indexes"></a>

分区索引是 AWS Glue Data Catalog 中的一项功能，它可以提高具有大量分区的表的分区查找性能。

目录中的分区列表就像关系数据库中的表。该表包含用于分区键的列，还有一列用于分区位置。查询分区表时，将通过扫描该表来查找分区位置。

与关系数据库一样，您可以通过添加索引来提高查询性能。您可以添加多个索引以支持不同的查询模式。AWS Glue Data Catalog 分区索引同时支持等式和比较运算符，例如 `>`、`>=` 和 `<`，并与 `AND` 运算符组合使用。有关更多信息，请参阅《AWS Glue 开发人员指南**》中的[在 AWS Glue 中使用分区索引](https://docs.aws.amazon.com/glue/latest/dg/partition-indexes.html)和 *AWS 大数据博客*中的[使用 AWS Glue Data Catalog 分区索引提高 Amazon Athena 查询性能](https://aws.amazon.com/blogs/big-data/improve-amazon-athena-query-performance-using-aws-glue-data-catalog-partition-indexes/)。

## 始终使用 STRING 作为分区键的类型
<a name="performance-tuning-always-use-string-as-the-type-for-partition-keys"></a>

在查询分区键时，请记住：Athena 要求分区键的类型必须为 `STRING` 类型才能下推分区筛选到 AWS Glue。如果分区数量不小，则使用其他类型可能会导致性能降低。如果您的分区键值类似于日期或类似数字，请在查询中将其转换为相应的类型。

## 删除旧的和空的分区
<a name="performance-tuning-remove-old-and-empty-partitions"></a>

如果您从 Amazon S3 上的分区中移除数据（例如，使用 Amazon S3 [生命周期](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html)），则还应从 AWS Glue Data Catalog 中删除该分区条目。在查询计划期间，与查询匹配的任何分区都会在 Amazon S3 上列出。如果您有许多空分区，则列出这些分区的开销可能会造成不利影响。

另外，如果您有成千上万个分区，可以考虑删除旧数据不再相关的分区元数据。例如，如果查询从不查看超过一年的数据，则可以定期删除旧分区的分区元数据。如果分区的数量增加到数万个，则移除未使用的分区可以加快所有分区键上都不包含谓词的查询速度。有关在查询中包含所有分区键的谓词的信息，请参阅 [按等式查询分区](#performance-tuning-query-partitions-by-equality)。

## 按等式查询分区
<a name="performance-tuning-query-partitions-by-equality"></a>

由于可以直接加载分区元数据，因此在所有分区键上包含等式谓词的查询运行得更快。避免查询中一个或多个分区键没有谓词，或者谓词选择一定范围的值。对于此类查询，必须筛选所有分区的列表以找到匹配的值。对于大多数表来说，开销很小，但是对于具有成千上万或更多分区的表，开销可能会变得很大。

如果无法重写查询以按等式筛选分区，则可以尝试分区投影。有关更多信息，请参阅 [使用分区投影](#performance-tuning-use-partition-projection)。

## 避免使用 MSCK REPAIR TABLE 进行分区维护
<a name="performance-tuning-avoid-using-msck-repair-table-for-partition-maintenance"></a>

由于 `MSCK REPAIR TABLE` 可能需要很长时间才能运行，只能添加新分区，而不会删除旧分区，因此这不是管理分区的有效方法（请参阅 [注意事项和限制](msck-repair-table.md#msck-repair-table-considerations)）。

使用 [AWS Glue Data Catalog API](https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-catalog.html)、[ALTER TABLE ADD PARTITION](alter-table-add-partition.md) 或 [AWS Glue 爬网程序](https://docs.aws.amazon.com/glue/latest/dg/crawler-running.html)以更好地手动管理分区。作为替代方案，您可以使用分区投影，这样就无需管理分区。有关更多信息，请参阅 [将分区投影与 Amazon Athena 结合使用](partition-projection.md)。

## 验证您的查询是否与分区方案兼容
<a name="performance-tuning-validate-that-your-queries-are-compatible-with-the-partitioning-scheme"></a>

您可以使用 [`EXPLAIN`](athena-explain-statement.md) 语句提前检查查询将扫描哪些分区。在查询前面加上 `EXPLAIN` 关键字，然后在 `EXPLAIN` 输出底部附近查找每个表的源片段（例如 `Fragment 2 [SOURCE]`）。查找右侧被定义为分区键的分配。下面的行包括运行查询时将扫描的该分区键的所有值的列表。

例如，假设您在表上有一个 `dt` 分区键的查询，并在查询前面加上 `EXPLAIN`。如果查询中的值是日期，并且筛选器选择了三天的范围，则 `EXPLAIN` 输出可能如下所示：

```
dt := dt:string:PARTITION_KEY
    :: [[2023-06-11], [2023-06-12], [2023-06-13]]
```

`EXPLAIN` 输出显示计划程序为该分区键找到了三个与查询相匹配的值。同时还会显示这些值是什么。有关使用 `EXPLAIN` 的更多信息，请参阅 [在 Athena 中使用 EXPLAIN 和 EXPLAIN ANALYZE](athena-explain-statement.md) 和 [了解 Athena EXPLAIN 语句结果](athena-explain-statement-understanding.md)。

## 使用列式文件格式
<a name="performance-tuning-use-columnar-file-formats"></a>

Parquet 和 ORC 等列式文件格式专为分布式分析工作负载而设计。它们按列而不是按行组织数据。以列式格式组织数据具有以下优点：
+ 仅加载查询所需的列
+ 减少了需要加载的总数据量
+ 列值存储在一起，因此可以有效地压缩数据 
+ 文件可以包含允许引擎跳过加载不需要的数据的元数据

举例说明如何使用文件元数据，文件元数据可以包含有关数据页面中最小值和最大值的信息。如果查询的值不在元数据中注明的范围内，则可以跳过该页面。

使用此元数据来提高性能的一种方法是确保对文件中的数据进行排序。例如，假设您有查询来查找短时间段内 `created_at` 条目所在的记录。如果您的数据按 `created_at` 列排序，Athena 可以使用文件元数据中的最小值和最大值来跳过数据文件中不需要的部分。

使用列式文件格式时，请确保文件不要太小。如 [避免文件过多](#performance-tuning-avoid-having-too-many-files) 中所述，包含许多小文件的数据集会导致性能问题。对于列式文件格式尤其如此。对于小文件，列式文件格式的开销大于好处。

请注意，Parquet 和 ORC 在内部按行组（Parquet）和条带（ORC）进行组织。行组的默认大小为 128MB，条带的默认大小为 64MB。如果您有许多列，则可以增加行组和条带大小以提高性能。不建议将行组或条带大小减小到其默认值以下。

要将其他数据格式转换为 Parquet 或 ORC，您可以使用 AWS Glue ETL 或 Athena。有关更多信息，请参阅 [转换为列式格式](columnar-storage.md#convert-to-columnar)。

## 压缩数据
<a name="performance-tuning-compress-data"></a>

Athena 支持多种压缩格式。查询压缩数据更快，也更便宜，因为您需要为解压缩前扫描的字节数付费。

[gzip](https://www.gnu.org/software/gzip/) 格式提供了良好的压缩比，并且在其他工具和服务中具有广泛的支持。[zstd](https://facebook.github.io/zstd/)（Zstandard）格式是一种较新的压缩格式，在性能和压缩比之间取得了很好的平衡。

压缩 JSON 和 CSV 数据等文本文件时，请尽量在文件数量和文件大小之间取得平衡。大多数压缩格式都要求读取器从一开始就读取文件。这意味着通常不能并行处理压缩的文本文件。在查询处理过程中，大型未压缩文件通常在工作线程之间拆分，以实现更高的并行度；但是对于大多数压缩格式来说，这是不可能的。

如 [避免文件过多](#performance-tuning-avoid-having-too-many-files) 中所述，文件最好不要太多也不要太少。由于文件数量是可以处理查询的工作线程数量的限制，因此对于压缩的文件尤其如此。

有关在 Athena 中使用压缩的更多信息，请参阅 [在 Athena 中使用压缩](compression-formats.md)。

## 使用存储桶查找具有高基数的键
<a name="performance-tuning-use-bucketing-for-lookups-on-keys-with-high-cardinality"></a>

存储桶是一种根据其中一列的值将记录分发到单独文件中的技术。这样可以确保所有具有相同值的记录都位于同一个文件中。当您的键具有高基数并且您的许多查询都查找该键的特定值时，存储桶很有用。

例如，假设您查询一组记录寻求特定用户。如果数据按用户 ID 划分存储桶，Athena 会事先知道哪些文件包含特定 ID 的记录，哪些文件没有。这使得 Athena 能够只读取可能包含该 ID 的文件，从而大大减少了读取的数据量。它还缩短了在数据中搜索特定 ID 所需的计算时间。

### 当查询经常在列中搜索多个值时，请避免分桶
<a name="performance-tuning-disadvantages-of-bucketing"></a>

当查询经常在存储数据的列中搜索多个值时，存储桶的价值就会降低。查询的值越多，必须读取所有或大多数文件的可能性就越大。例如，如果您有三个存储桶，并且查询查找三个不同的值，则可能必须读取所有文件。当查询查找单个值时，存储桶效果最好。

有关更多信息，请参阅 [使用分区和分桶](ctas-partitioning-and-bucketing.md)。

## 避免文件过多
<a name="performance-tuning-avoid-having-too-many-files"></a>

由许多小文件组成的数据集会导致整体查询性能不佳。当 Athena 计划查询时，它会列出所有分区位置，这需要时间。处理和请求每个文件也会产生计算开销。因此，从 Amazon S3 加载单个更大的文件要比从许多较小的文件加载相同记录要快。

在极端情况下，您可能会遇到 Amazon S3 服务限制。Amazon S3 每秒最多支持向单个索引分区发出 5500 个请求。最初，存储桶被视为单个索引分区，但是随着请求负载的增加，可以将其拆分为多个索引分区。

Amazon S3 着眼于请求模式，并根据键前缀进行拆分。如果您的数据集包含成千上万个文件，则来自 Athena 的请求可能会超过请求限额。即使文件较少，如果对同一个数据集进行多个并发查询，也可能超过限额。访问相同文件的其他应用程序可能会占请求总数。

超过请求速率 `limit` 时，Amazon S3 会返回以下错误。此错误包含在 Athena 中查询的状态信息中。

 减速：请降低请求速率 

要排除故障，首先要确定错误是由单个查询引起的，还是由读取相同文件的多个查询引起的。如果是后者，请协调查询的运行，这样它们就不会同时运行。为此，请在应用程序中添加排队机制甚至重试。

如果运行单个查询触发错误，请尝试合并数据文件或修改查询以减少读取的文件。合并小文件的最佳时机是在写入它们之前。为此，请考虑以下技术：
+ 将写入文件的过程更改为写入更大的文件。例如，可以在写入记录之前将其缓冲更长时间。
+ 将文件放在 Amazon S3 上的某个位置，然后使用像 Glue ETL 这样的工具将它们合并成更大的文件。然后，将较大的文件移动到表格所指向的位置。有关更多信息，请参阅《AWS Glue 开发人员指南**》中的[读取较大组中的输入文件](https://docs.aws.amazon.com/glue/latest/dg/grouping-input-files.html)和 *AWS re:Post 知识中心*中的[如何配置 AWS Glue ETL 任务以输出较大文件？](https://repost.aws/knowledge-center/glue-job-output-large-files)。
+ 减少分区键的数量。当分区键过多时，每个分区可能只有几个记录，从而导致小文件数量过多。有关决定创建哪些分区的信息，请参阅 [选择支持您的查询的分区键](#performance-tuning-pick-partition-keys-that-will-support-your-queries)。

## 避免在分区之外设置额外的存储层次结构
<a name="performance-tuning-avoid-additional-storage-hierarchies-beyond-the-partition"></a>

为避免查询计划开销，请在每个分区位置以扁平结构存储文件。请勿使用任何其他目录层次结构。

当 Athena 计划查询时，它会列出与该查询匹配的所有分区中的所有文件。尽管 Amazon S3 本身没有目录，但惯例是将 `/` 正斜杠解释为目录分隔符。当 Athena 列出分区位置时，它会递归列出找到的任何目录。当分区内的文件按层次结构组织时，会出现多轮列表。

当所有文件都直接位于分区位置时，大多数时候只需要执行一个列表操作。但是，如果分区中有超过 1000 个文件，则需要进行多次顺序列表操作，因为 Amazon S3 每次列表操作仅返回 1000 个对象。一个分区中有 1000 个以上的文件还会造成其他更严重的性能问题。有关更多信息，请参阅 [避免文件过多](#performance-tuning-avoid-having-too-many-files)。

## 仅在必要时使用 SymlinkTextInputFormat
<a name="performance-tuning-use-symlinktextinputformat-only-when-necessary"></a>

使用 [https://athena.guide/articles/stitching-tables-with-symlinktextinputformat](https://athena.guide/articles/stitching-tables-with-symlinktextinputformat) 技术可以解决表的文件没有整齐地组织到分区中的情况。例如，当所有文件都使用相同前缀或具有不同架构的文件位于同一位置时，符号链接可能很有用。

但是，使用符号链接会增加查询执行的间接级别。这些间接级别会影响整体性能。必须读取符号链接文件，并且必须列出它们定义的位置。这会增加到 Amazon S3 的多次往返行程，而通常的 Hive 表不需要这些往返行程。总之，只有在没有更好的选项（例如重组文件）时，才应使用 `SymlinkTextInputFormat`。