PipeCraft

Roadmap Progress

ข้อมูลเบื้องต้นเกี่ยวกับวิศวกรรมข้อมูล (Introduction to Data Engineering) Python สำหรับ Data Engineering พื้นฐาน Linux & CLI สำหรับวิศวกรข้อมูล Git สำหรับระบบงานข้อมูลและทีมพัฒนา พื้นฐานเครือข่าย เว็บเทคโนโลยี และระบบกระจายศูนย์ (Web Fundamentals, Networking & Distributed Systems) SQL ขั้นสูง (Window Functions & Optimization) ฐานข้อมูลไม่ใช่เชิงสัมพันธ์ (NoSQL Databases & Modern Stores) การออกแบบโครงสร้างข้อมูล (Star/Snowflake & SCD) โครงสร้างพื้นฐานเครือข่ายระบบคลาวด์ (Cloud-based Networking) สถาปัตยกรรม Data Lakehouse (Apache Iceberg & MinIO) Local-first Data Engineering ด้วย DuckDB โครงสร้างพื้นฐานในรูปแบบโค้ด (IaC ด้วย Terraform) การแปลงข้อมูลระดับโปรด้วย dbt-core ข้อตกลงร่วมด้านข้อมูล (Data Contracts) การบริการและการส่งต่อข้อมูลวิเคราะห์ (Data Serving & Reverse ETL) การประมวลผลข้อมูลขนาดใหญ่แบบกระจายด้วย Apache Spark ระบบจัดการเวิร์กโฟลว์ข้อมูล (Airflow, Dagster & Prefect) การประมวลผลข้อมูลแบบเรียลไทม์ด้วย Kafka & Redpanda การจัดการคอนเทนเนอร์และคลัสเตอร์ (Containers & Kubernetes) ระบบ CI/CD และการเฝ้าระวังคุณภาพข้อมูล (CI/CD, Monitoring & Testing) ระบบความปลอดภัยและการควบคุมข้อมูล (Security, Governance & Privacy) ระบบปฏิบัติการและประมวลผลโมเดล (Machine Learning & MLOps) รากฐานของ AWS VPC การจัดการเส้นทางขั้นสูงและ NAT Gateway การเชื่อมต่อหลาย VPC และ Hybrid Cloud VPC Endpoints (PrivateLink) ระบบรักษาความปลอดภัยขอบเขตเครือข่าย สถาปัตยกรรมเครือข่ายสำหรับ EKS IPv6 และการจัดการ IP Address สถาปัตยกรรมเครือข่ายระดับโลก แก่นแท้ของสถาปัตยกรรม Kubernetes สถาปัตยกรรมเครื่องและคอมโพเนนต์ เลเยอร์ที่เชื่อมต่อได้ (Pluggable Layers) การรันบนโปรดักชันระดับองค์กร Apache Flink Stateful Stream Processing Real-Time CDC & Event Sourcing at Scale Low-Latency Stream-Table Joins & Windowing Data Lineage & Metadata Graph Engineering Statistical Anomaly Detection & Data Drift Data Incident Management & Automated DLQ Remediation Cloud Data FinOps & Cost Optimization Mechanics Data Mesh & Multi-Tenant Platform Architecture Vector Databases & AI-Ready Data Infrastructure API Fundamentals, Architectures & Core Components API Versioning, Docs, Real-Time & Microservices Production Reliability, Security & Resiliency World-Class Master Architecture & Traffic Management
บทที่ 8: สถาปัตยกรรมประมวลผลสตรีมมิ่งเรียลไทม์ขั้นสูง (Real-Time Streaming Engine)

Low-Latency Stream-Table Joins & Windowing

ขั้นสูงกับการ Join ระหว่าง Stream และ Table

ปลดล็อกขีดสุดของการประมวลผลข้อมูลสตรีมมิ่ง ด้วยการผสานข้อมูลที่ไหลอย่างต่อเนื่องเข้ากับตารางข้อมูลที่เปลี่ยนแปลงตลอดเวลา เจาะลึก Windowing และ Temporal Joins ใน Apache Flink

Technical Architecture Diagram
Click to Zoom
🖼️ 3D Isometric Architecture Breakdown:
  • Step 1: Data Ingestion & Event Processing
  • Step 2: Distributed Computation & State Management
  • Step 3: Orchestration & Resource Allocation

1. Tumbling, Sliding, และ Session Windows

ทฤษฎีและกลไกการทำงาน

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

ข้อมูลสตรีมมาเรื่อยๆ ไม่มีที่สิ้นสุด หากต้องการคำนวณยอดรวม (Aggregations) เราต้องหั่นข้อมูลเป็นท่อนๆ เรียกว่า Windows
- Tumbling Windows: ขนาดคงที่ ไม่ทับซ้อน (เช่น ตัดยอดเป๊ะๆ ทุก 5 นาที)
- Sliding Windows: ขนาดคงที่ แต่มีส่วนทับซ้อน (เช่น ดูย้อนหลัง 5 นาที โดยอัปเดตยอดใหม่ทุก 1 นาที)
- Session Windows: ขนาดไม่ตายตัว กำหนดโดยช่องว่างเวลา (เช่น จัดกลุ่ม Event จนกว่าผู้ใช้จะหยุดพักไป 10 นาที)

ตัวอย่างโค้ดหรือการตั้งค่า


// การใช้ Datastream API สร้าง Session Window
DataStream<UserClick> clickStream = ...;

DataStream<SessionAnalytics> sessionStats = clickStream
    .keyBy(click -> click.getUserId())
    // จัดกลุ่ม Session และจะปิด Window นี้เมื่อผู้ใช้หายไปนาน 15 นาที
    .window(EventTimeSessionWindows.withGap(Time.minutes(15)))
    .aggregate(new ClickAggregator());
        

Use Case ในชีวิตจริง

เว็บ E-commerce วิเคราะห์พฤติกรรมลูกค้า โดยใช้ Session Window ที่มีช่องว่าง (Gap) 15 นาที เพื่อรวมทุกคลิกให้เป็น "1 รอบการช็อปปิ้ง" หากผู้ใช้ปิดเว็บไปทานข้าวแล้วกลับมาใหม่ในอีก 1 ชั่วโมง ระบบจะสร้าง Session ใหม่ให้ทันที ทำให้วิเคราะห์ Conversion Rate ต่อรอบการใช้งานได้แม่นยำ

ข้อควรระวังและวิธีแก้

Pitfall: หากมีเหตุการณ์มาช้า (Out-of-order) อาจทำให้ Session Window ปิดตัวไปก่อน และสร้าง Session ใหม่แยกเป็นสองอันทั้งที่ควรจะเป็นอันเดียวกัน
Mitigation: ต้องใช้ Watermark Strategy ที่เหมาะสม และเปิดใช้งาน .allowedLateness() Flink จะทำการควบรวม (Merge) Session ย้อนหลังให้อัตโนมัติเมื่อข้อมูลที่มาช้าโผล่มาถึง

2. Stream-Table Joins (Interval Joins)

ทฤษฎีและกลไกการทำงาน

การทำ SQL Join ปกติในสตรีมจะกินเมมโมรี่มหาศาล เพราะระบบต้องจำข้อมูลทั้งหมดของทั้งสองตาราง Interval Joins แก้ปัญหานี้โดยบังคับให้จับคู่กันตามเงื่อนไขของเวลาเท่านั้น (เช่น Timestamp ของสตรีม A ต้องอยู่ระหว่าง Timestamp ของ B ลบ 5 นาที ถึงบวก 10 นาที) เมื่อ Watermark ผ่านช่วงเวลานั้นไป Flink จะเคลียร์ State เก่าๆ ทิ้งให้ทันที

ตัวอย่างโค้ดหรือการตั้งค่า


// การ Join ข้อมูลคลิกโฆษณากับการซื้อสินค้า
DataStream<EnrichedPurchase> joined = clickStream
    .keyBy(click -> click.getUserId())
    .intervalJoin(purchaseStream.keyBy(purchase -> purchase.getUserId()))
    // การซื้อต้องเกิดขึ้นระหว่าง 0 ถึง 60 นาที หลังจากที่คลิกโฆษณา
    .between(Time.minutes(0), Time.minutes(60))
    .process(new ProcessJoinFunction<AdClick, Purchase, EnrichedPurchase>() {
        @Override
        public void processElement(AdClick left, Purchase right, Context ctx, Collector<EnrichedPurchase> out) {
            out.collect(new EnrichedPurchase(left, right));
        }
    });
        

Use Case ในชีวิตจริง

ระบบจัดการโฆษณาต้องการนับผลงาน (Attribution) ว่าโฆษณาตัวไหนทำยอดขายได้ ด้วย Interval Join ระบบจะจับคู่ ClickEvent กับ PurchaseEvent ด้วย userId โดยมีกฎเหล็กว่าการซื้อต้องเกิดภายใน 1 ชั่วโมงหลังคลิก ช่วยให้ทำงานแบบ Real-time ได้โดยไม่ทำให้ State ระเบิด

ข้อควรระวังและวิธีแก้

Pitfall: หากตั้งระยะห่าง Interval นานเกินไป (เช่น 30 วัน) RocksDB State จะบวมอย่างรุนแรงและทำให้ระบบช้า
Mitigation: ควรใช้ระยะเวลาสั้นที่สุดตาม Business Logic หรือถ้าต้องใช้ระยะเวลานานมากๆ ให้พิจารณาไป Join ใน Data Warehouse แทนที่จะทำใน Streaming แบบ Real-time

3. Temporal Table Joins

ทฤษฎีและกลไกการทำงาน

บ่อยครั้งที่เราต้องเสริมข้อมูล (Enrich) ให้สตรีมด้วยข้อมูลอ้างอิงที่มีการเปลี่ยนแปลงตลอดเวลา (เช่น อัตราแลกเปลี่ยนเงิน) Temporal Table จะเก็บประวัติการเปลี่ยนแปลงตามเวลา และ Temporal Join จะจับคู่ Event กับข้อมูลเวอร์ชันที่ถูกต้องตามจุดเวลาที่ Event เกิดขึ้นเป๊ะๆ (Point-in-time correctness)

ตัวอย่างโค้ดหรือการตั้งค่า


-- การใช้ Flink SQL สร้าง Temporal Join
SELECT 
  o.order_id, 
  o.amount, 
  o.currency,
  r.rate,
  o.amount * r.rate AS amount_usd
FROM orders AS o
-- Join กับเรทราคาเวอร์ชันที่ตรงกับเวลาที่เกิดคำสั่งซื้อ (Event Time)
JOIN rates FOR SYSTEM_TIME AS OF o.order_time AS r
  ON o.currency = r.currency;
        

Use Case ในชีวิตจริง

บริษัท Payment Gateway ต้องแปลงสกุลเงินทุกรายการ อัตราแลกเปลี่ยนเปลี่ยนทุกชั่วโมง เมื่อระบบประมวลผลธุรกรรมของเวลา 14:15 Temporal Table Join จะบังคับใช้เรทราคาของตอน 14:15 เป๊ะๆ แม้สตรีมเส้นนี้จะถูกนำมารันย้อนหลัง (Reprocess) ในเดือนหน้า ก็ยังรับประกันความถูกต้องทางการเงิน 100%

ข้อควรระวังและวิธีแก้

Pitfall: หากไม่เก็บประวัติการเปลี่ยนแปลงของตารางอ้างอิง (เช่น แค่ดึงข้อมูลล่าสุดจาก DB) ระบบจะไม่สามารถย้อนเวลาไป Join ได้ถูกต้อง
Mitigation: ตารางอ้างอิงควรดึงมาจาก CDC Log (Debezium) หรือ Kafka แบบ Log Compaction และต้องกำหนด Primary Key กับ Time Attributes ให้ Flink SQL ทราบอย่างชัดเจนเสมอ

Weekend Sandbox Challenge 🚀

Dashboard สกุลเงินทั่วโลก: สร้าง Flink SQL เพื่อแปลงสกุลเงินแบบ Real-time

  • สร้าง Kafka Topic สองอัน: `transactions` (id, amount, curr, time) และ `rates` (curr, rate, time)
  • Register ทั้งสอง Topic เป็น Flink Tables และตั้งค่า Event Time
  • เขียนคำสั่ง Flink SQL แบบ Temporal Join (`FOR SYSTEM_TIME AS OF`) เพื่อแปลงยอดให้เป็นดอลลาร์ (USD)
  • ใช้ Tumbling Window สรุปยอด USD รวมทุกๆ 1 ชั่วโมง และปริ้นต์ผลลัพธ์ลง Console

คำถามสัมภาษณ์ระดับ Senior (Interview Q&A)

Q: การทำงานระหว่าง Interval Join กับ Window Join ใน Flink แตกต่างกันอย่างไร?

A: Window Join เป็นการตัดแบ่งกรอบเวลาตายตัว (เช่น บังคับให้ทั้งสอง Event ต้องอยู่ในกล่อง 10:00-10:05 เท่านั้น) ถ้า Event หนึ่งมา 10:04 อีกอันมา 10:06 จะจับคู่กันไม่ได้ ส่วน Interval Join ยืดหยุ่นกว่ามาก เพราะมันคำนวณจากระยะห่างระหว่าง Event โดยตรง (เช่น B ต้องเกิดหลัง A ภายใน 5 นาที) โดยไม่สนใจกรอบเวลาของนาฬิกาเลย

Q: ทำไม Temporal Table Joins ถึงสำคัญมากเมื่อเราต้องนำข้อมูลประวัติศาสตร์มาคำนวณใหม่ (Reprocessing)?

A: หากคุณ Join ข้อมูลสตรีมย้อนหลังกับคำสั่ง Database Query ธรรมดา คุณจะได้ค่าที่ "ล่าสุด" เสมอ ซึ่งทำให้ข้อมูลผิดเพี้ยนทั้งหมด Temporal Table Join ใช้ประโยชน์จากประวัติข้อมูลดิบที่มี Event Time กำกับ ทำให้เวลารันคำนวณซ้ำ ธุรกรรมของเดือนที่แล้วก็จะถูกจับคู่กับฐานข้อมูลในสถานะของเดือนที่แล้วเป๊ะๆ ทำให้ผลลัพธ์มีความเสถียร (Deterministic) และถูกต้อง 100%