web-dev-qa-db-ja.com

spark-csvを使用して単一のCSVファイルを書き込む

私は https://github.com/databricks/spark-csv を使用しています。= 1つのCSVファイルを書き込もうとしていますが、できません。フォルダを作成しています。

パスやファイル名などのパラメータを取り、そのCSVファイルに書き込むScala関数が必要です。

82
user1735076

各パーティションは個別に保存されるため、複数のファイルを含むフォルダーを作成しています。単一の出力ファイル(フォルダ内にある)が必要な場合はrepartitionを使用できます(アップストリームデータが大きい場合は推奨されますが、シャッフルが必要です)。

df
   .repartition(1)
   .write.format("com.databricks.spark.csv")
   .option("header", "true")
   .save("mydata.csv")

またはcoalesce

df
   .coalesce(1)
   .write.format("com.databricks.spark.csv")
   .option("header", "true")
   .save("mydata.csv")

保存前のデータフレーム:

すべてのデータはmydata.csv/part-00000に書き込まれます。このオプションを使う前に、何が起こっているのか、そしてすべてのデータを1人のワーカーに転送するのにかかるコストはいくらか理解しておいてください。レプリケーションで分散ファイルシステムを使用している場合、データは複数回転送されます。最初に単一のワーカーにフェッチされ、続いてストレージノードに分散されます。

あるいは、コードをそのままにして、catHDFS getmerge のような汎用ツールを使用して、後ですべての部分を単純にマージすることもできます。

133
zero323

あなたがHDFSでSparkを走らせているならば、私はcsvファイルを普通に書き、マージをするためにHDFSを利用することによって問題を解決してきました。私はそれをSpark(1.6)で直接やっています。

import org.Apache.hadoop.conf.Configuration
import org.Apache.hadoop.fs._

def merge(srcPath: String, dstPath: String): Unit =  {
   val hadoopConfig = new Configuration()
   val hdfs = FileSystem.get(hadoopConfig)
   FileUtil.copyMerge(hdfs, new Path(srcPath), hdfs, new Path(dstPath), true, hadoopConfig, null) 
   // the "true" setting deletes the source files once they are merged into the new output
}


val newData = << create your dataframe >>


val outputfile = "/user/feeds/project/outputs/subject"  
var filename = "myinsights"
var outputFileName = outputfile + "/temp_" + filename 
var mergedFileName = outputfile + "/merged_" + filename
var mergeFindGlob  = outputFileName

    newData.write
        .format("com.databricks.spark.csv")
        .option("header", "false")
        .mode("overwrite")
        .save(outputFileName)
    merge(mergeFindGlob, mergedFileName )
    newData.unpersist()

私がこのトリックを学んだ場所を覚えていないことができます、しかしそれはあなたのために働くかもしれません。

33
Minkymorgan

私はここで少しゲームに遅れるかもしれませんが、coalesce(1)またはrepartition(1)を使うことは小さなデータセットのために働くかもしれませんが、大きなデータセットはすべて1つのノードの1つのパーティションに投げられるでしょう。これはOOMエラーをスローするか、せいぜいゆっくりと処理する可能性があります。

Hadoop APIの FileUtil.copyMerge() 関数を使用することを強くお勧めします。これにより、出力が単一のファイルにマージされます。

編集 - これにより、データがエグゼキュータノードではなくドライバに効果的にもたらされます。単一のexecutorがドライバよりもRAMを使用する場合はCoalesce()は問題ありません。

編集2:copyMerge()はHadoop 3.0で削除されています。最新バージョンでの作業方法について詳しくは、以下のスタックオーバーフローの記事を参照してください。 Hadoop Hadoop 3.0でCopyMergeを実行する方法

22
etspaceman

データブリックを使用していて、1人のワーカーですべてのデータをRAMに収めることができる場合(したがって.coalesce(1)を使用する場合)、dbfsを使用して結果のCSVファイルを見つけて移動できます。

val fileprefix= "/mnt/aws/path/file-prefix"

dataset
  .coalesce(1)       
  .write             
//.mode("overwrite") // I usually don't use this, but you may want to.
  .option("header", "true")
  .option("delimiter","\t")
  .csv(fileprefix+".tmp")

val partition_path = dbutils.fs.ls(fileprefix+".tmp/")
     .filter(file=>file.name.endsWith(".csv"))(0).path

dbutils.fs.cp(partition_path,fileprefix+".tab")

dbutils.fs.rm(fileprefix+".tmp",recurse=true)

ファイルがワーカーのRAMに収まらない場合は、FileUtils.copyMerge()を使用するという chaotic3quilibriumの提案を検討してください 。私はこれをしていません、そして、それが可能であるかどうかをまだ知りません、例えば、S3で。

この答えは、この質問に対する以前の答え、および提供されたコードスニペットの私自身のテストに基づいています。 私はもともとそれをDatabricks に投稿し、ここで再公開しています。

私が見つけたdbfsのrmの再帰的なオプションのための最もよいドキュメンテーションは Databricksフォーラム にあります。

14
Josiah Yoder

Minkymorganから変更されたS3用のソリューション。

一時パーティション・ディレクトリー・パスを(最終パスとは異なる名前で)srcPathとして渡し、単一の最終csv/txtをdestPathとして渡すだけです。元のディレクトリーを削除したい場合は、deleteSourceも指定します。

/**
* Merges multiple partitions of spark text file output into single file. 
* @param srcPath source directory of partitioned files
* @param dstPath output path of individual path
* @param deleteSource whether or not to delete source directory after merging
* @param spark sparkSession
*/
def mergeTextFiles(srcPath: String, dstPath: String, deleteSource: Boolean): Unit =  {
  import org.Apache.hadoop.fs.FileUtil
  import Java.net.URI
  val config = spark.sparkContext.hadoopConfiguration
  val fs: FileSystem = FileSystem.get(new URI(srcPath), config)
  FileUtil.copyMerge(
    fs, new Path(srcPath), fs, new Path(dstPath), deleteSource, config, null
  )
}
2
John Zhu

あなたはrdd.coalesce(1, true).saveAsTextFile(path)を使うことができます

データを単一ファイルとしてpath/part-00000に保存します。

2
Gourav

保存する前に1つのパーティションに再分割/合体します(まだフォルダを取得できますが、その中に1つの部分ファイルがあります)。

2