Funke> = 2.3.0
SPARK-22614 macht die Bereichspartitionierung verfügbar .
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 macht die Partitionierung externer Formate in der Datenquellen-API v2 verfügbar .
Funke> = 1.6.0
In Spark> = 1.6 ist es möglich, die Partitionierung nach Spalten für die Abfrage und das Caching zu verwenden. Siehe: SPARK-11410 und SPARK-4849 unter Verwendung der folgenden repartition
Methode:
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]
Im Gegensatz zu RDDs
Spark Dataset
(einschließlich Dataset[Row]
aka DataFrame
) kann derzeit kein benutzerdefinierter Partitionierer verwendet werden. Sie können dies normalerweise beheben, indem Sie eine künstliche Partitionierungsspalte erstellen, die Ihnen jedoch nicht die gleiche Flexibilität bietet.
Funke <1.6.0:
Eine Sache, die Sie tun können, ist, Eingabedaten vorab zu partitionieren, bevor Sie eine erstellen 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)
Da für die DataFrame
Erstellung aus einer RDD
nur eine einfache Kartenphase erforderlich ist, sollte das vorhandene Partitionslayout beibehalten werden *:
assert(df.rdd.partitions == partitioned.partitions)
Auf die gleiche Weise können Sie vorhandene Partitionen neu partitionieren DataFrame
:
sqlContext.createDataFrame(
df.rdd.map(r => (r.getInt(1), r)).partitionBy(partitioner).values,
df.schema
)
Es sieht also so aus, als wäre es nicht unmöglich. Die Frage bleibt, ob es überhaupt Sinn macht. Ich werde argumentieren, dass dies die meiste Zeit nicht der Fall ist:
Die Neupartitionierung ist ein teurer Prozess. In einem typischen Szenario müssen die meisten Daten serialisiert, gemischt und deserialisiert werden. Andererseits ist die Anzahl der Vorgänge, die von vorpartitionierten Daten profitieren können, relativ gering und wird weiter eingeschränkt, wenn die interne API nicht dafür ausgelegt ist, diese Eigenschaft zu nutzen.
- tritt in einigen Szenarien bei, würde aber eine interne Unterstützung erfordern,
- Fensterfunktionsaufrufe mit passendem Partitionierer. Wie oben, beschränkt auf eine einzelne Fensterdefinition. Es ist jedoch bereits intern partitioniert, sodass die Vorpartitionierung möglicherweise redundant ist.
- einfache Aggregationen mit
GROUP BY
- es ist möglich, den Speicherbedarf der temporären Puffer ** zu reduzieren, aber die Gesamtkosten sind viel höher. Mehr oder weniger äquivalent zu groupByKey.mapValues(_.reduce)
(aktuelles Verhalten) vs reduceByKey
(Vorpartitionierung). In der Praxis unwahrscheinlich.
- Datenkomprimierung mit
SqlContext.cacheTable
. Da es so aussieht, als würde es eine Lauflängencodierung verwenden, OrderedRDDFunctions.repartitionAndSortWithinPartitions
könnte das Anwenden das Komprimierungsverhältnis verbessern.
Die Leistung hängt stark von der Verteilung der Schlüssel ab. Wenn es schief ist, führt dies zu einer suboptimalen Ressourcennutzung. Im schlimmsten Fall ist es unmöglich, den Auftrag überhaupt zu beenden.
- Ein wichtiger Punkt bei der Verwendung einer deklarativen API auf hoher Ebene besteht darin, sich von den Implementierungsdetails auf niedriger Ebene zu isolieren. Wie bereits von @dwysakowicz und @RomiKuntsman erwähnt, ist eine Optimierung eine Aufgabe des Catalyst Optimizer . Es ist ein ziemlich raffiniertes Tier, und ich bezweifle wirklich, dass Sie das leicht verbessern können, ohne viel tiefer in seine Innereien einzutauchen.
Verwandte konzepte
Partitionierung mit JDBC-Quellen :
JDBC-Datenquellen unterstützen predicates
Argumente . Es kann wie folgt verwendet werden:
sqlContext.read.jdbc(url, table, Array("foo = 1", "foo = 3"), props)
Es wird eine einzelne JDBC-Partition pro Prädikat erstellt. Beachten Sie, dass in der resultierenden Tabelle Duplikate angezeigt werden, wenn Sätze, die mit einzelnen Prädikaten erstellt wurden, nicht disjunkt sind.
partitionBy
Methode inDataFrameWriter
:
Spark DataFrameWriter
bietet eine partitionBy
Methode, mit der Daten beim Schreiben "partitioniert" werden können. Es trennt Daten beim Schreiben unter Verwendung der bereitgestellten Spalten
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")
Dies ermöglicht das Herunterdrücken von Prädikaten beim Lesen für Abfragen basierend auf dem Schlüssel:
val df1 = sqlContext.read.schema(df.schema).json("/tmp/foo.json")
df1.where($"k" === "bar")
aber es ist nicht gleichbedeutend mit DataFrame.repartition
. Insbesondere Aggregationen wie:
val cnts = df1.groupBy($"k").sum()
wird noch erfordern 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
Methode inDataFrameWriter
(Spark> = 2.0):
bucketBy
hat ähnliche Anwendungen wie partitionBy
, ist jedoch nur für Tabellen verfügbar ( saveAsTable
). Bucketing-Informationen können zur Optimierung von Joins verwendet werden:
// 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>
* Mit Partitionslayout meine ich nur eine Datenverteilung. partitioned
RDD hat keinen Partitionierer mehr. ** Vorausgesetzt, keine frühe Projektion. Wenn die Aggregation nur eine kleine Teilmenge von Spalten abdeckt, gibt es wahrscheinlich überhaupt keinen Gewinn.