web-dev-qa-db-ja.com

Spark 2.0データセットとデータフレーム

spark 2.0.1で始まります。いくつかの質問を受けました。多くのドキュメントを読みましたが、これまでのところ十分な答えが見つかりませんでした。

  • 違いは何ですか
    • df.select("foo")
    • df.select($"foo")
  • 私はそれを正しく理解していますか
    • myDataSet.map(foo.someVal)はタイプセーフであり、RDDに変換されませんが、DataSet表現のままです/追加のオーバーヘッドはありません(2.0.0のパフォーマンスは賢明です)
  • 他のすべてのコマンド、たとえばselect、..は単に構文上の砂糖です。それらはタイプセーフではなく、代わりにマップを使用できます。どうすればマップステートメントなしでdf.select("foo")型保証できますか?
    • マップの代わりにUDF/UADFを使用する必要があるのはなぜですか(マップがデータセット表現に留まっていると仮定した場合)?
27
Georg Heiler
  1. df.select("foo")df.select($"foo")の違いはシグネチャです。前者は少なくとも1つのStringを受け取り、後者は0以上のColumnsを受け取ります。それを超える実用的な違いはありません。
  2. myDataSet.map(foo.someVal)型チェックが行われますが、Dataset操作はオブジェクトのRDDを使用するため、DataFrame操作と比較すると、かなりのオーバーヘッドがあります。簡単な例を見てみましょう:

    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]
    

    ご覧のとおり、この実行プランはすべてのフィールドへのアクセスを必要とし、DeserializeToObjectを実行する必要があります。

  3. いいえ。一般的に、他のメソッドは構文砂糖ではなく、大幅に異なる実行プランを生成します。例えば:

    ds.select($"foo").explain
    
    == Physical Plan ==
    LocalTableScan [foo#117]
    

    列に直接アクセスできるようになる前のプランと比較。これはAPIの制限ではなく、運用上のセマンティクスの違いによるものです。

  4. Mapステートメントなしでdf.select( "foo")の型保証をするにはどうすればよいですか?

    そのようなオプションはありません。型指定された列を使用すると、静的にDatasetを静的に型指定された別のDatasetに変換できます。

    ds.select($"bar".as[Int])
    

    タイプセーフはありません。タイプセーフな最適化された操作 型付き集計のような を含める他の試みがいくつかありますが、この実験的なAPIです。

  5. マップの代わりにUDF/UADFを使用する理由

    それは完全にあなた次第です。 Sparkの各分散データ構造には、独自の利点と欠点があります(たとえば、 Spark UDAFとArrayTypeをbufferSchemaのパフォーマンスの問題として使用 を参照)。

個人的には、静的に型付けされたDatasetが最も役に立たないことがわかります。

  • Dataset[Row]と同じ範囲の最適化を提供しないでください(ただし、ストレージ形式といくつかの実行プランの最適化を共有しますが、コード生成やオフヒープストレージのメリットは十分ではありません)。 DataFrame

  • 型付き変換はブラックボックスであり、オプティマイザの分析バリアを効果的に作成します。たとえば、選択(フィルター)を型付き変換にプッシュすることはできません。

    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]
    

    に比べ:

    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] 
    

    これは、述語プッシュダウンやプロジェクションプッシュダウンなどの機能に影響します。

  • ネイティブでサポートされている型の小さなサブセットのみで、RDDsほど柔軟性はありません。

  • Encodersを使用した「タイプセーフ」は、Datasetasメソッドを使用して変換された場合に問題になります。データ形式は署名を使用してエンコードされないため、コンパイラーはEncoderの存在のみを検証できます。

関連する質問:

29
user6910411

Spark Datasetは、Spark Dataframeよりも強力です。小さな例-作成できるのはDataframe of Rowだけです。 TupleまたはDataset以外の任意のプリミティブデータ型を使用すると、プリミティブ以外の型のDatasetを作成することもできます。つまり、文字どおりオブジェクト型のDatasetを作成できます。 。

例:

case class Employee(id:Int,name:String)

Dataset[Employee]   // is valid
Dataframe[Employee] // is invalid
2
Kapil