Spark 2.0 Dataset против DataFrame

начиная с искры 2.0.1 У меня возникли некоторые вопросы. Я прочитал много документации, но пока не нашел достаточных ответов:

  • В чем разница между
    • df.select("foo")
    • df.select($"foo")
  • Правильно ли я понимаю, что
    • myDataSet.map(foo.someVal) является typeafe и не будет преобразовываться в RDD, но оставаться в представлении DataSet/без дополнительных накладных расходов (производительность для 2.0.0)
  • все другие команды, например. выберите,.. просто синтаксический сахар. Они не являются типичными, и вместо них можно использовать карту. Как я мог df.select("foo") безопасный тип без оператора карты?
    • Почему я должен использовать UDF/UADF вместо карты (предполагая, что карта остается в представлении набора данных)?

Ответ 1

  • Разница между df.select("foo") и df.select($"foo") является сигнатурой. Первый принимает по крайней мере один String, более поздний один или более Columns. За пределами этого нет никакой практической разницы.
  • 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.

  • Нет. В общем, другие методы не являются синтаксическим сахаром и создают значительно другой план выполнения. Например:

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

    По сравнению с планом, показанным перед тем, как он сможет напрямую обращаться к столбцу. Это не столько ограничение API, сколько результат в функциональной семантике.

  • Как я мог бы df.select( "foo" ) безопасный тип без оператора карты?

    Такого варианта нет. В то время как типизированные столбцы позволяют преобразовать статически Dataset в другой статически типизированный Dataset:

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

    нет безопасного типа. Существуют и другие попытки включить оптимизированные по типу операции типа как типизированные агрегаты, но этот экспериментальный API.

  • почему я должен использовать 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 является спорным, когда Dataset преобразуется с использованием метода as. Поскольку форма данных не кодируется с использованием сигнатуры, компилятор может проверить только существование Encoder.

Похожие вопросы:

Ответ 2

Spark Dataset намного эффективнее, чем Spark Dataframe. Небольшой пример - вы можете создавать Dataframe Row, Tuple или любой примитивный тип данных, но Dataset дает вам возможность создавать Dataset любого не примитивного типа. т.е. вы можете буквально создать Dataset типа объекта.

Пример:

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

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