スパーク> = 2.3.0
SPARK-22614は範囲分割を公開します。
val partitionedByRange = df.repartitionByRange(42, $"k")
partitionedByRange.explain
// == Parsed Logical Plan ==
// 'RepartitionByExpression ['k ASC NULLS FIRST], 42
// +- AnalysisBarrier Project [_1#2 AS k#5, _2#3 AS v#6]
//
// == Analyzed Logical Plan ==
// k: string, v: int
// RepartitionByExpression [k#5 ASC NULLS FIRST], 42
// +- Project [_1#2 AS k#5, _2#3 AS v#6]
// +- LocalRelation [_1#2, _2#3]
//
// == Optimized Logical Plan ==
// RepartitionByExpression [k#5 ASC NULLS FIRST], 42
// +- LocalRelation [k#5, v#6]
//
// == Physical Plan ==
// Exchange rangepartitioning(k#5 ASC NULLS FIRST, 42)
// +- LocalTableScan [k#5, v#6]
SPARK-22389は、データソースAPI v2で外部フォーマットパーティションを公開します。
スパーク> = 1.6.0
Spark> = 1.6では、クエリとキャッシングに列によるパーティション化を使用できます。参照:SPARK-11410およびSPARK-4849を使用するrepartition
方法:
val df = Seq(
("A", 1), ("B", 2), ("A", 3), ("C", 1)
).toDF("k", "v")
val partitioned = df.repartition($"k")
partitioned.explain
// scala> df.repartition($"k").explain(true)
// == Parsed Logical Plan ==
// 'RepartitionByExpression ['k], None
// +- Project [_1#5 AS k#7,_2#6 AS v#8]
// +- LogicalRDD [_1#5,_2#6], MapPartitionsRDD[3] at rddToDataFrameHolder at <console>:27
//
// == Analyzed Logical Plan ==
// k: string, v: int
// RepartitionByExpression [k#7], None
// +- Project [_1#5 AS k#7,_2#6 AS v#8]
// +- LogicalRDD [_1#5,_2#6], MapPartitionsRDD[3] at rddToDataFrameHolder at <console>:27
//
// == Optimized Logical Plan ==
// RepartitionByExpression [k#7], None
// +- Project [_1#5 AS k#7,_2#6 AS v#8]
// +- LogicalRDD [_1#5,_2#6], MapPartitionsRDD[3] at rddToDataFrameHolder at <console>:27
//
// == Physical Plan ==
// TungstenExchange hashpartitioning(k#7,200), None
// +- Project [_1#5 AS k#7,_2#6 AS v#8]
// +- Scan PhysicalRDD[_1#5,_2#6]
RDDs
Spark Dataset
(Dataset[Row]
別名を含むDataFrame
)とは異なり、現時点ではカスタムパーティショナーを使用できません。通常は、人工的なパーティション列を作成することで対処できますが、同じ柔軟性は得られません。
スパーク<1.6.0:
できることの1つは、作成する前に入力データを事前分割することです。 DataFrame
import org.apache.spark.sql.types._
import org.apache.spark.sql.Row
import org.apache.spark.HashPartitioner
val schema = StructType(Seq(
StructField("x", StringType, false),
StructField("y", LongType, false),
StructField("z", DoubleType, false)
))
val rdd = sc.parallelize(Seq(
Row("foo", 1L, 0.5), Row("bar", 0L, 0.0), Row("??", -1L, 2.0),
Row("foo", -1L, 0.0), Row("??", 3L, 0.6), Row("bar", -3L, 0.99)
))
val partitioner = new HashPartitioner(5)
val partitioned = rdd.map(r => (r.getString(0), r))
.partitionBy(partitioner)
.values
val df = sqlContext.createDataFrame(partitioned, schema)
からのDataFrame
作成にRDD
は単純なマップフェーズのみが必要なので、既存のパーティションレイアウトを保持する必要があります*:
assert(df.rdd.partitions == partitioned.partitions)
既存のパーティションを再分割できるのと同じ方法DataFrame
:
sqlContext.createDataFrame(
df.rdd.map(r => (r.getInt(1), r)).partitionBy(partitioner).values,
df.schema
)
だから不可能ではないようです。それがまったく理にかなっている場合、問題は残ります。私はほとんどの場合そうではないと主張します:
再パーティション化はコストのかかるプロセスです。典型的なシナリオでは、ほとんどのデータをシリアル化、シャッフル、および非シリアル化する必要があります。一方、事前にパーティション化されたデータの恩恵を受けることができる操作の数は比較的少なく、内部APIがこのプロパティを活用するように設計されていない場合はさらに制限されます。
- 一部のシナリオでは参加しますが、内部サポートが必要になります。
- ウィンドウ関数は、一致するパーティショナーで呼び出します。上記と同じで、単一のウィンドウ定義に限定されます。ただし、すでに内部でパーティション分割されているため、事前のパーティション分割は冗長になる可能性があります。
- を使用した単純な集計
GROUP BY
-一時バッファー**のメモリフットプリントを削減することは可能ですが、全体的なコストははるかに高くなります。groupByKey.mapValues(_.reduce)
(現在の動作)対reduceByKey
(事前パーティション化)とほぼ同等です。実際に役立つことはほとんどありません。
- によるデータ圧縮
SqlContext.cacheTable
。ランレングスエンコーディングを使用しているように見えるOrderedRDDFunctions.repartitionAndSortWithinPartitions
ため、適用すると圧縮率が向上する可能性があります。
パフォーマンスは、キーの配布に大きく依存します。これが歪んでいると、リソースの使用率が最適化されません。最悪のシナリオでは、ジョブを完了することがまったく不可能になります。
- 高レベルの宣言型APIを使用する全体のポイントは、低レベルの実装の詳細から自分を分離することです。@dwysakowiczと@RomiKuntsmanですでに述べたように、最適化はCatalystオプティマイザの仕事です。それはかなり洗練された獣であり、私はあなたがその内部を深く掘り下げることなく簡単にそれを改善できることを本当に疑っています。
関連する概念
JDBCソースを使用したパーティション化:
JDBCデータソースはpredicates
引数をサポートします。次のように使用できます。
sqlContext.read.jdbc(url, table, Array("foo = 1", "foo = 3"), props)
述語ごとに単一のJDBCパーティションを作成します。個々の述語を使用して作成されたセットがばらばらでない場合、結果のテーブルに重複が表示されることに注意してください。
partitionBy
の方法 DataFrameWriter
:
Spark DataFrameWriter
はpartitionBy
、書き込み時にデータを「パーティション化」するために使用できるメソッドを提供します。提供された列のセットを使用して書き込み時にデータを分離します
val df = Seq(
("foo", 1.0), ("bar", 2.0), ("foo", 1.5), ("bar", 2.6)
).toDF("k", "v")
df.write.partitionBy("k").json("/tmp/foo.json")
これにより、キーに基づくクエリの読み取り時に述語プッシュダウンが可能になります。
val df1 = sqlContext.read.schema(df.schema).json("/tmp/foo.json")
df1.where($"k" === "bar")
ただし、とは異なりDataFrame.repartition
ます。特に次のような集計:
val cnts = df1.groupBy($"k").sum()
まだ必要になりますTungstenExchange
:
cnts.explain
// == Physical Plan ==
// TungstenAggregate(key=[k#90], functions=[(sum(v#91),mode=Final,isDistinct=false)], output=[k#90,sum(v)#93])
// +- TungstenExchange hashpartitioning(k#90,200), None
// +- TungstenAggregate(key=[k#90], functions=[(sum(v#91),mode=Partial,isDistinct=false)], output=[k#90,sum#99])
// +- Scan JSONRelation[k#90,v#91] InputPaths: file:/tmp/foo.json
bucketBy
DataFrameWriter
(Spark> = 2.0)のメソッド:
bucketBy
と同様のアプリケーションpartitionBy
がありますが、テーブル(saveAsTable
)でのみ使用できます。バケット情報を使用して、結合を最適化できます。
// Temporarily disable broadcast joins
spark.conf.set("spark.sql.autoBroadcastJoinThreshold", -1)
df.write.bucketBy(42, "k").saveAsTable("df1")
val df2 = Seq(("A", -1.0), ("B", 2.0)).toDF("k", "v2")
df2.write.bucketBy(42, "k").saveAsTable("df2")
// == Physical Plan ==
// *Project [k#41, v#42, v2#47]
// +- *SortMergeJoin [k#41], [k#46], Inner
// :- *Sort [k#41 ASC NULLS FIRST], false, 0
// : +- *Project [k#41, v#42]
// : +- *Filter isnotnull(k#41)
// : +- *FileScan parquet default.df1[k#41,v#42] Batched: true, Format: Parquet, Location: InMemoryFileIndex[file:/spark-warehouse/df1], PartitionFilters: [], PushedFilters: [IsNotNull(k)], ReadSchema: struct<k:string,v:int>
// +- *Sort [k#46 ASC NULLS FIRST], false, 0
// +- *Project [k#46, v2#47]
// +- *Filter isnotnull(k#46)
// +- *FileScan parquet default.df2[k#46,v2#47] Batched: true, Format: Parquet, Location: InMemoryFileIndex[file:/spark-warehouse/df2], PartitionFilters: [], PushedFilters: [IsNotNull(k)], ReadSchema: struct<k:string,v2:double>
* パーティションレイアウトとは、データ分散のみを意味します。partitioned
RDDにはパーティショナーがなくなりました。**初期の予測がないと仮定します。集計が列の小さなサブセットしかカバーしていない場合は、おそらくまったく利益がありません。