web-dev-qa-db-fra.com

Spark 2.0 Dataset vs DataFrame

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:

  • Quelle est la différence entre
    • df.select("foo")
    • df.select($"foo")
  • est-ce que je comprends bien que
    • 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)
  • toutes les autres commandes, par ex. sélectionnez, .. ne sont que du sucre syntaxique. Ils ne sont pas sécurisés et une carte pourrait être utilisée à la place. Comment pourrais-je df.select("foo") type-safe sans une instruction de carte?
    • pourquoi devrais-je utiliser un UDF/UADF au lieu d'une carte (en supposant que la carte reste dans la représentation de l'ensemble de données)?
27
Georg Heiler
  1. La différence entre 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.
  2. 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.

  3. 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.

  4. 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.

  5. 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.

  • "Type de sécurité" avec 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:

29
user6910411

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
2
Kapil