Іскра> = 2.3.0
СПАРК-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]
СПАРК-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
ІскриDataset
(включаючи Dataset[Row]
ака 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
)
Так виглядає, що це не неможливо. Залишається питання, чи має це взагалі сенс. Я заперечу, що більшість часу це не так:
Перерозподіл - це дорогий процес. За типового сценарію більшість даних мають бути серіалізовані, перетасовані та десеріалізовані. З іншого боку, кількість операцій, які можуть отримати користь від попередньо розділених даних, порівняно невелика і ще більше обмежена, якщо внутрішній API не призначений для використання цієї властивості.
- приєднується до деяких сценаріїв, але це потребує внутрішньої підтримки,
- віконні функції дзвінків із відповідним учасником. Те саме, що вище, обмежено визначенням одного вікна. Він уже розділений внутрішньо, хоча попереднє розділення може бути зайвим,
- прості агрегації з
GROUP BY
- можна зменшити слід пам'яті тимчасових буферів **, але загальна вартість набагато вище. Більш-менш еквівалентний groupByKey.mapValues(_.reduce)
(поточна поведінка) проти reduceByKey
(попереднє розділення). Навряд чи стане в нагоді на практиці.
- стиснення даних за допомогою
SqlContext.cacheTable
. Оскільки виглядає, що використовується кодування довжини запуску, застосування OrderedRDDFunctions.repartitionAndSortWithinPartitions
може покращити коефіцієнт стиснення.
Продуктивність сильно залежить від розподілу клавіш. Якщо вона перекошена, це призведе до неоптимального використання ресурсів. У гіршому випадку взагалі неможливо закінчити роботу.
- Вся суть використання декларативного API високого рівня полягає в тому, щоб ізолювати себе від деталей реалізації низького рівня. Як уже згадували @dwysakowicz та @RomiKuntsman , оптимізація - це робота оптимізатора каталізаторів . Це досить витончений звір, і я дуже сумніваюся, що ви можете легко вдосконалити це, не занурившись всередину.
Пов'язані поняття
Розмежування з джерелами JDBC :
predicates
Аргумент підтримки джерел даних JDBC . Його можна використовувати наступним чином:
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 більше не є учасником. ** Якщо не передбачається ранньої прогнозування. Якщо агрегація охоплює лише невелику підмножину стовпців, мабуть, жодного посилення немає.