Apache Flink Stateful Stream Processing
เจาะลึก Stateful Streaming ใน Apache Flink
เรียนรู้ความสามารถเชิงลึกของ Apache Flink ในการประมวลผลสตรีมมิ่งแบบมีสถานะ (Stateful) โดยเน้นที่การจัดการเวลา, Watermarking, Complex Event Processing (CEP), และการสเกลสถานะด้วย RocksDB
- Step 1: Data Ingestion & Event Processing
- Step 2: Distributed Computation & State Management
- Step 3: Orchestration & Resource Allocation
1. Event Time vs. Processing Time & Watermarking
ทฤษฎีและกลไกการทำงาน
Learning Progression
- 🟢 Basic Level: ปูพื้นฐานภาษาเข้าใจง่าย - เข้าใจคอนเซปต์ภาพรวมและการทำงานเบื้องต้นแบบไม่ซับซ้อน
- 🟡 Intermediate Level: โค้ด/คอนฟิกไวยากรณ์จริง - การเขียนโค้ดและตั้งค่าพารามิเตอร์เพื่อใช้งานในระบบจริง
- 🔴 Professional Level: Under-the-hood & Performance/FinOps - กลไกเบื้องลึก การจูนประสิทธิภาพ และการจัดการทรัพยากร
🏢 Real-World Enterprise Scenario
เคสระบบการเงิน/Big Tech: การรองรับ Transaction จำนวนมหาศาลต่อวินาทีพร้อมประกัน Data Integrity สูงสุด ลด Latency และประหยัดค่าใช้จ่ายผ่าน FinOps practices
ในการประมวลผลสตรีม การจัดการเวลาเป็นสิ่งสำคัญ Processing Time คือเวลาปัจจุบันของเครื่องเซิร์ฟเวอร์ที่กำลังรันโค้ด ซึ่งทำงานเร็วแต่ผลลัพธ์อาจไม่สม่ำเสมอหากนำข้อมูลมาประมวลผลใหม่ Event Time คือเวลาที่เหตุการณ์นั้นเกิดขึ้นจริง (มักแฝงมาในตัวข้อมูล) เพื่อรองรับข้อมูลที่มาถึงไม่เรียงตามลำดับ (Out-of-order) ใน Event Time, Flink ใช้ Watermarks เพื่อบอกระบบว่าเวลาได้เดินไปถึงไหนแล้ว และคาดว่าจะไม่มีข้อมูลที่เก่ากว่า Watermark ส่งมาอีก
ตัวอย่างโค้ดหรือการตั้งค่า
// กำหนด Watermark Strategy สำหรับจัดการ Event Time
WatermarkStrategy<MyEvent> strategy = WatermarkStrategy
.<MyEvent>forBoundedOutOfOrderness(Duration.ofSeconds(5)) // ยอมให้ข้อมูลมาช้าได้ 5 วินาที
.withTimestampAssigner((event, timestamp) -> event.getEventTime());
DataStream<MyEvent> stream = env.addSource(new MyEventSource())
.assignTimestampsAndWatermarks(strategy);
Use Case ในชีวิตจริง
ระบบคำนวณปริมาณการซื้อขายหุ้นทุกๆ นาที ปัญหาเน็ตเวิร์กอาจทำให้คำสั่งซื้อส่งมาถึงเซิร์ฟเวอร์ช้า การใช้ Event Time และ Watermark ที่หน่วงเวลา 5 วินาที จะทำให้ระบบคำนวณยอดของนาทีที่ 10:00-10:01 ได้ถูกต้อง แม้คำสั่งของเวลา 10:00:59 จะมาถึงระบบตอน 10:01:04 ก็ตาม
ข้อควรระวังและวิธีแก้
Pitfall: การตั้งเวลาหน่วงของ Watermark นานเกินไปจะทำให้ระบบหน่วง (Latency สูง) และเปลือง Memory ในการเก็บ State รอข้อมูล ส่วนถ้าตั้งน้อยไป ข้อมูลจะถูกทิ้ง (Dropped) เยอะ
Mitigation: ตรวจสอบพฤติกรรมความล่าช้าของข้อมูลจริง และใช้ฟีเจอร์ Side Output ของ Flink เพื่อเก็บข้อมูลที่มาช้า (Late Data) ไปบันทึกแยกไว้โดยไม่กระทบ Pipeline หลัก
2. Complex Event Processing (CEP)
ทฤษฎีและกลไกการทำงาน
Flink CEP ช่วยให้เราสามารถค้นหารูปแบบ (Pattern) ของเหตุการณ์ที่ซับซ้อนในกระแสข้อมูลที่หลั่งไหลมาไม่ขาดสาย โดยใช้ State Machine ในการติดตามลำดับ เช่น "พบ Event A แล้วตามด้วย Event B ภายใน 10 วินาที" ระบบจะตรวจสอบเงื่อนไขที่ตั้งไว้แบบ Real-time ทันที
ตัวอย่างโค้ดหรือการตั้งค่า
// กำหนด Pattern: ล็อกอิน 'ล้มเหลว' แล้วตามด้วยล็อกอิน 'ล้มเหลว' อีกครั้งภายใน 10 วินาที
Pattern<LoginEvent, ?> pattern = Pattern.<LoginEvent>begin("first_fail")
.where(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent event) {
return !event.isSuccess();
}
})
.next("second_fail")
.where(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent event) {
return !event.isSuccess();
}
})
.within(Time.seconds(10));
PatternStream<LoginEvent> patternStream = CEP.pattern(loginStream, pattern);
Use Case ในชีวิตจริง
ระบบความปลอดภัยของ E-commerce ใช้ CEP ดักจับพฤติกรรมสวมรอย (Account Takeover) หากพบการล็อกอินผิดพลาดจาก IP เดียวกันหลายครั้ง ตามด้วยการล็อกอินสำเร็จและเปลี่ยนรหัสผ่านทันทีภายใน 2 นาที ระบบจะแจ้งเตือนและระงับบัญชีชั่วคราวแบบอัตโนมัติ
ข้อควรระวังและวิธีแก้
Pitfall: การเขียน Pattern ที่กว้างเกินไปและมีระยะเวลา (within) นานเกินไป จะทำให้ระบบต้องเก็บสถานะที่เป็นไปได้ทั้งหมดไว้จนเกิด State Explosion
Mitigation: ควรกำหนดเงื่อนไขให้เฉพาะเจาะจง ใช้ระยะเวลาที่สั้นที่สุดเท่าที่เป็นไปได้ และต้องมีการจัดกลุ่มข้อมูล (keyBy(userId)) เสมอ เพื่อจำกัดขอบเขตการคำนวณให้อยู่ภายในผู้ใช้แต่ละราย
3. RocksDB State Backend
ทฤษฎีและกลไกการทำงาน
Flink จำเป็นต้องมี State Backends เพื่อเก็บข้อมูลระหว่างการประมวลผล RocksDB State Backend จะเก็บ State ลงใน RocksDB (Database ฝังตัวแบบ Key-Value) ซึ่งเซฟลงดิสก์ของ TaskManager แทนที่จะเก็บใน RAM (เหมือน HashMap) ทำให้ Flink สามารถจัดการ State ขนาดใหญ่ระดับ Terabytes ได้โดยไม่เจอโจทย์ Out of Memory (OOM)
ตัวอย่างโค้ดหรือการตั้งค่า
# ตั้งค่าในไฟล์ flink-conf.yaml
state.backend.type: rocksdb
state.backend.rocksdb.localdir: /data/flink/rocksdb
state.checkpoints.dir: hdfs://namenode:9000/flink-checkpoints
state.backend.incremental: true # เปิดใช้งาน Incremental Checkpoints
Use Case ในชีวิตจริง
แอปเรียกรถ (Ride-hailing) ต้องจดจำตำแหน่งและสถานะของคนขับนับล้านคนตลอดเวลา เมื่อ State สะสมมากขึ้นจนเกิน RAM 32GB ของแต่ละโหนด RocksDB จะช่วยเขียนข้อมูลลง SSD ทำให้ระบบสามารถประมวลผลต่อไปได้อย่างเสถียร ไม่ล่ม
ข้อควรระวังและวิธีแก้
Pitfall: การอ่านเขียน State ถี่มากอาจทำให้เกิดคอขวดที่ Disk I/O และทำให้ระยะเวลาทำ Checkpoint นานขึ้นมาก
Mitigation: ควรใช้ดิสก์ SSD NVMe ที่มีความเร็ว I/O สูงๆ และต้องเปิดใช้งาน Incremental Checkpoints เพื่อให้ Flink แบ็กอัปเฉพาะไฟล์ RocksDB SST ที่เปลี่ยนแปลงเท่านั้น ซึ่งช่วยลดเวลาการทำ Checkpoint ได้มหาศาล
Weekend Sandbox Challenge 🚀
สร้างระบบตรวจจับการทุจริต: เขียนแอปพลิเคชัน Flink แบบสตรีมมิ่งเพื่อดักจับธุรกรรมต้องสงสัย
- สร้างข้อมูลจำลองของธุรกรรม (userId, amount, location, timestamp)
- สร้าง Event Time Watermark Strategy โดยยอมให้มาช้าได้ 2 วินาที
- ใช้ Flink CEP ค้นหา Pattern ที่ผู้ใช้โอนเงิน > $1000 ในประเทศหนึ่ง แล้วโอนอีกครั้งในอีกประเทศหนึ่งภายในระยะเวลา 10 นาที
- ตั้งค่าให้แอปใช้ RocksDB และทดลองรันสั่ง Savepoint
คำถามสัมภาษณ์ระดับ Senior (Interview Q&A)
Q: Watermarks แก้ปัญหาข้อมูลที่มาถึงช้า (Late-arriving data) ได้อย่างไร และจะเกิดอะไรขึ้นกับข้อมูลที่มาช้ากว่า Watermark?
A: Watermark คือตัวบอกเวลาของระบบประมวลผล เพื่อยืนยันว่าระบบจะไม่รอข้อมูลที่เก่ากว่าเวลานี้แล้ว ทำให้ Operator เช่น Window สามารถปิดและสรุปผลลัพธ์ออกไปได้ ข้อมูลที่เก่ากว่า Watermark จะถือว่าเป็น "Late Data" ซึ่งโดยค่าเริ่มต้น Flink จะทิ้ง (Drop) ข้อมูลเหล่านั้น แต่เราสามารถตั้งค่า allowed lateness เพื่ออัปเดตผลลัพธ์ย้อนหลังได้ หรือโยนข้อมูลเข้า Side Output ไปจัดการแยกเพื่อไม่ให้ข้อมูลสูญหาย
Q: ในสถานการณ์ใดที่คุณจะเลือกใช้ RocksDB State Backend แทน HashMap State Backend?
A: เราจะใช้ HashMap สำหรับระบบที่ State ไม่ใหญ่มากและต้องการความเร็วระดับ Microsecond แต่จะเปลี่ยนไปใช้ RocksDB เมื่อ State เริ่มใหญ่เกิน Memory ของ JVM (เช่น มี Key จำนวนมาก, หรือคำนวณ Window ข้ามสัปดาห์) แม้ RocksDB จะมีความหน่วงจากกระบวนการ Serialize/Deserialize และ Disk I/O (ระดับ Millisecond) แต่ก็แลกมาด้วยความสามารถในการ Scale ได้อย่างไร้ขีดจำกัดและไม่เกิด OutOfMemory Error