commençant par spark 2.0.1 J'ai eu quelques questions. J'ai lu beaucoup de documentation mais jusqu'à présent je n'ai pas trouvé de réponses suffisantes:
df.select("foo")
df.select($"foo")
myDataSet.map(foo.someVal)
est de type sécurisé et ne se convertira pas en RDD
mais restera dans la représentation DataSet/pas de surcharge supplémentaire (en termes de performances pour 2.0.0)df.select("foo")
type-safe sans une instruction de carte? df.select("foo")
et df.select($"foo")
est la signature. Le premier prend au moins un String
, le dernier zéro ou plusieurs Columns
. Il n'y a aucune différence pratique au-delà de cela.myDataSet.map(foo.someVal)
vérifie le type, mais comme toute opération Dataset
utilise RDD
d'objets, et par rapport aux opérations DataFrame
, il y a un surcoût important. Jetons un coup d'œil à un exemple simple:
case class FooBar(foo: Int, bar: String)
val ds = Seq(FooBar(1, "x")).toDS
ds.map(_.foo).explain
== Physical Plan ==
*SerializeFromObject [input[0, int, true] AS value#123]
+- *MapElements <function1>, obj#122: int
+- *DeserializeToObject newInstance(class $line67.$read$$iw$$iw$FooBar), obj#121: $line67.$read$$iw$$iw$FooBar
+- LocalTableScan [foo#117, bar#118]
Comme vous pouvez le voir, ce plan d'exécution nécessite l'accès à tous les champs et doit DeserializeToObject
.
Non. En général, les autres méthodes ne sont pas du sucre syntaxique et génèrent un plan d'exécution sensiblement différent. Par exemple:
ds.select($"foo").explain
== Physical Plan ==
LocalTableScan [foo#117]
Par rapport au plan indiqué avant, il peut accéder directement à la colonne. Ce n'est pas tant une limitation de l'API que le résultat d'une différence dans la sémantique opérationnelle.
Comment pourrais-je utiliser df.select ("foo") sans instruction de carte?
Il n'y a pas une telle option. Alors que les colonnes saisies vous permettent de transformer statiquement Dataset
en un autre _ statiquement typé Dataset
:
ds.select($"bar".as[Int])
il n'y a pas de type sûr. Il y a d'autres tentatives pour inclure des opérations optimisées de type sécurisé, comme des agrégations typées , mais cette API expérimentale.
pourquoi devrais-je utiliser un UDF/UADF au lieu d'une carte
Cela dépend entièrement de vous. Chaque structure de données distribuée dans Spark offre ses propres avantages et inconvénients (voir par exemple Spark UDAF avec ArrayType comme problèmes de performances de bufferSchema ).
Personnellement, je trouve que le Dataset
typé statiquement est le moins utile:
Ne fournissez pas la même plage d'optimisations que Dataset[Row]
(bien qu'ils partagent le format de stockage et certaines optimisations de plan d'exécution, il ne bénéficie pas pleinement de la génération de code ou du stockage hors segment) ni accès à toutes les capacités analytiques du DataFrame
.
Les transformations typées sont des boîtes noires et créent efficacement une barrière d'analyse pour l'optimiseur. Par exemple, les sélections (filtres) ne peuvent pas être poussées sur la transformation typée:
ds.groupBy("foo").agg(sum($"bar") as "bar").as[FooBar].filter(x => true).where($"foo" === 1).explain
== Physical Plan ==
*Filter (foo#133 = 1)
+- *Filter <function1>.apply
+- *HashAggregate(keys=[foo#133], functions=[sum(cast(bar#134 as double))])
+- Exchange hashpartitioning(foo#133, 200)
+- *HashAggregate(keys=[foo#133], functions=[partial_sum(cast(bar#134 as double))])
+- LocalTableScan [foo#133, bar#134]
Par rapport à:
ds.groupBy("foo").agg(sum($"bar") as "bar").as[FooBar].where($"foo" === 1).explain
== Physical Plan ==
*HashAggregate(keys=[foo#133], functions=[sum(cast(bar#134 as double))])
+- Exchange hashpartitioning(foo#133, 200)
+- *HashAggregate(keys=[foo#133], functions=[partial_sum(cast(bar#134 as double))])
+- *Filter (foo#133 = 1)
+- LocalTableScan [foo#133, bar#134]
Cela affecte des fonctionnalités telles que le pushdown de prédicat ou le pushdown de projection.
Il n'y a pas aussi flexible que RDDs
avec seulement un petit sous-ensemble de types pris en charge nativement.
Encoders
est discutable lorsque Dataset
est converti à l'aide de la méthode as
. Étant donné que la forme des données n'est pas codée à l'aide d'une signature, un compilateur peut uniquement vérifier l'existence d'un Encoder
.Questions connexes:
Spark Dataset
est bien plus puissant que Spark Dataframe
. Petit exemple - vous ne pouvez créer que Dataframe
de Row
, Tuple
ou tout type de données primitif mais Dataset
vous donne également le pouvoir de créer Dataset
de tout type non primitif. Vous pouvez donc créer littéralement Dataset
de type objet .
Ex:
case class Employee(id:Int,name:String)
Dataset[Employee] // is valid
Dataframe[Employee] // is invalid