จุดประกาย> = 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 exposes รูปแบบการแบ่งภายนอกใน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]
aka DataFrame
) ไม่สามารถใช้พาร์ติชันเนอร์แบบกำหนดเองได้ในตอนนี้ โดยทั่วไปคุณสามารถจัดการสิ่งนั้นได้โดยการสร้างคอลัมน์การแบ่งพาร์ติชันเทียม แต่จะไม่ให้ความยืดหยุ่นเท่าเดิม
จุดประกาย <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
)
จึงดูเหมือนไม่ใช่เรื่องที่เป็นไปไม่ได้ คำถามยังคงอยู่หากมันสมเหตุสมผล ฉันจะเถียงว่าส่วนใหญ่ไม่ได้:
การแบ่งพาร์ติชันใหม่เป็นกระบวนการที่มีราคาแพง ในสถานการณ์ทั่วไปข้อมูลส่วนใหญ่จะต้องถูกทำให้เป็นอนุกรมสับเปลี่ยนและ deserialized ในทางกลับกันจำนวนการดำเนินการที่สามารถได้รับประโยชน์จากข้อมูลที่แบ่งพาร์ติชันล่วงหน้านั้นค่อนข้างเล็กและมีข้อ จำกัด เพิ่มเติมหาก API ภายในไม่ได้ออกแบบมาเพื่อใช้ประโยชน์จากคุณสมบัตินี้
- เข้าร่วมในบางสถานการณ์ แต่จะต้องมีการสนับสนุนภายใน
- ฟังก์ชันหน้าต่างเรียกด้วยพาร์ติชันที่ตรงกัน เช่นเดียวกับข้างต้น จำกัด ไว้ที่คำจำกัดความของหน้าต่างเดียว มีการแบ่งพาร์ติชันภายในอยู่แล้วดังนั้นการแบ่งพาร์ติชันล่วงหน้าอาจซ้ำซ้อน
- การรวมอย่างง่ายด้วย
GROUP BY
- สามารถลดรอยเท้าหน่วยความจำของบัฟเฟอร์ชั่วคราว ** แต่ค่าใช้จ่ายโดยรวมสูงกว่ามาก มากกว่าหรือน้อยกว่าเทียบเท่ากับgroupByKey.mapValues(_.reduce)
(พฤติกรรมปัจจุบัน) เทียบกับreduceByKey
(การแบ่งพาร์ติชันล่วงหน้า) ไม่น่าจะมีประโยชน์ในทางปฏิบัติ
- การบีบอัดข้อมูลด้วย
SqlContext.cacheTable
. เนื่องจากดูเหมือนว่าจะใช้การเข้ารหัสความยาวรันการใช้OrderedRDDFunctions.repartitionAndSortWithinPartitions
สามารถปรับปรุงอัตราส่วนการบีบอัดได้
ประสิทธิภาพขึ้นอยู่กับการกระจายของคีย์ หากมีการเบ้จะส่งผลให้เกิดการใช้ทรัพยากรที่ไม่เหมาะสม ในกรณีที่เลวร้ายที่สุดจะไม่สามารถทำงานให้เสร็จได้เลย
- จุดรวมของการใช้ API ที่เปิดเผยระดับสูงคือการแยกตัวเองออกจากรายละเอียดการใช้งานระดับต่ำ ดังที่ได้กล่าวไว้แล้วโดย@dwysakowiczและ@RomiKuntsmanเพิ่มประสิทธิภาพเป็นงานของตัวเร่งเพิ่มประสิทธิภาพ มันเป็นสัตว์ร้ายที่ค่อนข้างซับซ้อนและฉันสงสัยจริงๆว่าคุณสามารถปรับปรุงสิ่งนั้นได้อย่างง่ายดายโดยไม่ต้องดำน้ำลึกเข้าไปในภายใน
แนวคิดที่เกี่ยวข้อง
การแบ่งพาร์ติชันด้วยแหล่งที่มา 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 ไม่มีตัวแบ่งพาร์ติชันอีกต่อไป ** สมมติว่าไม่มีการฉายในช่วงต้น หากการรวมครอบคลุมเฉพาะส่วนย่อยของคอลัมน์อาจไม่มีผลตอบแทนใด ๆ