হালনাগাদ
এই উত্তরটি যদিও সবকিছু এখন আরও ভাল হয়, বৈধ এবং তথ্যপূর্ণ এখনও 2.2 / 2.3, যোগ, যার জন্য বিল্ট ইন এনকোডার সমর্থন যেহেতু Set
, Seq
, Map
, Date
, Timestamp
, এবং BigDecimal
। যদি আপনি কেবল কেস ক্লাস এবং সাধারণ স্কালাল প্রকারের সাথে ধরণের তৈরি করতে থাকেন তবে কেবল অন্তর্নিহিত দ্বারা আপনার ভাল হওয়া উচিত SQLImplicits
।
দুর্ভাগ্যক্রমে, এটিতে কার্যত কিছুই যোগ করা হয়নি। খুঁজছেন @since 2.0.0
যে Encoders.scala
বা SQLImplicits.scala
খুঁজে বের করে কিছু বেশিরভাগই আদিম প্রকার (এবং কেস ক্লাস কিছু টোয়েকিং) সঙ্গে না। সুতরাং, প্রথমটি বলার জন্য: কাস্টম শ্রেণীর এনকোডারদের জন্য বর্তমানে কোনও সত্যিকারের ভাল সমর্থন নেই । উপায়টির বাইরে যাওয়ার সাথে সাথে কিছু কৌশল অনুসরণ করা হয় যা একটি ভাল কাজ করে যা আমরা কখনই আশা করতে পারি, আমাদের বর্তমানে আমাদের কাছে যা আছে তা প্রদত্ত। একটি সুস্পষ্ট দাবি অস্বীকার হিসাবে: এটি পুরোপুরি কার্যকর হবে না এবং আমি সমস্ত সীমাবদ্ধতা পরিষ্কার এবং সামনে করার জন্য যথাসাধ্য চেষ্টা করব।
ঠিক সমস্যা কি
আপনি যখন কোনও ডেটাসেট বানাতে চান, তখন স্পার্কের জন্য একটি এনকোডার প্রয়োজন হয় (অভ্যন্তরীণ স্পার্ক এসকিউএল উপস্থাপনায় এবং টাইপ টির একটি জেভিএম বস্তু রূপান্তর করতে) যা সাধারণত কোনও থেকে ইমপ্লিটের মাধ্যমে স্বয়ংক্রিয়ভাবে তৈরি হয় SparkSession
, বা স্ট্যাটিক পদ্ধতিতে কল করে স্পষ্টভাবে তৈরি করা যেতে পারে on Encoders
"( দস্তাবেজগুলিcreateDataset
থেকে নেওয়া ) একটি এনকোডার ফর্ম নিতে হবে Encoder[T]
যেখানে T
আপনি যে প্রকারের এনকোডিং হয়। প্রথম পরামর্শটি হ'ল import spark.implicits._
(যা আপনাকে এই অন্তর্নিহিত এনকোডার দেয় ) এবং দ্বিতীয় পরামর্শটি হ'ল এনকোডার সম্পর্কিত ফাংশনগুলির এই সেটটি ব্যবহার করে নিখুঁত এনকোডারকে স্পষ্টভাবে পাস করা ।
নিয়মিত ক্লাসগুলির জন্য কোনও এনকোডার নেই, তাই
import spark.implicits._
class MyObj(val i: Int)
// ...
val d = spark.createDataset(Seq(new MyObj(1),new MyObj(2),new MyObj(3)))
আপনাকে নিম্নলিখিত অন্তর্ভুক্ত সম্পর্কিত সংকলন সময় ত্রুটি দেবে:
কোনও ডেটাসেটে সঞ্চিত প্রকারের জন্য এনকোডার খুঁজে পাওয়া যায়নি। প্রিমিটিভ টাইপ (ইনট, স্ট্রিং, ইত্যাদি) এবং প্রোডাক্ট টাইপ (কেস ক্লাস) sqlContext.implicits আমদানি করে সমর্থিত __ অন্যান্য ধরণের সিরিয়ালাইজ করার জন্য সমর্থন ভবিষ্যতে প্রকাশিত হবে
যাইহোক, আপনি কিছু শ্রেণীর উপরের ত্রুটিটি কেবল প্রসারিত করার জন্য যা কিছু প্রকারের জন্য আবদ্ধ করেন তা Product
ত্রুটি বিভ্রান্তিকরভাবে রানটাইম হতে দেরি করে, তাই
import spark.implicits._
case class Wrap[T](unwrap: T)
class MyObj(val i: Int)
// ...
val d = spark.createDataset(Seq(Wrap(new MyObj(1)),Wrap(new MyObj(2)),Wrap(new MyObj(3))))
ঠিকঠাক কম্পাইল করে তবে রানটাইমের সাথে ব্যর্থ হয়
java.lang.UnsupportedOperationException: MyObj এর জন্য কোনও এনকোডার পাওয়া যায় নি
এর কারণ হ'ল যে এনকোডারগুলি স্পার্কটি ইমপ্লিটগুলি দিয়ে তৈরি করে তা আসলে রানটাইমে (স্কাল রিল্ফেকশনের মাধ্যমে) তৈরি হয়। এই ক্ষেত্রে, সংকলনের সময় সমস্ত স্পার্ক চেকগুলি হ'ল বাইরেরতম শ্রেণিটি প্রসারিত হয় Product
(যা সমস্ত কেস ক্লাসগুলি করে) এবং এটি রানটাইম এ উপলব্ধি করে যে এটি এখনও কী করতে হবে তা জানে না MyObj
(যদি আমি তৈরি করার চেষ্টা করি তবে একই সমস্যা দেখা দেয়) এ Dataset[(Int,MyObj)]
- স্পার্ক রানটাইম চালিয়ে যাওয়ার সময় পর্যন্ত অপেক্ষা করে MyObj
)। এগুলি কেন্দ্রীয় সমস্যা যা সমাধানের গুরুতর প্রয়োজন:
- কিছু ক্লাস যা
Product
সর্বদা রানটাইম এবং ক্র্যাশ হওয়া সত্ত্বেও সংকলন প্রসারিত করে
- নেস্টেড প্রকারের জন্য কাস্টম এনকোডারগুলিতে পাস করার কোনও উপায় নেই (আমার কাছে এমন কোনও এনকোডার স্পার্ক খাওয়ানোর কোনও উপায় নেই
MyObj
যে এটি তখন কীভাবে এনকোড করতে হয় Wrap[MyObj]
বা কীভাবে জানে (Int,MyObj)
)।
শুধু ব্যবহার kryo
প্রত্যেকে যে সমাধানটির পরামর্শ দেয় তা হ'ল kryo
এনকোডার ব্যবহার করা ।
import spark.implicits._
class MyObj(val i: Int)
implicit val myObjEncoder = org.apache.spark.sql.Encoders.kryo[MyObj]
// ...
val d = spark.createDataset(Seq(new MyObj(1),new MyObj(2),new MyObj(3)))
এটি যদিও খুব ক্লান্তিকর হয়ে ওঠে। বিশেষত যদি আপনার কোডটি সব ধরণের ডেটাসেটগুলিতে চালাচ্ছে, যোগদান করছে, গোষ্ঠীকরণ করছে You সুতরাং, কেন কেবল এমন একটি অন্তর্নিহিত না করে যা স্বয়ংক্রিয়ভাবে এগুলি করে?
import scala.reflect.ClassTag
implicit def kryoEncoder[A](implicit ct: ClassTag[A]) =
org.apache.spark.sql.Encoders.kryo[A](ct)
এবং এখন, দেখে মনে হচ্ছে আমি যা খুশি তাই করতে পারি (নীচের উদাহরণটি spark-shell
যেখানে spark.implicits._
স্বয়ংক্রিয়ভাবে আমদানি করা হয় সেখানে কাজ করবে না )
class MyObj(val i: Int)
val d1 = spark.createDataset(Seq(new MyObj(1),new MyObj(2),new MyObj(3)))
val d2 = d1.map(d => (d.i+1,d)).alias("d2") // mapping works fine and ..
val d3 = d1.map(d => (d.i, d)).alias("d3") // .. deals with the new type
val d4 = d2.joinWith(d3, $"d2._1" === $"d3._1") // Boom!
বা প্রায়। সমস্যাটি হ'ল kryo
স্পার্কের সাহায্যে ড্যাটাসেটে প্রতিটি সারি একটি ফ্ল্যাট বাইনারি অবজেক্ট হিসাবে স্টোর করা যায়। জন্য map
, filter
, foreach
যে যথেষ্ট, কিন্তু মত অপারেশনের জন্য join
, স্পার্ক সত্যিই এই কলাম বিভক্ত করা প্রয়োজন। d2
বা এর জন্য স্কিমাটি পরীক্ষা করে d3
দেখেন যে এখানে কেবল একটি বাইনারি কলাম রয়েছে:
d2.printSchema
// root
// |-- value: binary (nullable = true)
টিপলসগুলির জন্য আংশিক সমাধান
সুতরাং, স্কালায় জড়িতদের যাদুটি ব্যবহার করে (আরও 6.26.3 ওভারলোডিং রেজোলিউশনে ), আমি নিজেকে অন্ততপক্ষে টিউপসগুলির পক্ষে যথাসম্ভব ভাল কাজ করতে সক্ষম এমন একটি ধারাবাহিকতা তৈরি করতে পারি এবং বিদ্যমান প্রভাবগুলির সাথে ভালভাবে কাজ করব:
import org.apache.spark.sql.{Encoder,Encoders}
import scala.reflect.ClassTag
import spark.implicits._ // we can still take advantage of all the old implicits
implicit def single[A](implicit c: ClassTag[A]): Encoder[A] = Encoders.kryo[A](c)
implicit def tuple2[A1, A2](
implicit e1: Encoder[A1],
e2: Encoder[A2]
): Encoder[(A1,A2)] = Encoders.tuple[A1,A2](e1, e2)
implicit def tuple3[A1, A2, A3](
implicit e1: Encoder[A1],
e2: Encoder[A2],
e3: Encoder[A3]
): Encoder[(A1,A2,A3)] = Encoders.tuple[A1,A2,A3](e1, e2, e3)
// ... you can keep making these
তারপরে, এই প্রভাবগুলিতে সজ্জিত, আমি আমার কলের উপরের উদাহরণটি তৈরি করতে পারি, কিছু কলামের নাম পরিবর্তন করেও
class MyObj(val i: Int)
val d1 = spark.createDataset(Seq(new MyObj(1),new MyObj(2),new MyObj(3)))
val d2 = d1.map(d => (d.i+1,d)).toDF("_1","_2").as[(Int,MyObj)].alias("d2")
val d3 = d1.map(d => (d.i ,d)).toDF("_1","_2").as[(Int,MyObj)].alias("d3")
val d4 = d2.joinWith(d3, $"d2._1" === $"d3._1")
আমি এখনো মূর্ত আউট নি প্রত্যাশিত tuple নাম পেতে কিভাবে ( _1
, _2
তাদের পুনঃনামকরনের ছাড়া, ...) ডিফল্টরূপে - অন্য কেউ এই সঙ্গে চারপাশে খেলা করতে চায় কিনা, এই যেখানে নাম "value"
চালু পরার এবং এই যেখানে tuple হয় নামগুলি সাধারণত যুক্ত করা হয়। তবে মূল কথাটি হ'ল আমার কাছে এখন একটি সুন্দর কাঠামোগত স্কিমা রয়েছে:
d4.printSchema
// root
// |-- _1: struct (nullable = false)
// | |-- _1: integer (nullable = true)
// | |-- _2: binary (nullable = true)
// |-- _2: struct (nullable = false)
// | |-- _1: integer (nullable = true)
// | |-- _2: binary (nullable = true)
সুতরাং, সংক্ষেপে, এই কর্মসূচী:
- আমাদের টিপলসের জন্য আলাদা কলাম পেতে দেয় (যাতে আমরা আবার টিপলগুলিতে যোগ দিতে পারি, হ্যাঁ!)
- আমরা আবার কেবল প্রভাবগুলির উপর নির্ভর করতে পারি (সুতরাং
kryo
পুরো জায়গা জুড়ে যাওয়ার দরকার নেই )
- প্রায় পুরোপুরি পিছনের দিকে সামঞ্জস্যপূর্ণ
import spark.implicits._
(কিছু নাম পরিবর্তনের সাথে জড়িত)
- সিরিয়ালযুক্ত বাইনারি কলামগুলিতে আমাদের যোগদান করতে দেয় না
kyro
, ক্ষেত্রগুলি ছেড়ে দিন alone
- কিছু টিপল কলামকে "মান" হিসাবে নামকরণের অপ্রীতিকর পার্শ্ব-প্রতিক্রিয়া রয়েছে (যদি প্রয়োজন হয় তবে এটি রূপান্তর করে
.toDF
, নতুন কলামের নাম উল্লেখ করে এবং কোনও ডেটাশেটে রূপান্তরিত করে পূর্বাবস্থায় ফেলা যায় - এবং স্কিমা নামগুলি যোগদানের মাধ্যমে সংরক্ষণ করা হবে বলে মনে হয়) , যেখানে তাদের সবচেয়ে বেশি প্রয়োজন))
সাধারণভাবে ক্লাসের জন্য আংশিক সমাধান
এই এক কম আনন্দদায়ক এবং কোন ভাল সমাধান আছে। তবে, এখন যেহেতু আমাদের উপরে উপরের টিউপল সলিউশন রয়েছে, আমার কাছে অন্য একটি উত্তর থেকে অন্তর্নিহিত রূপান্তর সমাধানটি কিছুটা কম বেদনাদায়ক হবে কারণ আপনি আপনার আরও জটিল ক্লাসগুলিকে টুপলে রূপান্তর করতে পারেন। তারপরে, ডেটাसेट তৈরি করার পরে, আপনি সম্ভবত ডেটা ফ্রেম পদ্ধতির ব্যবহার করে কলামগুলির নাম পরিবর্তন করবেন। যদি সবকিছু ঠিকঠাক হয় তবে এটি সত্যিই একটি উন্নতি, কারণ আমি এখন আমার ক্লাসের ক্ষেত্রগুলিতে যোগদান করতে পারি। আমি যদি কেবল একটি ফ্ল্যাট বাইনারি kryo
সিরিয়ালাইজার ব্যবহার করি তবে এটি সম্ভব হত না।
এখানে একটি উদাহরণ যে সবকিছু একটি বিট নেই: আমি একটি বর্গ আছে MyObj
কোন ধরনের ক্ষেত্র রয়েছে Int
, java.util.UUID
এবং Set[String]
। প্রথম নিজের যত্ন নেয়। দ্বিতীয়টি, যদিও আমি সিরিয়ালটি ব্যবহার করে kryo
আরও কার্যকর হতে পারি যদি এটি হিসাবে সংরক্ষণ করা হয় String
(যেহেতু UUID
সাধারণত এমন কিছু যা আমি এর সাথে যোগ দিতে চাই)। তৃতীয়টি সত্যিই কেবল বাইনারি কলামে অন্তর্ভুক্ত।
class MyObj(val i: Int, val u: java.util.UUID, val s: Set[String])
// alias for the type to convert to and from
type MyObjEncoded = (Int, String, Set[String])
// implicit conversions
implicit def toEncoded(o: MyObj): MyObjEncoded = (o.i, o.u.toString, o.s)
implicit def fromEncoded(e: MyObjEncoded): MyObj =
new MyObj(e._1, java.util.UUID.fromString(e._2), e._3)
এখন, আমি এই যন্ত্রপাতিটি ব্যবহার করে একটি দুর্দান্ত স্কিমা দিয়ে একটি ডেটাসেট তৈরি করতে পারি:
val d = spark.createDataset(Seq[MyObjEncoded](
new MyObj(1, java.util.UUID.randomUUID, Set("foo")),
new MyObj(2, java.util.UUID.randomUUID, Set("bar"))
)).toDF("i","u","s").as[MyObjEncoded]
এবং স্কিমা আমাকে সঠিক নামগুলি দিয়ে এবং প্রথম দুটি দুটি জিনিসই আমার বিপরীতে যোগ দিতে পারে তার সাথে আমি কলামগুলি দেখায়।
d.printSchema
// root
// |-- i: integer (nullable = false)
// |-- u: string (nullable = true)
// |-- s: binary (nullable = true)
ExpressionEncoder
জেএসওএন সিরিয়ালাইজেশন ব্যবহার করে কি কাস্টম ক্লাস তৈরি করা সম্ভব ? আমার ক্ষেত্রে আমি টিপলস নিয়ে দূরে যেতে পারি না, এবং ক্রিও আমাকে একটি বাইনারি কলাম দেয় ..