优化亚马逊 S3 上的 Apache Spark 工作负载的性能

这篇博客介绍了在 Amazon EK S 上运行的 OSS Spark 特有的性能指标、优化和配置调整。对于使用或考虑在 EKS 上使用 Amazon EMR 的客户,请参阅服务 文档 以开始使用,并阅读这 篇 博客文章 了解最新的性能基准。


对于在 Apache Spark 和 Kubernetes 上运行流式传输、数据提取转换负载 (ETL)、机器学习 (ML) 或图形处理工作负载的客户来说,性能是重中之重。尽管客户一开始通常使用 Apache Spark 和 Kubernetes 等框架的默认设置,但他们的下一步是调整这些工作负载。这是一个重要步骤,因为每个工作负载都是独一无二的,需要调整性能以有效地使用分配的资源并避免瓶颈。

要在 Kubernetes 上运行 Apache Spark 任务,客户通常使用 亚马逊弹性 Kubernetes 服务 (EKS),数据存储在亚马逊简单 存储服务 (S3) 上。 亚马逊 EKS 是一项完全托管的服务,可以轻松地在 亚马逊云科技 上运行 Kubernetes,而无需安装、操作和维护 Kubernetes 控制平面。对于数据湖存储,客户通常使用 Amazon S3,因为它安全、可扩展、耐用且高度可用。

在这篇文章中,我们概述了在亚马逊 EKS 和亚马逊 S3 上优化 Apache Spark 任务的分步过程。我们首先使用样本数据设置一个数据湖,然后详细完成调整步骤。通过应用这些更改,Apache Spark 作业的运行时间减少了 60%,CPU 利用率提高了 30%,从而降低了运行作业的总体成本。尽管这些优化是为在亚马逊 EKS 上运行的 Apache Spark 任务量身定制的,但它们也可以与使用 S3A 或其他适用于 Amazon S3 连接器的 Apache Spark 集群在 Aws、亚马逊云科技 Fargate 和 Apache Spar k 集群上使用自行管理的 Kubernetes 一起使用。

初始设置和观察

为了模拟典型的客户工作负载,我们在 Amazon S3 上设置了一个 130 GB 的数据湖,该数据湖由平均大小为 13 GB 的镶木地板文件组成。我们从 Amazon S3 读取镶木地板文件,选择几列,然后将选定的列保存回 Amazon S3 到目标文件夹。在亚马逊 EKS 上运行 Apache Spark 任务时,我们使用了一台具有 16 个内核、64 GB 内存、最大网络吞吐量为 10 Gbit/s 的 m5.4xl 机器。我们的工作负载使用了 Apache Spark 3.4 和 Hadoop 3.3.1 版本。

使用默认参数时,作业需要 10 分钟才能完成,平均 CPU 利用率为 50%。我们观察到 CPU 利用率很低,因此正在寻找改进的方法,从而缩短任务完成时间。对Apache Spark事件日志的快速分析表明,大约有一半的时间花在从亚马逊S3读取数据上。因此,我们研究了优化从 Amazon S3 读取数据的时间的方法。

缩短 Amazon S3 数据读取时间的步骤

我们的改进集中在以下几个方面:

  • 调整读取的数据字节范围
  • 优化 Kubernetes Pod 资源
  • 修改 Kubernetes 的 DNS 配置

接下来,我们将逐一介绍这些优化。

1/ 调整读取的数据字节范围

首先,我们查看了每个请求读取的字节数。这对于优化网络利用率至关重要,因为在较小字节范围内获取的数据通常会导致 Apache Spark 作业花费更多的 CPU 周期来管理网络连接,而不是处理数据。尽管我们研究了各种各样的字节读取,但在整个实验中,我们希望在数据读取大小和并行运行的作业数量之间取得平衡。这很重要,因为在大型顺序文件读取(1 GB+)时,分区(要查询的单个数据段)的数量会显著减少(> 75%)。这降低了任务的总吞吐量。

为了找到合适的 Amazon S3 读取大小,我们尝试了以下四个参数:

a/ 调整 p arquet.block.size :一个 Parquet 文件由一个或多个行组组成。行组由每列的一个数据块组成,每个数据块紧随其后,每个数据块由一个或多个包含列数据的页面组成。较大的行组允许更大的列区块,这使得执行更大的顺序 I/O 成为可能。较大的组在写入路径中也需要更多的缓冲。相反,一小部分会降低顺序 I/O 并降低吞吐量。我们在写入 parquet 文件时尝试了更大的拼花板行组大小,以便在读取数据时提高网络利用率。

df.write.option("parquet.block.size", 128*1024*1024).parquet('s3://data/file/parquet')

b/ 调整 输入文件的大小以确保块大小的增加是有效的,因为非常低的输入文件大小可能是一个限制因素。例如,将区块大小设置为 128 MiB,而输入文件大小仅为 64 MiB,这意味着有效的区块大小仅为 64 MiB,因为单个 Amazon S3 请求不能超过 64 MiB。因此,输入文件的大小必须至少大于区块大小。这可以通过在保存输入文件之前减少分区数量来实现。

df.repartition(100).write.option("parquet.block.size", 128*1024*1024).parquet('s3://data/file/parquet')

c/ 调整 sp ark.hadoop.parquet.read.allocation.size:此参数指定读取镶木地板文件时使用的缓冲 区大小。 我们观察到,在使用 S3A 从 Amazon S3 读取数据时,它还限制了每个 Amazon S3 请求中传输的字节数。默认值设置为 8 MB,它将每个 Amazon S3 请求限制为 8 MB。为了提高吞吐量并实现更大的顺序 I/O 读取,我们尝试了更高的值,最终获得了 128 MB 以获得最优的性能。将参数增加到 128MB 以外会减少作业中的分区总数,从而延长作业的总体运行时间。

d/ 调整 Spark.sql.Files.maxpartitionBytes :此参数指定读入分区的总数据大小,如果将其配置为低于 spark.hadoop.parquet.read.allocation.size,则可以作为限制因素。 为了实现大规模的顺序 I/O 读取以更快的吞吐量,我们最终使用了 512 MB 以实现最佳性能。

我们尝试了前面提到的参数的不同组合,最终得出了以下工作负载值:

  • parquetblock.size = 512 MB
  • 输入文件大小 = 13 GB
  • parquet.read.allocation.size = 128 MB
  • 最大分区字节数 = 512 MB

通过此次调整,平均吞吐量增加了77%,作业时间从10分钟减少了40%至6分钟。整体CPU利用率也从50%提高到65%。

接下来,我们将考虑为我们的 Apache Spark 任务分配正确数量的 CPU 内核。

2/ 优化 Kubernetes Pod 资源

在 Kubernetes 上获得正确数量的 CPU 请求需要进行一些实验。当我们观察到 CPU 利用率低于分配值时,我们会考虑缩小容器的大小,因为计算能力被浪费了。但是,如果 Pod 的 CPU 限制过于严格,则工作负载可以观察 CPU 节流。这意味着任务需要更长的时间才能完成。因此,我们调整分配的 vCPU 内核数量以提高性能或削减成本。为了实现这一目标,我们审查了 Apache Spark 作业的性能指标,以确保请求的 vCPU 最准确地反映了我们从应用程序中观察到的实际资源利用率。这允许 Kubernetes 将更多的应用程序副本打包到一个节点上,从而提高每个节点的资源利用率并减少总节点数。

以下是我们为数据湖确定正确数量的 vCPU 所遵循的步骤:

步骤 1 :确定应用程序当前使用的资源。这可以通过测量平均 CPU 使用率、CPU 限制和正常负载下的最大内存使用率来实现。与 kube- prometheus-stack 一起安装的 kubernetes-m ixin 仪表板 可以显示每个 Pod 的使用情况以及节点上的使用情况。您也可以参考这 篇 关于使用 Prometheus 的 亚马逊云科技 帖子 ,了解有关 CPU 限制和相关指标的更多详细信息。

第 2 步 :在未定义限制的情况下找到应用程序的最大性能。这为我们提供了在 pod 的 “最佳场景” 中的使用情况,它可以获得所需的所有资源。为了实现这一目标,我们取消了 pod 规范中的限制,并在空的 Kubernetes 工作节点上重新运行我们的 Apache Spark 执行器。

步骤 3 :计算 CPU 单位的平均 CPU 使用率和 峰值 负载下的最大内存使用量。这些值是我们对单个 Executor Pod 的期望,该容器被驱动到运行所需的极限值。

步骤 4 :根据步骤 3 中的计算,我们配置了以下内容:

  • vCPU 对平均 CPU 使用率的请求。
  • 内存请求和限制最大内存使用量,以及缓冲区以避免内存耗尽。

我们没有设置 vCPU 限制,我们再次运行了 Apache Spark 任务以验证峰值负载下的资源使用情况。

通过这个过程,我们将 vCPU 请求从每个 Pod 的 4 个内核调整为 0.75 个内核,并取消了 vCPU 限制。此请求通过共享 CPU 并将利用率提高到 75%,有效减少了 CPU 空闲时间。因此,我们将作业运行时间从 6 分钟缩短到 5 分钟,减少了 16%。为了进一步提高整体吞吐量,我们还查看了 Apache Spark 任务的 DNS 配置。

3/ 修改 Kubernetes 的 DNS 配置

通过分析网络流量,我们发现所有数据包中有35%以上是对不存在的主机名进行DNS查询,这导致了网络延迟。每个 Amazon S3 GET 请求都会导致五次 DNS 查询,每次查询都以失败的查询告终。这些额外的 DNS 查询延长了 Apache Spark 作业的等待时间。以下是所有不存在的 URL 的列表,以及最后一行的真实地址。

amazon.com.default.svc.cluster.local
 amazon.com.svc.cluster.local
 amazon.com.cluster.local
 amazon.com.ec2.internal
 amazon.com.

Kubernetes 为服务和容器创建 DNS 记录。默认情况下,Kubernetes 配置为没有 DNS 缓存,并且将点数(称为 ndots )设置为五。在进行初始绝对查询之前, n dots 值为名称中必须出现的点数设置阈值。我们调整了 Kuber netes 中配置的 ndot s 值,以进行优化,从而实现更快、更高效的 DNS 解析。

nameserver 10.100.0.10
 search default.svc.cluster.local svc.cluster.local cluster.local ec2.internal
 options ndots:5

对于我们的工作负载,Kubernetes 正在对外部服务 Amazon S3 进行服务查询。因此,我们希望 Apache Spark 仅查找有效的亚马逊 S3 DNS 名称。我们发现,将 ndots 值减少到二会导致 Apache Spark 进行单个 DNS 查询,而不是一系列请求失败。我们在 Kubernetes 部署的 D NSConfig 中配置了 nd ots 值:

 spec:
   dnsConfig:
     options:
       - name: ndots
         value: "2"

为了验证更改前后 ndots 设置的效果,我们查看了 CoreDNS 窗格中的日志。首先,我们在 CoreDNS 部署中开启了详细日志设置。我们使用以下命令编辑了 CoreDNS 的配置图:

kubectl edit configmap coredns -n kube-system

我们在 configmap 中添加了一行带有 登录信息的行并保存了编辑内容。以下是我们观察到的日志输出示例:

apiVersion: v1
 data:
   Corefile: |
     .:53 {
         log
         errors
         health
         kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
         }
 ...............

然后,我们使用以下命令检查了 DNS 日志: kubectl logs coredns_pod_name — n kube-s ystem。

对于我们的样本工作负载,将 ndots 值设置为二可使平均吞吐量增加 5%。尽管在我们的实验中这种改进微乎其微,但通过这种配置,客户已经看到吞吐量提高了多达30%。最终的优化将我们的任务运行时间缩短至五分钟,CPU 利用率为 75%,总吞吐量增加了 82%。

监控和验证工作负载的变化

验证您在工作负载中微调的每个参数的变化至关重要。这很重要,因为由于工作负载的确切环境,相同的设置可能会对每个工作负载产生不同的影响。以下是我们推荐的监控和验证 Apache Spark 作业变化的方法列表:

  • 在 Apache Spark 事件日志中查看作业运行时间和数据扫描时间,这些日志可从 Apache Spark Web 用户界面获得。
  • 使用 亚马逊云科技 或开源可观测性工具 监控网络使用情况、集群的 CPU 利用率和错误率。
  • 启用 Amazon S3 访问日志并检查请求大小和吞吐量。有关更多详细信息,您可以参阅 Amazon S3 用户指南

结论

在这篇文章中,我们展示了三种针对亚马逊 EKS 上的 Apache Spark 工作负载优化亚马逊 S3 性能的技术。我们调整了读取的数据字节范围,优化了 Kubernetes Pod 资源,并修改了 Kubernetes DNS 配置。通过性能调整步骤,我们将作业运行时间从 10 分钟减半到 5 分钟,CPU 利用率从 50% 提高到 75%,总体吞吐量提高了 82%。这些优化有助于缩短运行 Apache Spark 作业的时间。采用这些技术可以帮助更快地从数据中获得见解,并降低工作负载的总成本。

请在评论中告诉我们这些技术是否可以帮助您优化工作负载。