สิ่งที่เปลี่ยนไปหลักๆของ version 2.x+ คือการใช้ Dataframe/Dataset แทน RDD แบบเก่าๆที่ยากกว่า:
https://databricks.com/blog/2016/06/22/apache-spark-key-terms-explained.html
ปัญหาหนึ่งที่ Kafka ต้องปวดหัวมาตลอด คือ delivery semantics อย่างไรให้โดนใจผู้ใช้ และยิ่งต้องมาผสมปนกับ Spark streaming แล้วละก็ทำให้ยิ่งยากเข้าไปใหญ่ เนื่องจากหาก pipeline ของเราไม่สามารถ handle fault tolerance และ exactly once processing ได้ อาจส่งผลเสียต่องานบางประเภทที่ต้องการความแม่นยำสูง
แต่เดิม Kafka + Spark streaming นั้นใช้ วิธี Receiver แต่ปัจจุบันนั้นเลิกใช้แล้ว หันไปใช้ Kafka direct แทน โดยการมองให้ Kafka buffer = File system ธรรมดาๆ
บทนี้ผมจึงพูดเรื่องของ Kafka กับ Spark Streaming แบบใหม่กัน
enable.auto.commit = false
เนื่องจากโดยธรรมชาติ Kafka จะ auto commit เมื่อได้ records จาก Kafka แต่เพราะเราไม่รู้ว่า executor nodes เราจะตายระหว่างการคิดหรือไม่ เราจึงจะทำการ commit manually เองcommitAsync
เราลองมาดูกรณีที่เกิด failures กันครับ
Spark Streaming ถูกอออกแบบมาบนพื้นฐานของ batch processing ทำให้โดนวิจารณ์ว่ามันไม่ใช่ true streaming engine (1.x) มาตลอดเพราะ
ลองแอบมองไปที่ stream processing ตัวอื่นๆกันว่าเค้าไปไกลกันขนาดไหน
- Flink ไปไกลถึงขั้นทำ end-to-end exactly once + strong consistency via snapshots and savepoints (ใช้ทั้ง Rockdb + snapshots + HDFS + Kafka ช่วย)
- ในขณะที่ Kafka Streams ก็เทพเหมือนกัน เนื่องจากพี่แกมี Kafka อยู่แล้ว แกเลย commit changelog จาก Rockdb ไปยัง topic เลย กรณีพังก็แค่ให้ rockdb replay changelog ใน topic เราใหม่
และนั้นคือเหตุผลที่ Spark streaming ถูกชุบชีวิตใหม่ด้วย Spark Structured Streaming
Spark Structured Streaming ถูกสร้างอยู่บนพื้นฐานของ Dataframe โดยการมอง unbounded data streams ให้เป็น virtual tables ใหญ่ๆที่ทุกๆ records จะมา append ต่อท้ายไปเรื่อยๆเพื่อความเร็วในการคิดและดึงประสิทธิภาพจาก Dataframe/Dataset ให้สูงที่สุด
https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html#programming-model
เนื่องจากงานบางประเภทจำเป็นต้องเก็บ intermediate value เพื่อใช้ในการทำ stateful processing เช่น groupby, count, sum Spark structured streaming จำเป็นต้องหาวิธีจัดการ save ค่า state ชั่วคราวไว้สำหรับ trigger ในรอบต่อๆไป จึงต้องทำการ dump ค่าใน internal memory ลงไปใน persistent store อย่าง HDFS
https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html#programming-model
อย่าสับสนกันครับ เพราะจริงๆ Checkpoints มี 2 แบบ (อ่านต่อเพิ่มเติมได้ที่ Spark streaming doc)
อีกเรื่องที่ Spark เพิ่มเข้ามาคือ concept ของ event time เพื่อใช้จัดการกับ late data ได้ ซึ่งมีประโยชน์มากๆในการทำ aggregation เพราะเพิ่มความแม่นยำกว่าการใช้ processing time แบบเดิมๆเพียงอย่างเดียว
Event time คือ timestamp ของ event ที่เกิดขึ้นจริงๆบนโลก เช่น หากเราเล่นเกมบนมือถือแล้วชนะ ตัวเกมก็จะสร้าง event time ขึ้นมาและส่งไปยัง server เพื่อบอกว่าเราชนะเกมนี้แล้วนะ เอาไปขึ้น leaderboard เลย แต่หากแบตดันหมดกระทันหัน ตัว event นี้ก็จะถูก delay ออกไป และกว่าเราจะหาที่ชาร์ตแบตเจอ ก็อาจช้าไปเป็นชั่วโมง หากเป็น Spark แบบเก่าจะมองแค่ Processing time หรือเวลาที่ event ถูก process ในระบบ เมื่อเป็นแบบนี้มันก็คงไม่แฟร์กับคนเล่นเกมเพราะแทนที่เราจะได้เป็นที่ 1 ของ leaderboard กลับกลายเป็นช้าไปเป็นชั่วโมง
เนื่องจากเราต้องวุ่นวายกับทั้ง Event time และ Processing time คำถามคือจะทำยังไงให้ Spark รู้ว่า window ใดควรจะหยุดการ compute แล้ว ซึ่งนั้นคือประโยชน์ของ Watermark คือเป็นตัวบอกว่า เห้ย นายควรปิด window รอบนี้แล้วเพราะ late event time มันเกินค่า threshold ที่เรารับได้
วิธีคิด Watermark คือ max(event time) - your watermark time จะได้ range ที่เรายอมรับ late data นั้นให้ยังคงอยู่ใน windows นั้นๆได้
โจทย์ใหญ่อีกโจทย์ที่ Spark ต้องแก้คือการให้ Spark หลุดออกจาก Micro-batching ให้ได้เพื่อเป็น native continuous streaming เหมือนที่ Flink และ Kafka Streams เป็น เพื่อประสิทธิภาพที่ดีขึ้น และช่วย resources ให้ดีขึ้นเพราะไม่จำเป็นต้อง schedule และ trigger Spark jobs ทุกๆครั้ง แต่ใช้การ submit long running job ทิ้งไว้นานๆครั้งเดียวเลย
ข่าวดีคือ Spark มี Spark continuous streaming แล้วครับ ส่วนข่าวร้ายคือยังเป็น Experiment mode อยู่เอง