私はSpark 1.4.0でSpark SQLとDataFrameを使い始めました。私はScalaでDataFramesにカスタムパーティショナを定義したいのですが、これを行う方法を見ていません。
私が取り組んでいるデータテーブルの1つに、アカウント別のトランザクションのリストが含まれています。
Account Date Type Amount
1001 2014-04-01 Purchase 100.00
1001 2014-04-01 Purchase 50.00
1001 2014-04-05 Purchase 70.00
1001 2014-04-01 Payment -150.00
1002 2014-04-01 Purchase 80.00
1002 2014-04-02 Purchase 22.00
1002 2014-04-04 Payment -120.00
1002 2014-04-04 Purchase 60.00
1003 2014-04-02 Purchase 210.00
1003 2014-04-03 Purchase 15.00
少なくとも最初は、ほとんどの計算はアカウント内のトランザクション間で行われます。そのため、アカウントのすべてのトランザクションが同じSparkパーティションに入るようにデータを分割したいと思います。
しかし、私はこれを定義する方法を見ていません。 DataFrameクラスには 'repartition(Int)'というメソッドがあり、作成するパーティションの数を指定できます。しかし、RDDに指定できるような、DataFrame用のカスタムパーティショナーを定義するために使用できる方法はありません。
ソースデータはParquetに保存されています。私は、ParquetにDataFrameを書くとき、あなたが分割するカラムを指定できることを見ました、それでおそらく私はParquetに 'Account'カラムによってデータを分割するように言うことができます。しかし、何百万ものアカウントがある可能性があり、もし私がParquetを正しく理解していれば、それは各アカウントに対して別々のディレクトリを作成するでしょう、それでそれは合理的な解決策のように聞こえませんでした。
アカウントのすべてのデータが同じパーティションに入るようにSparkにこのDataFrameをパーティション分割させる方法はありますか?
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 で外部フォーマットのパーティション化を公開しています。
Spark> = 1.6では、クエリとキャッシュのためにカラムによるパーティショニングを使うことが可能です。参照: SPARK-11410 および SPARK-4849repartition
メソッドの使用:
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]
a.k.a DataFrame
を含む)は今のところカスタムパーティショナーを使用できません。通常は、人工的なパーティション化列を作成することで対処できますが、同じ柔軟性は得られません。
できることの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
を適用すると圧縮率を向上させることができます。パフォーマンスは鍵の配布に大きく依存します。歪んでいると、リソースの使用率が最適とは言えません。最悪のシナリオでは、仕事を完全に終えることは不可能です。
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には、もはやパーティショナがありません。 **早期の予測がないと仮定する。集計が列の小さなサブセットのみを対象としている場合は、おそらく何も得られないでしょう。
Spark <1.6では、普通のHiveContext
ではなくSqlContext
を作成する場合は、 HiveQLDISTRIBUTE BY colX...
を使用できます(N個のリデューサーのそれぞれが重複しない範囲になるようにします)例えば、x)&CLUSTER BY colX...
([並べ替え]および[並べ替え]のショートカット)。
df.registerTempTable("partitionMe")
hiveCtx.sql("select * from partitionMe DISTRIBUTE BY accountId SORT BY accountId, date")
これがSpark DF apiにどのように適合するのかわからない。これらのキーワードは、通常のSqlContextではサポートされていません(HiveContextを使用するためにHiveメタストアを用意する必要はありません)。
編集:Spark 1.6以降ではこれがネイティブのDataFrame APIに含まれています。
以下によって返されるDataFrameを使用してください。
yourDF.orderBy(account)
DataFrameでpartitionBy
を使用する明示的な方法は、PairRDDでのみ使用できますが、DataFrameを並べ替えるときは、LogicalPlanでそれを使用するので、各アカウントで計算を行う必要があるときに役立ちます。
私はちょうど同じ問題に出くわしました。アカウントで分割したいデータフレームがあります。 「アカウントのすべてのトランザクションが同じSparkパーティションに収まるようにデータを分割したい」と言った場合は、規模とパフォーマンスの面でそれが望ましいと思いますが、コードはそれに依存しません(以下のように)。 mapPartitions()
など)ね。
私はRDDを使ってこれを行うことができました。しかし、これがあなたにとって許容できる解決策であるかどうかはわかりません。 RDDとしてDFを使用できるようになったら、 repartitionAndSortWithinPartitions
を適用してデータのカスタム再分割を実行できます。
これが私が使ったサンプルです:
class DatePartitioner(partitions: Int) extends Partitioner {
override def getPartition(key: Any): Int = {
val start_time: Long = key.asInstanceOf[Long]
Objects.hash(Array(start_time)) % partitions
}
override def numPartitions: Int = partitions
}
myRDD
.repartitionAndSortWithinPartitions(new DatePartitioner(24))
.map { v => v._2 }
.toDF()
.write.mode(SaveMode.Overwrite)
それで、ある種の答えで始めるために:) - あなたはできません
私は専門家ではありませんが、DataFrameを理解する限り、それらはrddと同じではなく、DataFrameにはPartitionerのようなものはありません。
一般的にDataFrameの考えは、そのような問題自体を処理する別のレベルの抽象化を提供することです。 DataFrameに対するクエリは、RDDに対する操作にさらに変換される論理プランに変換されます。あなたが提案したパーティション分割はおそらく自動的に適用されるか、少なくとも適用されるでしょう。
SparkSQLがある種の最適な仕事を提供するとは思わないのであれば、コメントで示唆されているように常にDataFrameをRDD [Row]に変換できます。