web-dev-qa-db-ja.com

なぜSpark数千のファイルを作成するbucketByでsaveAsTableですか?

コンテキスト

Spark 2.0.1、クラスターモードでのspark-submit。 hdfsから寄木細工のファイルを読んでいます。

val spark = SparkSession.builder
      .appName("myApp")
      .config("Hive.metastore.uris", "thrift://XXX.XXX.net:9083")
      .config("spark.sql.sources.bucketing.enabled", true)
      .enableHiveSupport()
      .getOrCreate()

val df = spark.read
              .format("parquet")
              .load("hdfs://XXX.XX.X.XX/myParquetFile")

dfでグループ化された50個のバケットを持つuseridをHiveテーブルに保存しています:

df0.write
   .bucketBy(50, "userid")
   .saveAsTable("myHiveTable")

今、hdfs /user/Hive/warehouseでHiveウェアハウスを見ると、myHiveTableという名前のフォルダーがあります。その中にはたくさんのpart-*.parquetファイルがあります。 50個のファイルがあると予想されます。しかし、いいえ、3201ファイルがあります!!!!パーティションごとに64個のファイルがあるのはなぜですか? Hiveテーブルとして保存したファイルごとに、パーティションごとに異なる数のファイルがあります。すべてのファイルは非常に小さく、それぞれ数十Kb!

さらに、異なるuseridの数は、myParquetFile内の1 000 000についてです。

質問

50個では​​なく3201個のファイルがフォルダーにあるのはなぜですか!彼らは何ですか?

このテーブルをDataFrameに読み戻し、パーティションの数を出力すると:

val df2 = spark.sql("SELECT * FROM myHiveTable") 
println(df2.rdd.getNumPartitions)

パーティションの数は正しく50です。データがuseridによって正しくパーティション化されていることを確認しました。

私の大規模なデータセット3Tbの1つに対して、文字通り〜millionのファイルを作成した1000個のパーティションを持つテーブルを作成します!これは、1048576のディレクトリ項目制限を超え、org.Apache.hadoop.hdfs.protocol.FSLimitException$MaxDirectoryItemsExceededExceptionを提供します

質問

作成されるファイルの数は何に依存しますか?

質問

作成されるファイルの数を制限する方法はありますか?

質問

これらのファイルについて心配する必要がありますか?これらのファイルをすべて持つことで、df2のパフォーマンスが低下しますか?問題があるため、あまり多くのパーティションを作成するべきではないと常に言われています。

質問

私はこの情報を見つけました Hive Dynamic Partitioning tips ファイルの数はマッパーの数に関係しているかもしれないということです。 Hiveテーブルへの挿入中にdistribute byを使用することをお勧めします。 Sparkでどうすればよいですか?

質問

上記のリンクのように問題が実際にある場合は、こちら MapR-FSにデータを挿入した後にHiveテーブルのファイル番号を制御する方法Hive.merge.mapfilesHive.merge.mapredfilesなどのオプションを使用してすべての小さなファイルをマージすることをお勧めしますマップ削減ジョブの後。 Sparkにはこのオプションがありますか?

15
astro_asz

spark sqlを使用してください。sqlはHiveContextを使用してHiveテーブルにデータを書き込むため、テーブルスキーマで設定したバケット数を使用します。

 SparkSession.builder().
  config("Hive.exec.dynamic.partition", "true").
  config("Hive.exec.dynamic.partition.mode", "nonstrict").
  config("Hive.execution.engine","tez").
  config("Hive.exec.max.dynamic.partitions","400").
  config("Hive.exec.max.dynamic.partitions.pernode","400").
  config("Hive.enforce.bucketing","true").
  config("optimize.sort.dynamic.partitionining","true").
  config("Hive.vectorized.execution.enabled","true").
  config("Hive.enforce.sorting","true").
  enableHiveSupport().getOrCreate()

spark.sql(s"insert into hiveTableName partition (partition_column) select * from  myParquetFile")

sparkのバケット実装は、指定されたバケットサイズの数を受け入れません。各パーティションは個別のファイルに書き込みを行うため、各バケットに多くのファイルが作成されます。

このリンクを参照してください https://www.slideshare.net/databricks/Hive-bucketing-in-Apache-spark-with-tejas-patil

enter image description here これが役立つことを願っています。

ラビ

12
Ravikumar

回避策を見つけることができました(Spark 2.1)。これはファイル数の問題を解決しますが、パフォーマンスに影響を与える可能性があります。

dataframe
  .withColumn("bucket", pmod(hash($"bucketColumn"), lit(numBuckets)))
  .repartition(numBuckets, $"bucket")
  .write
  .format(fmt)
  .bucketBy(numBuckets, "bucketColumn")
  .sortBy("bucketColumn")
  .option("path", "/path/to/your/table")
  .saveAsTable("table_name")

Sparkのバケットアルゴリズムは、バケット列値のMurmurHash3の正のmodを実行すると思います。これは単純にそのロジックを複製し、データを再分割して、各パーティションにバケットのすべてのデータが含まれるようにします。

パーティショニング+バケティングでも同じことができます。

dataframe
  .withColumn("bucket", pmod(hash($"bucketColumn"), lit(numBuckets)))
  .repartition(numBuckets, $"partitionColumn", $"bucket")
  .write
  .format(fmt)
  .partitionBy("partitionColumn")
  .bucketBy(numBuckets, "bucketColumn")
  .sortBy("bucketColumn")
  .option("path", "/path/to/your/table")
  .saveAsTable("table_name")

Csv形式を使用して、3つのパーティションと5つのバケットでローカルにテストしました(パーティションとバケットの両方の列は単なる数字です):

$ tree .
.
├── _SUCCESS
├── partitionColumn=0
│   ├── bucket=0
│   │   └── part-00004-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00000.csv
│   ├── bucket=1
│   │   └── part-00003-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00001.csv
│   ├── bucket=2
│   │   └── part-00002-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00002.csv
│   ├── bucket=3
│   │   └── part-00004-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00003.csv
│   └── bucket=4
│       └── part-00001-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00004.csv
├── partitionColumn=1
│   ├── bucket=0
│   │   └── part-00002-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00000.csv
│   ├── bucket=1
│   │   └── part-00004-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00001.csv
│   ├── bucket=2
│   │   └── part-00002-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00002.csv
│   ├── bucket=3
│   │   └── part-00001-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00003.csv
│   └── bucket=4
│       └── part-00003-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00004.csv
└── partitionColumn=2
    ├── bucket=0
    │   └── part-00000-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00000.csv
    ├── bucket=1
    │   └── part-00001-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00001.csv
    ├── bucket=2
    │   └── part-00001-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00002.csv
    ├── bucket=3
    │   └── part-00003-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00003.csv
    └── bucket=4
        └── part-00000-c2f2b7b5-40a1-4d24-8c05-084b7a05e399_00004.csv

3つのパーティションすべてのバケット= 0です(これらはすべて同じ値であることがわかります)。

$ paste partitionColumn=0/bucket=0/part-00004-5f860e5c-f2c2-4d52-8035-aa00e4432770_00000.csv partitionColumn=1/bucket=0/part-00002-5f860e5c-f2c2-4d52-8035-aa00e4432770_00000.csv partitionColumn=2/bucket=0/part-00000-5f860e5c-f2c2-4d52-8035-aa00e4432770_00000.csv | head
0   0   0
4   4   4
6   6   6
16  16  16
18  18  18
20  20  20
26  26  26
27  27  27
29  29  29
32  32  32

実際、追加のバケットインデックスが好きでした。しかし、そうしないと、書き込みの直前にバケット列をドロップでき、パーティションごとにnumBuckets個のファイル数を取得できます。

10
Bill Kuang