火花> = 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]
akaDataFrame
)目前无法使用自定义分区程序。通常,您可以通过创建人为的分区列来解决此问题,但这不会为您提供相同的灵活性。
Spark <1.6.0:
您可以做的一件事是在创建数据之前对输入数据进行预分区 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 Optimizer的工作。这是一种非常复杂的野兽,我真的怀疑您是否可以轻松改进它,而不必深入了解其内部。
相关概念
使用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
(火花> = 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不再具有分区程序。**假设没有早期预测。如果聚合仅覆盖列的一小部分,则可能毫无收益。