ในยุคที่ข้อมูลถูกสร้างขึ้นทุกมิลลิวินาที ไม่ว่าจะเป็นการคลิกบนเว็บไซต์ การทำธุรกรรมทางการเงิน ข้อมูลจากเซ็นเซอร์ IoT หรือ Log จากเซิร์ฟเวอร์ การประมวลผลแบบ Batch ที่รันวันละครั้งไม่เพียงพออีกต่อไป ธุรกิจต้องการวิเคราะห์และตอบสนองต่อข้อมูลแบบ Real-time เพื่อตรวจจับการทุจริต แนะนำสินค้าแบบทันที หรือแจ้งเตือนความผิดปกติก่อนที่จะสายเกินไป
บทความนี้จะพาคุณเข้าใจ Stream Processing ตั้งแต่แนวคิดพื้นฐาน ไปจนถึงเครื่องมือหลักอย่าง Kafka Streams และ Apache Flink รวมถึง Pattern ที่ใช้ในการออกแบบ Real-time Data Pipeline สำหรับระบบจริง
Batch vs Stream Processing
ก่อนจะเข้าใจ Stream Processing ต้องเข้าใจความแตกต่างกับ Batch Processing ก่อน ทั้งสองเป็นวิธีการประมวลผลข้อมูลที่ต่างกันโดยสิ้นเชิง
| คุณสมบัติ | Batch Processing | Stream Processing |
|---|---|---|
| ข้อมูล | ชุดข้อมูลที่เก็บไว้แล้ว (bounded) | ข้อมูลที่ไหลเข้ามาต่อเนื่อง (unbounded) |
| เวลาประมวลผล | นาทีถึงชั่วโมง | มิลลิวินาทีถึงวินาที |
| ตัวอย่าง | สรุปยอดขายรายวัน | ตรวจจับ Fraud ทันที |
| เครื่องมือ | Spark Batch, Hadoop MapReduce | Kafka Streams, Flink, Spark Streaming |
| ความซับซ้อน | ต่ำกว่า | สูงกว่า (state, ordering, fault tolerance) |
| Latency | สูง | ต่ำมาก |
Stream Processing ไม่ได้มาแทนที่ Batch Processing ระบบจริงมักใช้ทั้งสอง เรียกว่า Lambda Architecture หรือ Kappa Architecture ขึ้นอยู่กับการออกแบบ
แนวคิดพื้นฐานของ Stream Processing
Event Time vs Processing Time
นี่คือแนวคิดที่สำคัญที่สุดใน Stream Processing:
- Event Time: เวลาที่เหตุการณ์เกิดขึ้นจริง เช่น เวลาที่ผู้ใช้กดคลิก ข้อมูลนี้ฝังอยู่ใน Event เอง
- Processing Time: เวลาที่ระบบประมวลผลได้รับ Event อาจต่างจาก Event Time เพราะ Network delay หรือ Buffering
การเลือกใช้ Event Time หรือ Processing Time มีผลต่อความถูกต้องของผลลัพธ์อย่างมาก ตัวอย่างเช่น ถ้าคุณนับจำนวนการคลิกต่อนาที การใช้ Processing Time อาจทำให้ Event ที่เกิดขึ้นในนาทีเดียวกันถูกนับคนละนาทีได้ เพราะ Event มาถึงช้า
Watermarks
Watermark คือกลไกที่บอกระบบว่า เราไม่น่าจะได้รับ Event ที่มี Event Time ก่อนเวลานี้อีกแล้ว เป็นวิธีจัดการกับ Event ที่มาสาย (Late Event) ถ้า Event มาหลังจาก Watermark ผ่านไปแล้ว ระบบจะจัดการตามนโยบายที่ตั้งไว้ อาจจะทิ้ง อาจจะ Update ผลลัพธ์ หรืออาจจะส่งไป Side Output
Windows
Window คือการแบ่ง Stream ที่ไม่มีที่สิ้นสุดออกเป็นกลุ่มย่อยที่จัดการได้ มีหลายแบบ:
- Tumbling Window: หน้าต่างขนาดคงที่ ไม่ซ้อนกัน เช่น ทุก 5 นาที ข้อมูลจะถูกจัดกลุ่มเป็นช่วง 0-5, 5-10, 10-15 นาที
- Sliding Window: หน้าต่างขนาดคงที่ แต่เลื่อนไปทีละช่วง เช่น Window 10 นาที เลื่อนทุก 5 นาที ทำให้ข้อมูลอาจอยู่ใน 2 Window ได้
- Session Window: หน้าต่างที่ปิดเมื่อไม่มี Activity ตามระยะเวลาที่กำหนด เหมาะสำหรับการวิเคราะห์ User Session
- Global Window: รวมทุกอย่างเป็น Window เดียว ใช้กับ Custom Trigger
State Management
Stream Processing ที่มีประโยชน์จริงต้องเก็บ State ไว้ได้ เช่น การนับจำนวน การรวม Aggregate หรือการ Join ข้อมูลจากหลาย Stream ระบบที่ดีต้องจัดการ State ให้ Fault-tolerant หมายความว่าถ้า Node ล่ม State จะไม่หาย และสามารถ Recover ได้
Apache Kafka Streams
Kafka Streams เป็น Library สำหรับ Java/Kotlin ที่ช่วยให้ประมวลผล Stream ได้โดยใช้ Kafka เป็น Backbone ข้อดีเด่นคือ ไม่ต้องมี Cluster แยก แค่เขียน Java Application ธรรมดาที่อ่านจาก Kafka Topic ประมวลผล แล้วเขียนกลับไป Kafka Topic อื่น
สถาปัตยกรรม Kafka Streams
Kafka Streams ทำงานเป็น Library ภายใน Application ของคุณเอง ไม่ต้องติดตั้ง Framework แยก ไม่ต้องมี Cluster Manager ทำให้ Deploy ง่ายมาก สามารถ Scale ด้วยการเพิ่ม Instance ของ Application แล้ว Kafka จะจัดการ Partition Rebalancing ให้อัตโนมัติ
KStream vs KTable
- KStream: แทน Event Stream ที่ไม่มีที่สิ้นสุด ทุก Record คือ Event ใหม่ เช่น Stream ของ Order ที่เข้ามา ทุก Record คือ Order ใหม่
- KTable: แทน Changelog Stream ที่แต่ละ Key มีค่าล่าสุดค่าเดียว เหมือน Table ในฐานข้อมูลที่ Update ตลอดเวลา เช่น ยอดคงเหลือล่าสุดของแต่ละบัญชี
// Kafka Streams Example (Java)
StreamsBuilder builder = new StreamsBuilder();
// อ่าน Stream จาก Topic
KStream<String, String> orders = builder.stream("orders");
// กรองเฉพาะ Order ที่มูลค่าสูง
KStream<String, String> highValue = orders.filter(
(key, value) -> parseAmount(value) > 10000
);
// นับจำนวน Order ต่อลูกค้า (Windowed)
KTable<Windowed<String>, Long> orderCounts = orders
.groupByKey()
.windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5)))
.count();
// เขียนผลลัพธ์ไป Topic ใหม่
highValue.to("high-value-orders");
// สร้างและ Start Application
KafkaStreams streams = new KafkaStreams(builder.build(), props);
streams.start();
Joins ใน Kafka Streams
Kafka Streams รองรับ Join หลายแบบ ซึ่งเป็นสิ่งที่ยากมากใน Stream Processing:
- KStream-KStream Join: Join สอง Event Stream ภายใน Time Window เช่น Match Click event กับ Purchase event ภายใน 30 นาที
- KStream-KTable Join: Enrich Stream ด้วยข้อมูลล่าสุดจาก Table เช่น เพิ่มข้อมูลลูกค้าลงใน Order Stream
- KTable-KTable Join: Join สอง Table ที่เปลี่ยนแปลงตลอดเวลา เหมือน SQL JOIN แต่ผลลัพธ์ Update แบบ Real-time
Exactly-Once Semantics
Kafka Streams รองรับ Exactly-once Processing ซึ่งหมายความว่า แม้จะมี Failure เกิดขึ้น ทุก Record จะถูกประมวลผลเพียงครั้งเดียวเท่านั้น ไม่ซ้ำ ไม่ขาด เปิดใช้งานง่ายมากแค่ตั้งค่า processing.guarantee=exactly_once_v2
Apache Flink
Apache Flink เป็น Distributed Stream Processing Framework ที่ออกแบบมาเพื่อ Stream Processing โดยเฉพาะตั้งแต่แรก (ไม่ได้เริ่มจาก Batch แล้วเพิ่ม Streaming ทีหลังเหมือน Spark) ทำให้มีสถาปัตยกรรมที่เหมาะกับ Stream Processing อย่างแท้จริง
สถาปัตยกรรม Flink
Flink มีสถาปัตยกรรมแบบ Master-Worker:
- JobManager: ทำหน้าที่เป็น Master ที่จัดการ Job Scheduling, Checkpoint Coordination และ Failure Recovery คอยดูแลว่า TaskManager แต่ละตัวทำงานถูกต้อง
- TaskManager: ทำหน้าที่เป็น Worker ที่ประมวลผล Task จริง แต่ละ TaskManager มีหลาย Task Slot สำหรับรัน Operator แบบ Parallel
- Checkpoint: Flink ใช้ Chandy-Lamport Algorithm สำหรับ Distributed Snapshot ทำให้สามารถ Recover จาก Failure ได้โดยไม่สูญเสียข้อมูล
// Flink DataStream API Example (Java)
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
// อ่านจาก Kafka
DataStream<String> stream = env.addSource(
new FlinkKafkaConsumer<>("events", new SimpleStringSchema(), props)
);
// Parse และ Filter
DataStream<Event> events = stream
.map(json -> parseEvent(json))
.filter(event -> event.getType().equals("purchase"));
// Windowed Aggregation
DataStream<Result> result = events
.keyBy(Event::getUserId)
.window(TumblingEventTimeWindows.of(Time.minutes(5)))
.aggregate(new PurchaseAggregator());
// เขียนไป Kafka
result.addSink(
new FlinkKafkaProducer<>("results", new ResultSerializer(), props)
);
env.execute("Purchase Analytics");
Flink SQL / Table API
Flink รองรับ SQL สำหรับ Stream Processing ทำให้สามารถเขียน Query แบบ SQL บน Real-time Data ได้เลย ไม่ต้องเขียน Java Code ซับซ้อน เหมาะสำหรับ Data Analyst ที่คุ้นเคยกับ SQL
-- Flink SQL Example
-- สร้าง Table จาก Kafka Topic
CREATE TABLE orders (
order_id STRING,
user_id STRING,
amount DECIMAL(10,2),
order_time TIMESTAMP(3),
WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH (
'connector' = 'kafka',
'topic' = 'orders',
'properties.bootstrap.servers' = 'localhost:9092',
'format' = 'json'
);
-- Real-time Aggregation
SELECT
user_id,
TUMBLE_START(order_time, INTERVAL '5' MINUTE) AS window_start,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
GROUP BY
user_id,
TUMBLE(order_time, INTERVAL '5' MINUTE)
HAVING SUM(amount) > 10000;
Spark Structured Streaming (เปรียบเทียบ)
Apache Spark Structured Streaming เป็นอีกทางเลือกหนึ่งที่นิยม ทำงานบนพื้นฐานของ Micro-batch โดยจะรวบรวม Event เป็นกลุ่มเล็กๆ แล้วประมวลผลเป็น Batch ข้อดีคือใช้ Spark API ที่คุ้นเคย แต่ Latency จะสูงกว่า Flink ที่ประมวลผลทีละ Event
# Spark Structured Streaming (Python)
from pyspark.sql import SparkSession
from pyspark.sql.functions import window, count, sum
spark = SparkSession.builder.appName("StreamApp").getOrCreate()
# อ่านจาก Kafka
df = spark.readStream .format("kafka") .option("kafka.bootstrap.servers", "localhost:9092") .option("subscribe", "orders") .load()
# Windowed Aggregation
result = df .withWatermark("timestamp", "5 minutes") .groupBy(window("timestamp", "5 minutes"), "user_id") .agg(count("*").alias("order_count"),
sum("amount").alias("total_amount"))
# เขียนไป Console (หรือ Kafka, Database)
result.writeStream .outputMode("update") .format("console") .start() .awaitTermination()
Kafka Streams vs Flink: เปรียบเทียบแบบละเอียด
| คุณสมบัติ | Kafka Streams | Apache Flink |
|---|---|---|
| ประเภท | Library (ไม่ต้องมี Cluster) | Framework (ต้องมี Cluster) |
| Deploy | JAR ธรรมดา, Container | YARN, K8s, Standalone Cluster |
| Input Source | Kafka เท่านั้น | Kafka, Kinesis, Files, JDBC, และอื่นๆ |
| Exactly-Once | รองรับ (Kafka transactions) | รองรับ (Checkpoint) |
| SQL Support | ไม่มี (ใช้ ksqlDB แยก) | Flink SQL ครบถ้วน |
| State Management | RocksDB, In-memory | RocksDB, Heap, พร้อม Savepoint |
| Throughput | สูง | สูงมาก (Millions events/sec) |
| Latency | ต่ำ (ms) | ต่ำมาก (sub-ms possible) |
| Batch Processing | ไม่รองรับ | รองรับ (Unified batch+stream) |
| Learning Curve | ง่ายกว่า | ยากกว่า |
| เหมาะกับ | Kafka-centric, ทีมเล็ก | Complex processing, ข้อมูลหลาย Source |
Stream Processing Patterns
Event-Time Join
การ Join สอง Stream ตาม Event Time เช่น Match Impression event กับ Click event ภายใน 30 นาทีหลัง Impression เพื่อคำนวณ Click-through Rate แบบ Real-time Pattern นี้ซับซ้อนเพราะ Event อาจมาไม่เรียงลำดับ ต้องใช้ Watermark และ Window ร่วมกัน
Sessionization
การจัดกลุ่ม Event เป็น Session ของผู้ใช้แต่ละคน โดยกำหนดว่า Session จะจบเมื่อไม่มี Activity ภายในเวลาที่กำหนด เช่น 30 นาที ทำให้สามารถวิเคราะห์พฤติกรรมผู้ใช้แบบ Real-time ได้ ใช้ Session Window ของ Flink หรือ Kafka Streams ในการ Implement
Anomaly Detection
ตรวจจับความผิดปกติแบบ Real-time เช่น ธุรกรรมที่ผิดปกติ Server ที่ใช้ CPU สูงเกินไป หรือ Login ที่น่าสงสัย วิธีทำคือเก็บ State ของ Baseline ปกติ แล้วเปรียบเทียบ Event ใหม่กับ Baseline ถ้าเบี่ยงเบนเกินค่าที่กำหนดก็แจ้งเตือน สำหรับระบบที่ต้องตรวจจับ Pattern ซับซ้อน อาจใช้ Machine Learning Model ร่วมกับ Stream Processing
Materialized View
สร้าง View ของข้อมูลที่ Update แบบ Real-time เช่น Dashboard ที่แสดงยอดขายล่าสุด จำนวน Active Users หรือ Inventory ปัจจุบัน ทำได้โดยใช้ KTable ใน Kafka Streams หรือ Flink SQL ที่ Write ผลลัพธ์ลง Database ที่ Query ได้เร็ว เช่น Redis หรือ Elasticsearch
Real-time Analytics Pipeline Architecture
สถาปัตยกรรมของ Real-time Analytics Pipeline ที่ใช้ในระบบจริงประกอบด้วยหลายส่วน:
Data Sources Message Broker Stream Processor Sink
┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────┐
│ Web App │────────────>│ │ │ │───────>│ Database │
│ Mobile │────────────>│ Apache │──────────>│ Flink / │───────>│ Redis │
│ IoT │────────────>│ Kafka │ │ Kafka │───────>│ Elastic │
│ Server │────────────>│ │ │ Streams │───────>│ S3 │
└──────────┘ └──────────┘ └──────────────┘ └──────────┘
│ │
│ State Store
│ (RocksDB/Checkpoint)
│
Schema Registry
(Avro/Protobuf)
แต่ละส่วนมีบทบาทดังนี้:
- Data Sources: ส่ง Event เข้า Kafka Topic ผ่าน Kafka Producer
- Schema Registry: จัดการ Schema ของข้อมูล เพื่อให้ Producer และ Consumer ใช้ Format เดียวกัน
- Stream Processor: อ่าน Event จาก Kafka ประมวลผล แล้วเขียนผลลัพธ์ไป Sink
- State Store: เก็บ State ที่จำเป็นสำหรับการประมวลผล เช่น Aggregate ที่กำลัง Compute
- Sink: ปลายทางของผลลัพธ์ อาจเป็น Database, Cache, Search Engine หรือ Object Storage
Stream-Table Duality
แนวคิดสำคัญใน Stream Processing คือ Stream กับ Table เป็นสิ่งเดียวกันที่มองต่างมุม Stream คือ Log ของ Change events ทั้งหมด ส่วน Table คือ State ล่าสุดที่ได้จากการ Replay Stream ทั้งหมด
ตัวอย่างเช่น ถ้ามี Stream ของการเปลี่ยนแปลงยอดเงินในบัญชี ซึ่งเป็น Changelog ที่บันทึกทุก Event เช่น ฝาก 1000 ถอน 500 ฝาก 2000 ถ้าเรา Replay Stream ทั้งหมดจะได้ Table ที่แสดงยอดคงเหลือล่าสุดของแต่ละบัญชี ในทางกลับกัน ถ้ามี Table และจับ Change events ทั้งหมดจะได้ Stream
แนวคิดนี้เป็นพื้นฐานของ KTable ใน Kafka Streams และ Dynamic Table ใน Flink SQL
CDC กับ Debezium + Stream Processing
Change Data Capture (CDC) เป็นเทคนิคการจับ Change events จากฐานข้อมูลแบบ Real-time โดยอ่านจาก Database Transaction Log เช่น MySQL Binlog หรือ PostgreSQL WAL แล้วส่งเข้า Kafka
Debezium เป็นเครื่องมือ CDC ที่นิยมที่สุด ทำงานเป็น Kafka Connect Source Connector ที่อ่านจากฐานข้อมูลแล้วส่ง Change events เข้า Kafka Topic
// Debezium MySQL Connector Config
{
"name": "mysql-connector",
"config": {
"connector.class": "io.debezium.connector.mysql.MySqlConnector",
"database.hostname": "mysql-host",
"database.port": "3306",
"database.user": "debezium",
"database.password": "password",
"database.server.id": "1",
"topic.prefix": "myapp",
"database.include.list": "mydb",
"schema.history.internal.kafka.bootstrap.servers": "kafka:9092",
"schema.history.internal.kafka.topic": "schema-changes"
}
}
เมื่อ Change events อยู่ใน Kafka แล้ว สามารถใช้ Kafka Streams หรือ Flink ประมวลผลต่อได้ ใช้กรณีที่พบบ่อย เช่น Sync ข้อมูลไปหลาย Database สร้าง Search Index ใน Elasticsearch หรือ Invalidate Cache แบบ Real-time
Testing Stream Processing
การทดสอบ Stream Processing เป็นสิ่งที่ท้าทาย เพราะต้องจัดการกับ Time, State และ Ordering เครื่องมือและวิธีการที่ใช้ได้:
- Kafka Streams TopologyTestDriver: ทดสอบ Kafka Streams Topology โดยไม่ต้องมี Kafka Cluster จริง ส่ง Input records เข้าไปแล้วตรวจสอบ Output ได้ทันที เร็วมาก
- Flink MiniCluster: รัน Flink Cluster ขนาดเล็กใน Unit Test สำหรับทดสอบ Flink Job
- Testcontainers: ใช้ Docker Container สำหรับ Integration Test ที่ต้องการ Kafka, Flink หรือ Database จริง
- Event Time Control: ใช้ Test Harness ที่ควบคุม Event Time ได้ เพื่อทดสอบ Windowing และ Watermark Logic
// Kafka Streams Unit Test (Java)
@Test
void testHighValueFilter() {
TopologyTestDriver driver = new TopologyTestDriver(topology, props);
TestInputTopic<String, Order> input =
driver.createInputTopic("orders", stringSerde, orderSerde);
TestOutputTopic<String, Order> output =
driver.createOutputTopic("high-value-orders", stringSerde, orderSerde);
// ส่ง Order มูลค่าสูง
input.pipeInput("key1", new Order("user1", 50000));
// ตรวจสอบว่าผ่าน Filter
assertFalse(output.isEmpty());
assertEquals(50000, output.readValue().getAmount());
// ส่ง Order มูลค่าต่ำ
input.pipeInput("key2", new Order("user2", 500));
// ตรวจสอบว่าถูกกรองออก
assertTrue(output.isEmpty());
}
Monitoring & Observability
Stream Processing ต้องมี Monitoring ที่ดี เพราะถ้าระบบมีปัญหาจะกระทบ Real-time ทันที Metrics ที่ต้องดูมีดังนี้:
- Lag: จำนวน Event ที่ยังไม่ได้ประมวลผล ถ้าเพิ่มขึ้นเรื่อยๆ แสดงว่า Consumer ช้ากว่า Producer
- Throughput: จำนวน Event ที่ประมวลผลต่อวินาที ใช้ดู Capacity และ Performance
- Latency: เวลาตั้งแต่ Event เกิดจนถึงผลลัพธ์พร้อมใช้ ยิ่งต่ำยิ่งดี
- Checkpoint Duration: เวลาที่ใช้ในการ Checkpoint ถ้านานเกินไปอาจมีปัญหา State ใหญ่
- Backpressure: สัญญาณว่า Downstream Operator ทำงานไม่ทัน ต้อง Scale หรือ Optimize
เครื่องมือที่ใช้ Monitoring ได้แก่ Prometheus + Grafana สำหรับ Metrics, Kafka Manager หรือ Confluent Control Center สำหรับ Kafka, และ Flink Web UI สำหรับ Flink Job สำหรับระบบที่ต้อง Monitor ข้อมูลแบบ Real-time การวางระบบ Observability ที่ดีเป็นสิ่งจำเป็น
เมื่อไหร่ควรใช้เครื่องมือไหน
การเลือกเครื่องมือที่เหมาะสมขึ้นอยู่กับหลายปัจจัย:
- Kafka Streams: เหมาะเมื่อข้อมูลอยู่ใน Kafka อยู่แล้ว ต้องการ Deploy ง่าย ทีมเล็ก ไม่ต้องการ Cluster แยก Use case ที่ดี เช่น Event enrichment, Simple aggregation, Microservice communication
- Apache Flink: เหมาะเมื่อต้องการ Complex event processing ต้องอ่านจากหลาย Source ต้องการ SQL บน Stream หรือต้องการ Throughput สูงมาก Use case ที่ดี เช่น Fraud detection, Real-time ML, Complex analytics
- Spark Structured Streaming: เหมาะเมื่อใช้ Spark อยู่แล้วสำหรับ Batch ต้องการ Unified API สำหรับทั้ง Batch และ Stream ยอมรับ Latency ระดับวินาทีได้
- ksqlDB: เหมาะเมื่อต้องการเขียน SQL บน Kafka Stream โดยไม่ต้องเขียน Code เลย เหมาะสำหรับ Data Analyst หรือ Prototype รวดเร็ว
สรุป
Stream Processing เป็นทักษะที่จำเป็นมากขึ้นเรื่อยๆ สำหรับ Data Engineer และ Backend Developer ในปี 2026 เพราะธุรกิจต้องการข้อมูล Real-time มากกว่าเดิม ไม่ว่าจะเป็นการตรวจจับ Fraud การแนะนำสินค้า หรือการแจ้งเตือนทันที
Kafka Streams เหมาะสำหรับการเริ่มต้นเพราะ Deploy ง่ายและไม่ต้องมี Cluster แยก ส่วน Apache Flink เป็นทางเลือกที่ทรงพลังกว่าสำหรับ Use case ที่ซับซ้อน สิ่งสำคัญคือเข้าใจแนวคิดพื้นฐานอย่าง Event Time, Watermarks, Windows และ State Management เพราะแนวคิดเหล่านี้ใช้ได้กับทุกเครื่องมือ
เริ่มจากการทดลองเขียน Kafka Streams Application ง่ายๆ ที่อ่านจาก Topic ประมวลผล แล้วเขียนกลับไป Topic อื่น จากนั้นค่อยเพิ่มความซับซ้อนด้วย Windowing, Join และ State เมื่อคุ้นเคยแล้วจะเข้าใจว่าการประมวลผลข้อมูลแบบ Real-time เปลี่ยนวิธีการทำงานของระบบไปอย่างสิ้นเชิง
