使用Spark通过s3a将镶木地板文件写入s3非常慢

时间:2016-04-29 01:05:35

标签: scala amazon-s3 apache-spark apache-spark-sql parquet

我正在尝试使用parquetAmazon S3文件写到Spark 1.6.1。我生成的小parquet一旦写入~2GB,所以数据并不多。我试图将Spark作为我可以使用的平台来证明。

基本上我要做的是设置star schema dataframes,然后我要把那些桌子写成实木复合地板。数据来自供应商提供的csv文件,我使用Spark作为ETL平台。我目前在ec2(r3.2xlarge)中有一个3节点集群,因此执行器上的内存120GB总共有16个内核。

输入文件总共大约22GB,我现在正在提取大约2GB的数据。最后,当我开始加载完整数据集时,这将是几TB。

这是我的spark / scala pseudocode

  def loadStage(): Unit = {
    sc.hadoopConfiguration.set("fs.s3a.buffer.dir", "/tmp/tempData")
    sc.hadoopConfiguration.set("spark.sql.parquet.output.committer.class","org.apache.spark.sql.parquet.DirectParquetOutputCommitter")
    sc.hadoopConfiguration.set("spark.sql.hive.convertMetastoreParquet","false")
    var sqlCtx = new SQLContext(sc)


    val DataFile = sc.textFile("s3a://my-bucket/archive/*/file*.gz")

    //Setup header table/df
    val header_rec = DataFile.map(_.split("\\|")).filter(x=> x(0) == "1")
    val headerSchemaDef = "market_no,rel_date,field1, field2, field3....."
    val headerSchema = StructType(headerSchemaDef.split(",").map(fieldName => StructField(fieldName, StringType,false)))
    val headerRecords = header_rec.map(p => Row(p(3), p(8), p(1), p(2), p(4), p(5), p(6) ))
    val header = sqlCtx.createDataFrame(headerRecords, headerSchema)
    header.registerTempTable("header")
    sqlCtx.cacheTable("header")


    //Setup fact table/df
    val fact_recs = DataFile.map(_.split("\\|")).filter(x=> x(0) == "2")
    val factSchemaDef = "market_no,rel_date,field1, field2, field3....."
    val factSchema = StructType(factSchemaDef.split(",").map(fieldName => StructField(fieldName, StringType,false)))
    val records = fact_recs.map(p => Row(p(11), p(12), p(1), p(2), p(3), p(4), p(5), p(6), p(7), p(8), p(9), p(10)))
    val df = sqlCtx.createDataFrame(records, factSchema)
    df.registerTempTable("fact")

    val results = sqlCtx.sql("select fact.* from header inner join fact on fact.market_no = header.market_no and fact.rel_date = header.rel_date")


    println(results.count())



    results.coalesce(1).write.mode(SaveMode.Overwrite).parquet("s3a://my-bucket/a/joined_data.parquet")


  }

465884512行的计数大约需要2分钟。对拼花的写入需要 38分钟

据我所知,coalesce对写作的驱动程序进行了随机播放....但是它所花费的时间让我觉得我做错了。如果没有coalesce,这仍然需要15分钟,IMO仍然太长,并且给了我大量的parquet文件。我想每天有一个大文件,我将拥有。我有代码来执行按字段值进行分区,而且速度也很慢。我也尝试将此输出到csv,这需要约1小时。

另外,当我提交作业时,我并没有真正设置运行时道具。我的一项工作的控制台统计信息是:

  • Alive Workers:2
  • 使用中的核心数:16总计,16使用
  • 正在使用的内存:总计117.5 GB,使用107.5 GB
  • 应用程序:1个正在运行,5个已完成
  • 驱动程序:0正在运行,0已完成
  • 状态:ALIVE

4 个答案:

答案 0 :(得分:18)

Spark默认值会在I / O操作期间导致大量(可能)不必要的开销,尤其是在写入S3时。 This article对此进行了更全面的讨论,但您有两种设置需要考虑更改。

  • 使用DirectParquetOutputCommitter。默认情况下,Spark会将所有数据保存到临时文件夹,然后再移动这些文件。使用DirectParquetOutputCommitter将通过直接写入S3输出路径来节省时间

    • No longer available in Spark 2.0+
      • 如jira票中所述,目前的解决方案是
          
            
        1. 将您的代码切换为使用s3a和Hadoop 2.7.2+;它全面变得更好,在Hadoop 2.8中变得更好,并且是s3guard的基础
        2.   
        3. 使用Hadoop FileOutputCommitter并将mapreduce.fileoutputcommitter.algorithm.version设置为2
        4.   

    - 默认情况下,自Spark 1.5 关闭模式合并时关闭模式合并。如果启用了架构合并,则驱动程序节点将扫描所有文件以确保一致的架构。这特别昂贵,因为它不是分布式操作。通过执行

    确保关闭此功能

    val file = sqx.read.option("mergeSchema", "false").parquet(path)

答案 1 :(得分:4)

直接输出提交器已从spark代码库中消失;你要在自己的JAR中编写自己的/恢复已删除的代码。如果你这样做,在你的工作中关闭推测,并知道其他失败也会导致问题,问题是"数据无效"。

更明亮的是,Hadoop 2.8将增加一些S3A加速,专门用于读取S3的优化二进制格式(ORC,Parquet);有关详细信息,请参阅HADOOP-11694。有些人正在努力将Amazon Dynamo用于一致的元数据存储,该存储应该能够在工作结束时执行强大的O(1)提交。

答案 2 :(得分:0)

加快Spark向S3写入速度的一种直接方法是使用EMRFS S3-optimized Committer

但是,如果您使用s3a,则此提交者cannot be used

  

未使用EMRFS S3优化的提交器时

     

在以下情况下不使用提交器:

When writing to HDFS

-> When using the S3A file system

When using an output format other than Parquet, such as ORC or text

When using MapReduce or Spark's RDD API

我已经在AWS EMR 5.26上测试了这种差异,并且使用s3://比s3a://快15%-30%(但仍然很慢)。

我设法完成这种复制/写入的最快方法是将Parquet写入本地HDFS,然后使用s3distcp复制到S3。在一种特定情况下(几百个小文件),这比将DataFrame直接写入Parquet到S3快5倍。

答案 3 :(得分:0)

我也遇到了这个问题。除了其他人所说的以外,以下是AWS的完整解释:https://aws.amazon.com/blogs/big-data/improve-apache-spark-write-performance-on-apache-parquet-formats-with-the-emrfs-s3-optimized-committer/

在实验过程中,仅将FileOutCommiter v2(从v1更改为v2)可提高3-4倍的写入率。

self.sc._jsc.hadoopConfiguration().set("mapreduce.fileoutputcommitter.algorithm.version", "2")