Apache spark Spark:控制分区以减少洗牌
为了减少特定管道上的混洗量,我正在尝试在Spark中对数据帧进行Apache spark Spark:控制分区以减少洗牌,apache-spark,optimization,partitioning,Apache Spark,Optimization,Partitioning,为了减少特定管道上的混洗量,我正在尝试在Spark中对数据帧进行分区的不同方法 这是我正在处理的数据框架,它包含4+十亿行和80列: +-----+-------------------+-----------+ | msn| timestamp| Flight_Id | +-----+-------------------+-----------+ |50020|2020-08-22 19:16:00| 72.0| |50020|2020-08-22 19:15
分区的不同方法
这是我正在处理的数据框架,它包含4+十亿行和80列:
+-----+-------------------+-----------+
| msn| timestamp| Flight_Id |
+-----+-------------------+-----------+
|50020|2020-08-22 19:16:00| 72.0|
|50020|2020-08-22 19:15:00| 84.0|
|50020|2020-08-22 19:14:00| 96.0|
|50020|2020-08-22 19:13:00| 84.0|
|50020|2020-08-22 19:12:00| 84.0|
|50020|2020-08-22 19:11:00| 84.0|
|50020|2020-08-22 19:10:00| 84.0|
|50020|2020-08-22 19:09:00| 84.0|
|50020|2020-08-22 19:08:00| 84.0|
|50020|2020-08-22 19:07:00| 84.0|
|50020|2020-08-22 19:06:00| 84.0|
|50020|2020-08-22 19:05:00| 84.0|
|50020|2020-08-22 19:04:00| 84.0|
|50020|2020-08-22 19:03:00| 84.0|
|50020|2020-08-22 19:02:00| 84.0|
|50020|2020-08-22 19:01:00| 84.0|
|50020|2020-08-22 19:00:00| 84.0|
|50020|2020-08-22 18:59:00| 84.0|
|50020|2020-08-22 18:58:00| 84.0|
|50020|2020-08-22 18:57:00| 84.0|
+-----+-------------------+-----------+
这表示不同飞机(总共41架飞机)的时间序列集合。
我对这些数据只做了两件事:
使用由MSN
和flight\u ID
划分的窗口,并使用order by
bytimestamp
过滤以保持每次航班的最后30分钟
在剩下的列上,计算mean
和stdev
并标准化数据
我有32个执行器,每个执行器有12g内存,作业在运行30小时后崩溃,并显示以下消息:
The driver running the job crashed, ran out of memory, or otherwise became unresponsive while it was running.
查看查询计划,我注意到我有300多个步骤,其中60多个步骤涉及洗牌(所有步骤物理计划看起来完全相同):
下面是代码的一部分,我将数据从存储在其中的数据池中拉到一起。
仅供参考,这是通过使用名为FoundryTS
的库的自定义API实现的。重要的是,在调用to_dataframe()
方法之前,不会收集任何数据。我在每个msn
上循环,以避免调用过大,然后我将所有数据帧与unionByName
# Loop over MSN to extract timeseries
df = []
for msn in msn_range:
search_results = (SeriesMetadata.M_REPORT_NUMBER == report_number) & (SeriesMetadata.M_AIRCRAFT == msn)
# Create the intervals to split TimeSeries extract by flight for each MSN
Start_int = list(df1.where(F.col("msn") == msn).select("Start").toPandas()["Start"])
End_int = list(df1.where(F.col("msn") == msn).select("End").toPandas()["End"])
flight_id = list(df1.where(F.col("msn") == msn).select("id_cmsReport").toPandas()["id_cmsReport"])
flights_interval = [Interval(
start, end, name=flight_Id
) for start, end, flight_Id in zip(
Start_int, End_int, flight_id
)]
""" Collect all the series in a node collections """
output = fts.search.series(
search_results,
object_types=["export-control-us-ear99-a220-dal-airline-series"])\
.map_by(FF.interpolate(
before='nearest',
internal='nearest',
after='nearest',
frequency=frequency,
rename_columns_by=lambda x: x.metadata["parameter_id"] + "_" + x.metadata["report_number"]),
keys='msn') \
.map_intervals(flights_interval, interval_name='Flight_Id_Int')\
.map(FF.time_range(period_start, period_end))\
.to_dataframe() # !!!! numPartitions=32 Foundry Doc : #partition = #executors see if it triggers OOM error
df.append(output)
output = df[0]
for df in df[1:]:
output = output.unionByName(df) # Same as union but matches name instead of columns order.
# Repartition by msn to improve latter calculation
N = len(msn_range)
output.repartition(N, 'msn')
运行作业的驱动程序在运行时崩溃、内存不足或无响应
您需要解决的第一个问题是增加驱动程序(而不是执行器)的内存。spark中的默认驱动程序内存通常很低,在许多查询中都会崩溃
“我的问题是如何以及在代码中的何处重新分区”
Spark已经完成了根据需要添加重新分区的工作。很有可能,只有在执行中途手动重新分区数据时,您才会创建额外的工作。一个潜在的优化是将数据存储在带扣的表中,但这只会潜在地删除第一次交换,并且仅当您的带扣列完全匹配时这是第一次交换的哈希分区
“查看查询计划,我注意到我有300多个步骤”
您上面描述的不需要300个步骤。这里似乎有点不对劲。您的优化逻辑计划是什么样子的?mean和std应该只需要扫描->部分agg->交换->最终agg。在您提供的查询计划中,您似乎有意只查看最后1600个数据点,而不是最后30m.D你的意思是做一个窗口函数,而不是一个简单的聚合(又名group by)
编辑:
对于msn_范围内的msn:
在我看来,这可能是你的问题的一部分。这个for循环会导致执行计划非常大,这可能是你在驱动程序上遇到OOM问题的原因。你可能可以将其转换为对spark更友好的东西,而不需要对驱动程序做太多的工作,将forloop转换为spark。并行化(…).map(/your code/)运行作业的驱动程序在运行时崩溃、内存不足或没有响应
您需要解决的第一个问题是增加驱动程序(而不是执行器)的内存。spark中的默认驱动程序内存通常很低,在许多查询中都会崩溃
“我的问题是如何以及在代码中的何处重新分区”
Spark已经完成了根据需要添加重新分区的工作。很有可能,只有在执行中途手动重新分区数据时,您才会创建额外的工作。一个潜在的优化是将数据存储在带扣的表中,但这只会潜在地删除第一次交换,并且仅当您的带扣列完全匹配时这是第一次交换的哈希分区
“查看查询计划,我注意到我有300多个步骤”
您上面描述的不需要300个步骤。这里似乎有点不对劲。您的优化逻辑计划是什么样子的?mean和std应该只需要扫描->部分agg->交换->最终agg。在您提供的查询计划中,您似乎有意只查看最后1600个数据点,而不是最后30m.D你的意思是做一个窗口函数,而不是一个简单的聚合(又名group by)
编辑:
对于msn_范围内的msn:
在我看来,这可能是你的问题的一部分。这个for循环会导致执行计划非常大,这可能是你在驱动程序上遇到OOM问题的原因。你可能可以将其转换为对spark更友好的东西,而不需要对驱动程序做太多的工作,将forloop转换为spark。并行化(…).map(/your code/)对于那些可能有帮助的人
以下是我在分区中出错的地方:
.to_dataframe()
:默认情况下,在我们的云平台Spark中创建200个分区。因此,通过在40msn
上循环,我生成了40 x 200分区。我最终需要管理很多小任务
.repartition()
:由于我在msn
上使用了窗口和partitionBy
,我认为使用msn
重新分区将加快这一步。但它引入了分区的完全洗牌
结果:根据Spark Job Tracker和>55k的任务,59 GB的随机写入。任务占用了一些开销,这可以解释驱动程序崩溃的原因
我做了什么使它工作:
我去掉了窗口
函数
在我从DataLake获取数据之前,通过在过程的早期进行过滤。我直接提取了我所需的飞行部分。因此,物理计划中的交换分区更少,用于完全相同的部分
以下是更新的物理计划:
AdaptiveSparkPlan(isFinalPlan=false)
+- CollectLimit 1
+- HashAggregate(keys=[], functions=[avg(3565000_421#213), stddev_samp(3565000_421#213)], output=[avg(3565000_421)#10246, stddev_samp(3565000_421)#10255])
+- ShuffleQueryStage 0
+- Exchange SinglePartition, true
+- *(43) HashAggregate(keys=[], functions=[partial_avg(3565000_421#213), partial_stddev_samp(3565000_421#213)], output=[sum#10317, count#10318L, n#10261, avg#10262, m2#10263])
+- Union
:- *(1) Project [3565000_421#213]
: +- *(1) Scan ExistingRDD[msn#208,Flight_Id_Int#209,Flight_Id_Int.start#210L
我减少了分区的数量:
# Loop over MSN to extract timeseries
df = []
for msn in msn_range:
search_results = (SeriesMetadata.M_REPORT_NUMBER == report_number) & (SeriesMetadata.M_AIRCRAFT == msn)
# Create the intervals to split TimeSeries extract by flight for each MSN
Start_int = list(df1.where(F.col("msn") == msn).select("Start").toPandas()["Start"])
End_int = list(df1.where(F.col("msn") == msn).select("End").toPandas()["End"])
flight_id = list(df1.where(F.col("msn") == msn).select("id_cmsReport").toPandas()["id_cmsReport"])
flights_interval = [Interval(
start, end, name=flight_Id
) for start, end, flight_Id in zip(
Start_int, End_int, flight_id
)]
""" Collect all the series in a node collections """
output = fts.search.series(
search_results,
object_types=["export-control-us-ear99-a220-dal-airline-series"])\
.map_by(FF.interpolate(
before='nearest',
internal='nearest',
after='nearest',
frequency=frequency,
rename_columns_by=lambda x: x.metadata["parameter_id"] + "_" + x.metadata["report_number"]),
keys='msn') \
.map_intervals(flights_interval, interval_name='Flight_Id_Int')\
.map(FF.time_range(period_start, period_end))\
.to_dataframe() # !!!! numPartitions=32 Foundry Doc : #partition = #executors see if it triggers OOM error
df.append(output)
output = df[0]
for df in df[1:]:
output = output.unionByName(df) # Same as union but matches name instead of columns order.
# Repartition by msn to improve latter calculation
N = len(msn_range)
output.repartition(N, 'msn')
AdaptiveSparkPlan(isFinalPlan=false)
+- CollectLimit 1
+- HashAggregate(keys=[], functions=[avg(3565000_421#213), stddev_samp(3565000_421#213)], output=[avg(3565000_421)#10246, stddev_samp(3565000_421)#10255])
+- ShuffleQueryStage 0
+- Exchange SinglePartition, true
+- *(43) HashAggregate(keys=[], functions=[partial_avg(3565000_421#213), partial_stddev_samp(3565000_421#213)], output=[sum#10317, count#10318L, n#10261, avg#10262, m2#10263])
+- Union
:- *(1) Project [3565000_421#213]
: +- *(1) Scan ExistingRDD[msn#208,Flight_Id_Int#209,Flight_Id_Int.start#210L