Tia lửa> = 2.3.0
SPARK-22614 hiển thị phân vùng phạm vi.
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 hiển thị phân vùng định dạng bên ngoài trong API nguồn dữ liệu v2 .
Tia lửa> = 1.6.0
Trong Spark> = 1.6 có thể sử dụng phân vùng theo cột cho truy vấn và bộ đệm. Xem: SPARK-11410 và SPARK-4849 sử dụng repartition
phương pháp:
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]
Không giống như RDDs
Spark Dataset
(bao gồm Dataset[Row]
aka DataFrame
) không thể sử dụng phân vùng tùy chỉnh như bây giờ. Thông thường bạn có thể giải quyết điều đó bằng cách tạo cột phân vùng nhân tạo nhưng nó sẽ không cung cấp cho bạn tính linh hoạt tương tự.
Tia lửa <1.6.0:
Một điều bạn có thể làm là phân vùng dữ liệu đầu vào trước khi tạo 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)
Vì DataFrame
việc tạo từ RDD
chỉ yêu cầu bố trí phân vùng hiện tại của giai đoạn bản đồ đơn giản nên được giữ nguyên *:
assert(df.rdd.partitions == partitioned.partitions)
Giống như cách bạn có thể phân vùng lại hiện có DataFrame
:
sqlContext.createDataFrame(
df.rdd.map(r => (r.getInt(1), r)).partitionBy(partitioner).values,
df.schema
)
Vì vậy, có vẻ như nó không phải là không thể. Câu hỏi vẫn còn nếu nó có ý nghĩa gì cả. Tôi sẽ lập luận rằng hầu hết thời gian nó không:
Phân vùng lại là một quá trình tốn kém. Trong một kịch bản điển hình, hầu hết các dữ liệu phải được tuần tự hóa, xáo trộn và giải tuần tự hóa. Mặt khác, số lượng hoạt động có thể được hưởng lợi từ dữ liệu được phân vùng trước là tương đối nhỏ và bị hạn chế hơn nữa nếu API nội bộ không được thiết kế để tận dụng thuộc tính này.
- tham gia vào một số tình huống, nhưng nó sẽ cần hỗ trợ nội bộ,
- chức năng cửa sổ gọi với phân vùng phù hợp. Tương tự như trên, giới hạn trong một định nghĩa cửa sổ duy nhất. Nó đã được phân vùng nội bộ, vì vậy phân vùng trước có thể là dư thừa,
- tập hợp đơn giản với
GROUP BY
- có thể giảm dung lượng bộ nhớ của bộ đệm tạm thời **, nhưng chi phí tổng thể cao hơn nhiều. Nhiều hơn hoặc ít hơn tương đương với groupByKey.mapValues(_.reduce)
(hành vi hiện tại) so với reduceByKey
(phân vùng trước). Không có khả năng hữu ích trong thực tế.
- nén dữ liệu với
SqlContext.cacheTable
. Vì có vẻ như nó đang sử dụng mã hóa chiều dài chạy, áp dụng OrderedRDDFunctions.repartitionAndSortWithinPartitions
có thể cải thiện tỷ lệ nén.
Hiệu suất phụ thuộc rất nhiều vào sự phân phối các phím. Nếu nó bị lệch, nó sẽ dẫn đến việc sử dụng tài nguyên dưới mức tối ưu. Trong trường hợp xấu nhất sẽ không thể hoàn thành công việc.
- Toàn bộ quan điểm của việc sử dụng API khai báo cấp cao là tự cô lập bản thân khỏi chi tiết triển khai cấp thấp. Như đã được đề cập bởi @dwysakowicz và @RomiKuntsman, tối ưu hóa là một công việc của Trình tối ưu hóa Catalyst . Nó là một con thú khá tinh vi và tôi thực sự nghi ngờ bạn có thể dễ dàng cải thiện điều đó mà không cần lặn sâu hơn vào bên trong nó.
Các khái niệm liên quan
Phân vùng với các nguồn JDBC :
Đối số nguồn dữ liệu JDBC hỗ trợ predicates
đối số . Nó có thể được sử dụng như sau:
sqlContext.read.jdbc(url, table, Array("foo = 1", "foo = 3"), props)
Nó tạo ra một phân vùng JDBC duy nhất cho mỗi vị từ. Hãy nhớ rằng nếu các tập hợp được tạo bằng các biến vị ngữ riêng lẻ không phân biệt bạn sẽ thấy các bản sao trong bảng kết quả.
partitionBy
phương pháp trongDataFrameWriter
:
Spark DataFrameWriter
cung cấp partitionBy
phương thức có thể được sử dụng để "phân vùng" dữ liệu khi ghi. Nó phân tách dữ liệu trên ghi bằng cách sử dụng bộ cột được cung cấp
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")
Điều này cho phép vị ngữ đẩy xuống đọc cho các truy vấn dựa trên khóa:
val df1 = sqlContext.read.schema(df.schema).json("/tmp/foo.json")
df1.where($"k" === "bar")
nhưng nó không tương đương với DataFrame.repartition
. Trong các tập hợp cụ thể như:
val cnts = df1.groupBy($"k").sum()
vẫn sẽ yêu cầu 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
phương thức trongDataFrameWriter
(Spark> = 2.0):
bucketBy
có các ứng dụng tương tự như partitionBy
nhưng nó chỉ có sẵn cho các bảng ( saveAsTable
). Thông tin khóa có thể được sử dụng để tối ưu hóa các phép nối:
// 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>
* Theo cách bố trí phân vùng, ý tôi chỉ là phân phối dữ liệu. partitioned
RDD không còn là một phân vùng. ** Giả sử không có chiếu sớm. Nếu tập hợp chỉ bao gồm các tập hợp con nhỏ của cột thì có lẽ không có lợi ích gì.