我有一个小的Hive Table,在HDFS上保存了1500万行(镶木地板/ 1152个文件 - 超过30GB)。
我在科学论文上做LDA。因此,第一步是使用StanfordNLP提取一些名词短语/块短语,我写了一个UDF来实现这个目标。
现在表现明智,有两种情况,每种情况都有不同的结果。
情景1:
val hiveTable = hivecontext.sql("""
SELECT ab AS text,
pmid AS id
FROM scientific.medline
LIMIT 15000000
""")
然后我通过hiveTable
:
val postagsDF = hiveTable.withColumn("words", StanfordNLP.posPhrases(col("text")))
现在,如果我触发任何动作/转换,例如.count()或对“ postagsDF ”执行CountVectorizer(),我会看到2个阶段。一个具有适当数量的任务(分区),另一个阶段只有一个任务。第一个在完成一些输入/随机写入后结束非常快,但第二个只有一个任务需要很长时间。看来我的UDF正在这个阶段执行,只有一个任务。 (在完成期间需要数小时,没有资源活动)
情景2:
val hiveTable = hivecontext.sql("""
SELECT ab AS text,
pmid AS id
FROM scientific.medline
LIMIT 15000000
""")
我根据parquets的数量将DataFrame
重新分区为spark检测到的确切分区数。 (我可以选择任何其他数字,但数字似乎没问题,因为我有超过500个核心可用 - 每个核心有2个任务)
val repartitionedDocDF = docDF.repartition(1152)
现在通过我的hiveTable
调用我的UDF:
val postagsDF = hiveTable.withColumn("words", StanfordNLP.posPhrases(col("text")))
但是,这次的任何行动/转型将分为四个阶段。其中两个阶段(比如计数)是1152个任务,其中两个是单任务。我可以看到我的UDF正在其中一个阶段执行,所有执行程序都使用我的整个集群正确执行1152个任务。
第1场景的结果: 看看我的集群,在长期运行的单任务阶段没有太多进展。没有CPU使用,没有内存,没有网络,也没有IO活动。只有一个执行程序,其中一个任务是在每个文档/列上应用我的UDF。
基准:场景编号1需要3-4个小时才能完成100万行。 (我迫不及待地想看到1500万行需要多少)
第2场景的结果: 看着我的集群,我可以清楚地看到我的所有资源都在被利用。我的所有节点几乎都处于满负荷状态。
基准测试:场景编号2需要超过30分钟才能获得1500万行。
真实问题
刚刚发生了什么?我认为默认情况下,Dataframe上的UDF将并行运行。如果partions / tasks的数量多于或少于核心总数,但至少与默认的200个分区/任务并行,则可能进行重新分区。我只是想了解为什么在我的情况下UDf是单任务而忽略了默认的200和实际的分区大小。 (这不仅仅是关于性能,它是单任务工作与多任务工作)
是否还有其他方法可以在不调用重新分区的情况下并行执行所有执行程序的UDF。我没有反对重新分区,但它是非常昂贵的操作,我不认为它应该是使UDF并行运行的唯一方法。即使我重新分配到完全相同数量的分区/文件,我仍然需要观看超过20GB的shuffle读取和写入飞越我的群集。
我已经阅读了有关重新分区和UDF的所有内容,但我找不到类似的问题,除非它进行重新分区,否则默认情况下不能并行运行UDF。 (当您将一种类型的int从int转换为bigint时,简单的UDF可能不可见,但是当您执行NLP时它确实可见)
我的群集大小:30个节点(16核/ 32G) - Spark 1.6 Cloudera CDH 5.11.1
Spark:--driver-cores 5 --driver-memory 8g --executor-cores 5 --executor-memory 5g --num-executors 116
非常感谢,
更新:
我在没有LIMIT条款的情况下运行了相同的代码,它在18分钟内完成了!所以LIMIT就是原因(答案中更多内容):
答案 0 :(得分:4)
此处的问题与您在查询中使用的LIMIT
子句特别相关,与UDF
无关。 LIMIT
子句将所有结果数据重新分配到单个分区,因此它不适用于大样本。
如果您想避免此问题并以某种方式减少记录数量,最好先对数据进行采样:
val p: Double = ???
spark.sql(s"SELECT * FROM df TABLESAMPLE($p percent)")
或:
spark.table("df").sample(false, p)
其中p
是所需的记录部分。
请注意,使用确切数量的值进行抽样会遇到与LIMIT
子句相同的问题。