Spark缓慢重新分区许多小文件

时间:2018-10-02 10:45:17

标签: scala apache-spark

我正在尝试读取一个包含许多小木地板文件的文件夹:600个文件,每个500KB。然后repartition将它们分成2个文件。

val df = spark.read.parquet("folder")
df.repartition(2).write.mode("overwrite").parquet("output_folder")

这太慢了,最多10分钟。从spark UI,我可以看到2个执行程序正在处理2个任务。我给每个执行者10GB的内存。

enter image description here

那速度慢的原因是什么?是因为磁盘IO吗?以及在这种情况下如何提高性能。

编辑:我也尝试使用coalesce,但效果似乎没有什么不同。

2 个答案:

答案 0 :(得分:1)

第一个选择是在源代码级别上用小型镶木文件制作一个大文件,将它们合并为多个文件> 128 mb大小的文件或您想要的大小

how to merge multiple parquet files to single parquet file using linux or hdfs command?

第二个选项,即使用spark:读取小的Parquet文件,然后在使用Spark进行实际的数据业务处理逻辑之前,按照您的期望将它们写入相对较大的文件中(将性能因素考虑在内)。考虑)


第二个选项

即使您不知道火花工作的配置是什么,我也不知道...但是总体上,coalesce应该可以工作。对我有用。 在此示例中,我在src / main / resources下获取了小文件“ ./userdata*.parquet”(5个小文件,共110 KB) 并使用coalesce ...

合并为最后2个文件

方法: 将每个实木复合地板文件作为数据框读取,然后合并以制作单个数据框,然后coalesce

  package com.examples

import org.apache.hadoop.conf._
import org.apache.hadoop.fs._
import org.apache.log4j.{Level, Logger}
import org.apache.spark.internal.Logging
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}

import scala.collection.mutable

/** *
  * take small pegs and make a large peg
  * and coalesce it
  *
  * @author : Ram Ghadiyaram
  */
object ParquetPlay extends Logging {
  Logger.getLogger("org").setLevel(Level.OFF)


  //public FileStatus[] globStatus(Path pathPattern) throws IOException
  def main(args: Array[String]): Unit = {


 val appName = if (args.length >0) args(0) else this.getClass.getName
    val spark: SparkSession = SparkSession.builder
      .config("spark.master", "local")
      .appName(appName)
      .getOrCreate()
    val fs = FileSystem.get(new Configuration())

    val files = fs.globStatus(new Path("./userdata*.parquet")).map(_.getPath.toString)
    val dfSeq = mutable.MutableList[DataFrame]()
    println(dfSeq)
    println(files.length)
    files.foreach(x => println(x))
    val newDFs = files.map(dir => {
      dfSeq += spark.read.parquet(dir).toDF()
    })
    println(dfSeq.length)
    val finalDF = dfSeq.reduce(_ union _)
      .toDF
    finalDF.show(false)
    println(System.getProperty("java.io.tmpdir"))
    println(System.getProperties.toString)
    finalDF.coalesce(2)
      .write
      .mode(SaveMode.Overwrite)
      .parquet(s"${System.getProperty("java.io.tmpdir")}/final.parquet")
    println("done")
  }
}

结果:大小几乎相等,如下所示,共有2个文件...在示例中再次生成了小文件,但是在您的情况下,由于文件大小为500KB,大约有600个文件,因此您可以看到文件的大小,并且可以决定{ {1}}(您期望的分区数)

enter image description here

第三种选择:正如评论中提到的 Minh (原始张贴者)...可能存在高度压缩的大文件,压缩后可能会变小这个。

答案 1 :(得分:0)

这是Spark当前具有的权衡(版本3.0应该可以解决),因为任务数应包含在1x1映射到文件数的映射中...因此,任务数越大,性能越好但从分区的角度来看确实不理想,因为在这种情况下文件可能很小。

另一个问题是,在大多数情况下,由于压缩算法不再具有有关键的信息,因此最终重新分区的数据集的数量将不断增长。对于现实生活中的大数据而言,这是一个主要问题,因为磁盘空间占用将大大增加。对于非常嵌套的数据集尤其如此。

一种解决方案是将数据集简化为简单模式,以便每次写入磁盘时都可以利用压缩算法。

希望有帮助!