我有一个大约5M行x20列的数据集,包含groupID和rowID。我的目标是检查(某些)列是否包含组内缺少(空)值的固定分数(例如,50%)。如果找到,则该组的整个列设置为missing(null)。
df = spark.read.parquet('path/to/parquet/')
check_columns = {'col1': ..., 'col2': ..., ...} # currently len(check_columns) = 8
for col, _ in check_columns.items():
total = (df
.groupBy('groupID').count()
.toDF('groupID', 'n_total')
)
missing = (df
.where(F.col(col).isNull())
.groupBy('groupID').count()
.toDF('groupID', 'n_missing')
)
# count_missing = count_missing.persist() # PERSIST TRY 1
# print('col {} found {} missing'.format(col, missing.count())) # missing.count() is b/w 1k-5k
poor_df = (total
.join(missing, 'groupID')
.withColumn('freq', F.col('n_missing') / F.col('n_total'))
.where(F.col('freq') > 0.5)
.select('groupID')
.toDF('poor_groupID')
)
df = (df
.join(poor_df, df['groupID'] == poor_df['poor_groupID'], 'left_outer')
.withColumn(col, (F.when(F.col('poor_groupID').isNotNull(), None)
.otherwise(df[col])
)
)
.select(df.columns)
)
stats = (missing
.withColumnRenamed('n_missing', 'cnt')
.collect() # FAIL 1
)
# df = df.persist() # PERSIST TRY 2
print(df.count()) # FAIL 2
我最初分配了1G spark.driver.memory
和4G spark.executor.memory
,最终将spark.driver.memory
增加到10G。
问题(S): 在第一次迭代中,循环像魅力一样运行,但是到最后, 在第6或第7次迭代中,我看到我的CPU利用率下降(使用1 而不是6个核心)。与此同时,一次迭代的执行时间 显着增加。 在某些时候,我得到一个OutOfMemory错误:
spark.driver.memory < 4G
:collect()
(FAIL 1
)4G <= spark.driver.memory < 10G
:count()
步(FAIL 2
) FAIL 1
案例(相关部分)的堆栈跟踪:
[...]
py4j.protocol.Py4JJavaError: An error occurred while calling o1061.collectToPython.
: java.lang.OutOfMemoryError: Java heap space
[...]
执行程序UI不反映过多的内存使用情况(显示使用的&lt; 50k)
驱动程序的内存和执行程序的&lt; 1G)。 Spark指标系统
(app-XXX.driver.BlockManager.memory.memUsed_MB
)也没有:它显示
600M到1200M的已用内存,但总是> 300M的剩余内存。
(这表明2G驱动程序内存应该这样做,但事实并非如此。)
首先处理哪个列也没关系(因为它是一个循环
一个dict()
,它可以按任意顺序排列。)
我的问题是:
spark.driver.memory
?一些(一般)问题,以确保我理解正确的事情:
count()
会导致OOM错误 - 我认为此操作只会
消耗exector(s)上的资源(向驱动程序提供几个字节)?BTW:我在独立模式下运行Spark 2.1.0。
为了进一步向下钻取,我为驱动程序启用了堆转储:
cfg = SparkConfig()
cfg.set('spark.driver.extraJavaOptions', '-XX:+HeapDumpOnOutOfMemoryError')
我使用8G
的{{1}}运行它,然后分析了堆转储
Eclipse MAT。事实证明,有两个相当大的类(每个大约4G):
spark.driver.memory
我尝试使用
关闭用户界面java.lang.Thread
- char (2G)
- scala.collection.IndexedSeqLike
- scala.collection.mutable.WrappedArray (1G)
- java.lang.String (1G)
org.apache.spark.sql.execution.ui.SQLListener
- org.apache.spark.sql.execution.ui.SQLExecutionUIData
(various of up to 1G in size)
- java.lang.String
- ...
这使得UI不可用,但对OOM错误没有帮助。另外,我试过了 使用
让UI保持较少的历史记录cfg.set('spark.ui.enabled', 'false')
这也没有帮助。
我发现了Spark的cfg.set('spark.ui.retainedJobs', '1')
cfg.set('spark.ui.retainedStages', '1')
cfg.set('spark.ui.retainedTasks', '1')
cfg.set('spark.sql.ui.retainedExecutions', '1')
cfg.set('spark.ui.retainedDeadExecutors', '1')
方法。 This is like persist
but gets rid of the dataframe's lineage。因此,它有助于规避上述问题。