Tư duy stream — event, input vô tận, xử lý liên tục
Stream xử lý một dòng sự kiện không bao giờ dứt (unbounded): job chạy mãi, phản hồi từng event khi tới. Vì sao input vô tận đổi hẳn cách thiết kế so với batch.
TL;DR: Stream processing là xử lý dòng sự kiện vô tận (unbounded) — job không bao giờ dừng, phản hồi từng event ngay khi nó tới. Khác batch (khối hữu hạn, chạy rồi dừng), stream không biết điểm kết thúc nên không thể gom cả khối rồi tính: thay vào đó cần xử lý từng event khi tới, duy trì state liên tục, và đặt window để nhóm sự kiện theo thời gian. Mô hình nền là producer/consumer: producer phát event vào một topic/channel, consumer đọc và xử lý mãi không ngừng. Kết quả bạn tính luôn là bản tạm thời, cập nhật theo event mới — không có "điểm hoàn thành" như batch output.
Batch xử lý tốt cực kỳ khi dữ liệu đã đóng — bạn thấy điều đó ở Module 1. Nhưng không phải mọi bài toán đều chờ được. Ngân hàng cần phát hiện giao dịch gian lận trong vài giây sau khi sự kiện xảy ra, không phải sau khi batch đêm chạy xong. Hệ thống phân tích lưu lượng mạng cần đếm số kết nối bất thường theo thời gian thực, không chờ đến 0 giờ sáng. Những bài toán này có chung một đặc điểm: dữ liệu không bao giờ đóng — sự kiện cứ tới mãi, và bạn cần phản hồi ngay, không phải chờ có đủ khối.
Bài này giải thích tại sao mô hình batch không khớp khi input là dòng vô tận, stream xử lý sự việc khác thế nào (event, producer/consumer, window, state liên tục), và đặt nền để bạn hiểu Kafka và Flink ở các bài sau.
1. Analogy — điều phối viên tổng đài trực
Hãy so sánh hai cách vận hành trung tâm hỗ trợ khách hàng. Cách thứ nhất: thu thập tất cả yêu cầu trong ngày, 11 giờ đêm mới mở ra xử lý một lần — đó là batch. Cách thứ hai: điều phối viên ngồi trực 24/7, cứ có cuộc gọi đến là nhấc máy xử lý ngay — đó là stream.
Điều phối viên stream không biết hôm nay sẽ có bao nhiêu cuộc gọi (input vô tận); không thể "gom hết rồi mới trả lời" (phải xử lý ngay); và phải ghi nhớ context của từng khách hàng đang nói chuyện (duy trì state).
| Điều phối viên tổng đài | Stream processing |
|---|---|
| Cuộc gọi đến liên tục, không biết bao nhiêu | Dòng event vô tận (unbounded) |
| Nhấc máy xử lý ngay từng cuộc | Xử lý từng event khi tới |
| Người gọi (khách) | Producer |
| Người nghe (điều phối) | Consumer |
| Đường dây điện thoại | Topic / Channel |
| Ghi nhớ context khách đang nói | State liên tục trong consumer |
| "30 phút qua có bao nhiêu cuộc gọi?" | Window theo thời gian |
| Số liệu thống kê cập nhật liên tục | Output tạm thời, cập nhật theo event |
Batch = xử lý lô thư hàng đêm. Stream = tổng đài trực 24/7 nhấc máy ngay. Cùng dữ liệu, khác nhau ở khi nào bạn muốn kết quả.
2. Stream là gì — event, producer, consumer
Mọi hệ thống stream đều xoay quanh một đơn vị nhỏ nhất: event (sự kiện). Event là một thực tế đã xảy ra, bất biến, được gắn timestamp: "khách hàng #1402 vừa nhấn thanh toán lúc 14:32:07.831", "cảm biến #77 ghi nhiệt độ 38.4°C lúc 14:32:08.012". Event không bị sửa sau khi phát ra — nó chỉ append vào dòng.
Dòng event này chạy qua mô hình producer/consumer:
flowchart LR P1["Producer A<br/>(payment-service)"] -->|"event"| CH["Topic /<br/>Channel"] P2["Producer B<br/>(sensor-service)"] -->|"event"| CH CH -->|"stream"| C1["Consumer X<br/>(fraud-detector)"] CH -->|"stream"| C2["Consumer Y<br/>(analytics)"]
- Producer phát event vào topic (kênh chủ đề). Nhiều producer khác nhau có thể cùng ghi vào một topic.
- Consumer đọc event từ topic và xử lý. Nhiều consumer có thể đọc cùng một topic độc lập — fraud detector và analytics cùng nhận event thanh toán mà không ảnh hưởng nhau.
- Topic/Channel là điểm tách biệt: producer không biết ai đang đọc; consumer không biết ai đang ghi. Producer chạy nhanh hay chậm không ảnh hưởng trực tiếp tới consumer.
Sự tách biệt này là thiết kế chủ đích. Consumer đọc theo tốc độ của mình, có thể bắt kịp hay tụt lại (consumer lag). Khi consumer bị lag, topic giữ lại event để consumer đọc sau — không mất sự kiện. Bạn sẽ thấy Kafka hiện thực hóa ý tưởng này cụ thể ở bài 02 — Log-based broker.
3. Vì sao input vô tận đổi cách xử lý?
Đây là câu hỏi cốt lõi của bài. Batch mạnh vì có điểm kết thúc: sau khi đọc hết khối, bạn có thể sort toàn bộ, gom theo key, tính aggregate trên tập đầy đủ. Stream không có điểm kết thúc — dòng event sẽ còn đến mãi. Điều đó thay đổi ba thứ căn bản.
3.1 Không gom được toàn bộ — phải dùng window
Batch có thể tính "tổng doanh thu tháng 6" vì tháng 6 đã đóng. Stream không thể hỏi "tổng doanh thu từ trước đến nay" mà có ý nghĩa thực tế — con số thay đổi từng giây. Thay vào đó, stream phải giới hạn phạm vi thời gian bằng window: "tổng doanh thu 5 phút vừa qua", "số kết nối mới trong 1 giờ cuối". Window biến câu hỏi vô tận thành câu hỏi hữu hạn tính được.
-- Batch: tinh tren toan bo input (da dong)
total_revenue = sum(all_orders.amount)
-- Stream: phai dung window (vo tan -> co gioi han)
total_revenue_last_5min = sum(events WHERE event_time >= now() - 5min)
-- Window dong moi 5 phut, tinh lai tu dau -> output cap nhat lien tuc
3.2 Phải duy trì state liên tục
Batch tính số lần mỗi IP xuất hiện bằng sort | uniq -c — sort cả khối rồi đếm. Stream không thể sort dòng vô tận. Thay vào đó, consumer duy trì một state trong bộ nhớ: mỗi event đến, cập nhật state tức thì rồi tiếp tục.
-- Consumer dem so lan IP xuat hien (stream, worked example)
state = {} -- bang dem song song qua toan bo vong doi consumer
on_event(e):
ip = e.source_ip
state[ip] = state.get(ip, 0) + 1 -- cap nhat tuc thi
if state[ip] > THRESHOLD:
alert("Unusual traffic from " + ip)
-- state khong reset giua cac event -- no song trong suot vong doi consumer
State sống trong suốt vòng đời của consumer — không reset giữa chừng như biến trong batch job. Bài 03 sẽ đào sâu cách giới hạn state theo window để tránh state tích luỹ vô hạn.
3.3 Kết quả luôn là bản tạm thời
Batch output là kết quả hoàn chỉnh tại thời điểm job chạy xong: "top 10 sản phẩm ngày hôm qua" là con số cuối cùng, không đổi. Stream output là kết quả đang diễn ra: "top 10 sản phẩm 1 giờ qua" cập nhật mỗi vài giây khi event mới đến. Không có "xong" — chỉ có "tính tới thời điểm này".
4. Batch vs stream — so sánh trực tiếp
Module 1 giải thích tại sao batch mạnh khi input bounded: có thể sort toàn bộ, throughput khổng lồ, idempotent khi chạy lại. Hai mô hình là cặp đối xứng — đọc lại bài 01 Tư duy batch để thấy rõ ranh giới.
| Tiêu chí | Batch (Module 1) | Stream |
|---|---|---|
| Input | Bounded — khối hữu hạn, biết điểm cuối | Unbounded — dòng sự kiện vô tận |
| Vòng đời job | Chạy → xong → dừng | Chạy mãi, không dừng |
| Đơn vị xử lý | Toàn bộ khối cùng lúc | Từng event (hoặc micro-batch nhỏ) |
| Kết quả | Hoàn chỉnh tại thời điểm job xong | Tạm thời, cập nhật liên tục |
| Độ trễ | Cao (phút–giờ) | Thấp (mili giây–giây) |
| Throughput | Rất cao — tối ưu trên khối lớn | Cao, nhưng ưu tiên độ trễ thấp |
| Ví dụ | Báo cáo cuối ngày, dựng index | Phát hiện gian lận, đếm live |
Không có kẻ thắng tuyệt đối — chọn theo bài toán. Báo cáo doanh thu hàng tháng: batch đủ và đơn giản hơn. Phát hiện gian lận trong 3 giây: stream là lựa chọn duy nhất. Nhiều hệ thống thực tế chạy cả hai song song (kiến trúc Lambda/Kappa — Module 3 sẽ bàn).
5. Micro-batch vs true streaming — ranh giới cần biết
Một biến thể phổ biến nằm giữa hai thái cực: micro-batch (Apache Spark Structured Streaming). Thay vì xử lý từng event, micro-batch gom event thành các khối nhỏ (vài trăm mili giây đến vài giây) rồi chạy batch job mini trên đó.
flowchart TD STREAM["Event stream (unbounded)"] STREAM --> MB["Micro-batch<br/>(gom event, moi 500ms)"] STREAM --> TS["True streaming<br/>(xu ly tung event)"] MB --> OUT1["Mini batch job<br/>-> output moi 500ms"] TS --> OUT2["Output ngay khi event toi<br/>(ms latency)"]
| Micro-batch (Spark) | True streaming (Flink) | |
|---|---|---|
| Mô hình xử lý | Batch rất nhỏ, lặp lại nhanh | Xử lý từng event khi tới |
| Độ trễ | Hàng trăm mili giây đến vài giây | Mili giây |
| Dễ debug | Cao — semantics quen với batch | Thấp hơn — state phức tạp hơn |
| Throughput | Tốt, dễ tối ưu từng micro-batch | Tốt, nhưng overhead per-event cao hơn |
Khi bài này dùng "stream", ý nghĩa rộng bao gồm cả micro-batch — điểm phân biệt là input unbounded và job chạy mãi, không phải chi tiết triển khai từng event hay micro-batch.
6. Pitfall — nhầm stream với batch "chạy nhanh hơn"
Cái bẫy phổ biến nhất khi mới tiếp cận stream: nghĩ rằng "stream chỉ là batch chạy thường xuyên hơn". Lối nghĩ này dẫn tới thiết kế không đạt yêu cầu độ trễ.
❌ SAI — gom event roi chay batch moi phut
cron job moi phut: doc log -> tinh toan -> ghi ket qua
=> van la batch, khong phai stream.
=> Do tre toi thieu 1 phut (event xay ra ngay sau batch vua chay
phai cho den luot sau).
=> Khi can < 1 giay, khong dat duoc voi cach nay.
✅ DUNG — consumer chay lien tuc, xu ly event khi toi
consumer loop:
event = channel.read() -- block cho den khi co event
update_state(event) -- cap nhat state tuc thi
if should_alert(state):
emit_alert() -- phat canh bao ms sau event
=> Do tre tinh bang ms, khong phai phut.
Hệ quả thực tế: nếu bạn đặt cron mỗi 1 phút chạy một batch mini, bạn vẫn có độ trễ tối thiểu 1 phút và không giải quyết được bài toán fraud detection yêu cầu phản hồi trong 3 giây. Stream thật sự nghĩa là consumer không dừng — event tới lúc nào, xử lý lúc đó.
7. 📚 Deep Dive
- The Dataflow Model — Akidau et al. (Google, VLDB 2015) — paper nền chuẩn cho stream processing hiện đại (DOI: 10.14778/2824032.2824076). Đây là nguồn gốc của mô hình what/where/when/how và khái niệm watermark — bài 03 của module này dựa trực tiếp trên paper này. Xem mục "Model" để thấy cách Google định nghĩa chính thức unbounded data và windowing.
- Kafka: a Distributed Messaging System for Log Processing — Kreps, Narkhede, Rao (NetDB 2011) — thiết kế gốc của Kafka: tại sao consumer-driven offset (không phải broker-driven) và log immutable giải quyết được bài toán nhiều consumer đọc cùng một dòng sự kiện độc lập. Benchmark 2011: producer đạt khoảng 50 MB/s trên 1 broker (6 đĩa RAID 10).
- Apache Flink — Timely Stream Processing — định nghĩa chính thức event-time, processing-time, và watermark trong Flink — framework triển khai Dataflow Model. Đọc mục "Event Time and Watermarks" để thấy tại sao phải phân biệt hai khái niệm thời gian.
Ghi chú: Designing Data-Intensive Applications (Kleppmann) Chương 11 là nguồn agnostic chuẩn cho stream — dựng trực giác từ "log sự kiện" → producer/consumer → windowing → fault tolerance. Đọc mục "Transmitting Event Streams" trước để có bức tranh tổng trước khi đi vào Kafka và Flink ở các bài sau.
8. Liên hệ các bài khác
- Tư duy batch — Module 1: nền đối xứng của bài này — batch bounded vs stream unbounded. Đọc lại "Vì sao input phải bất biến?" và "Derived data" để thấy stream thừa kế triết lý append-only theo cách riêng: event là log chỉ append, không sửa.
- Bài 02 — Log-based broker: Kafka hiện thực hóa mô hình producer/consumer ở bài này. Topic = log append-only; consumer tự giữ offset; nhiều consumer đọc cùng log độc lập — chi tiết kỹ thuật của channel "không mất event khi consumer lag".
- Bài 03 — Windowing: đào sâu các loại window (tumbling/sliding/session), event-time vs processing-time, và watermark — khái niệm giải quyết bài toán "event đến trễ" mà bài này chỉ đề cập sơ qua.
- Bài 04 — Stream join và exactly-once: khi cần ghép hai stream lại và đảm bảo mỗi event chỉ xử lý đúng một lần — xây trực tiếp trên producer/consumer và state liên tục ở bài này.
9. Tóm tắt
- Stream xử lý input unbounded (dòng sự kiện vô tận): job chạy mãi, phản hồi từng event khi tới — không có bước "xong, dừng" như batch.
- Mô hình nền: producer phát event vào topic, consumer đọc và xử lý liên tục; hai bên tách biệt — không biết nhau, không ảnh hưởng tốc độ nhau.
- Input vô tận đổi ba thứ: cần window để nhóm sự kiện (không gom cả khối), duy trì state liên tục (không reset), và kết quả luôn tạm thời (cập nhật theo event mới, không có điểm hoàn thành cố định).
- Batch mạnh khi input bounded (sort/gom toàn bộ, throughput khổng lồ). Stream mạnh khi cần độ trễ thấp (mili giây–giây) với input không bao giờ đóng. Không có kẻ thắng tuyệt đối — chọn theo bài toán; nhiều hệ thống chạy cả hai.
- Micro-batch (Spark) là trung gian: gom event thành batch mini vài trăm ms. True streaming (Flink) xử lý từng event tức thì. Cả hai đều là "stream" theo nghĩa rộng — input unbounded, job chạy mãi.
- Pitfall: coi stream là "batch chạy thường xuyên hơn" — cron mỗi phút vẫn là batch, vẫn có độ trễ 1 phút. Stream thật sự là consumer không dừng, event tới lúc nào xử lý lúc đó.
10. Tự kiểm tra
Q1Khác biệt căn bản giữa batch và stream là gì, và tại sao khác biệt đó khiến batch không dùng được khi cần phát hiện gian lận trong vòng 3 giây?▸
Batch xử lý input bounded — khối hữu hạn biết điểm cuối; job chạy hết rồi dừng. Stream xử lý input unbounded — dòng sự kiện vô tận; job không bao giờ dừng.
Batch cần gom đủ dữ liệu rồi mới tính — tức là có độ trễ tính bằng phút đến giờ (thời gian gom khối + thời gian chạy job). Fraud detection cần phản hồi trong vài giây sau khi giao dịch xảy ra — không thể chờ batch đêm. Stream xử lý event ngay khi tới, nên độ trễ tính bằng mili giây đến giây.
Q2Trong mô hình producer/consumer, vì sao producer và consumer được tách biệt qua topic thay vì gọi trực tiếp nhau? Lợi ích cụ thể là gì?▸
Topic là lớp tách biệt (decoupling): producer ghi event vào topic mà không biết ai đọc; consumer đọc theo tốc độ của mình mà không biết ai ghi. Hai bên không phụ thuộc tốc độ nhau — producer có thể phát nhanh hơn consumer xử lý, consumer bắt kịp dần (consumer lag).
Lợi ích cụ thể: (1) nhiều consumer đọc cùng một topic độc lập — fraud detector và analytics đều nhận event thanh toán mà không ảnh hưởng nhau; (2) thêm hoặc bỏ consumer mà không cần sửa producer; (3) consumer có thể restart và đọc lại từ điểm dừng mà không yêu cầu producer phát lại.
Q3Vì sao stream cần 'window' trong khi batch không cần? Cho ví dụ cụ thể về một câu hỏi chỉ có ý nghĩa với window.▸
Batch không cần window vì input đã đóng — bạn có thể hỏi "tổng tất cả" và có câu trả lời xác định. Stream input vô tận — hỏi "tổng từ đầu đến nay" cho kết quả thay đổi mỗi giây, không có ý nghĩa làm ngưỡng cảnh báo.
Window giới hạn phạm vi thời gian để câu hỏi trả lời được: "số kết nối từ IP X trong 5 phút qua vượt 1000?" thay vì "từ khi hệ thống khởi động". Ví dụ cụ thể: hệ thống phát hiện DDoS đặt window 1 phút — nếu một IP gửi quá 500 request trong 1 phút thì cảnh báo; không có window thì không có ngưỡng cố định để so sánh.
Q4Một đồng nghiệp đề xuất: 'đặt cron job mỗi 30 giây chạy batch mini là đủ thay stream'. Đúng hay sai? Tại sao?▸
Sai về bản chất. Cron mỗi 30 giây vẫn là batch — job chạy xong rồi dừng, event trong 30 giây đó phải chờ hết window mới được xử lý. Độ trễ tối thiểu là 30 giây (sự kiện xảy ra ngay sau khi batch vừa chạy xong phải chờ tới lần sau), chứ không phải mili giây.
Nếu bài toán chấp nhận độ trễ 30 giây thì micro-batch hoặc cron có thể đủ. Nếu cần phản hồi trong vài giây (fraud detection, live alerting), phải dùng consumer chạy liên tục — event tới lúc nào xử lý lúc đó, không chờ window tiếp theo.
Q5Stream consumer duy trì 'state' liên tục trong suốt vòng đời. Điều đó khác gì so với batch job, và tại sao điều đó cần thiết?▸
Batch job tính từ đầu mỗi lần chạy — state giữa hai lần chạy không liên tục (trừ khi ghi ra storage). Stream consumer duy trì state trong bộ nhớ trong suốt quá trình chạy: mỗi event đến, cập nhật state rồi tiếp tục; state không reset giữa các event.
Cần thiết vì stream không thể sort-rồi-đếm toàn bộ như batch. Ví dụ: đếm số lần IP xuất hiện — batch dùng sort | uniq -c trên toàn bộ log; stream phải giữ bảng đếm trong bộ nhớ và cập nhật khi event mới tới. Thiếu state liên tục, consumer không thể tính các aggregate qua thời gian (số lần gọi trong 5 phút, tổng tiền theo user).
Q6Micro-batch (Spark Structured Streaming) và true streaming (Flink) khác nhau chỗ nào? Khi nào nên chọn micro-batch thay vì true streaming?▸
Micro-batch gom event thành khối nhỏ (vài trăm ms đến vài giây) rồi chạy batch mini trên đó — vẫn là batch về mặt thực thi, chỉ rất nhỏ và lặp nhanh. True streaming (Flink, Kafka Streams) xử lý từng event ngay khi tới — không có bước gom.
- Micro-batch phù hợp khi: độ trễ vài trăm ms đến vài giây là chấp nhận được; team quen với batch semantics và muốn dễ debug; throughput quan trọng hơn latency cực thấp.
- True streaming phù hợp khi: cần độ trễ mili giây (fraud detection tức thì, live counter); mỗi event cần xử lý riêng lẻ ngay lập tức.
Cả hai đều xử lý input unbounded và job không dừng — đều là "stream" theo nghĩa rộng.
Q7Kết quả của stream job được gọi là 'tạm thời'. Điều đó có nghĩa là gì, và tại sao nó khác căn bản với batch output?▸
Batch output là kết quả hoàn chỉnh tại thời điểm job xong: "top 10 sản phẩm ngày hôm qua" là con số cuối cùng, không thay đổi sau đó (vì input đã đóng). Stream output cập nhật liên tục theo event mới: "top 10 sản phẩm 1 giờ qua" tính lại mỗi khi event mới đến — không có thời điểm "xong" cố định.
"Tạm thời" không có nghĩa là kém chính xác — nó chính xác tại thời điểm tính. Ý nghĩa là không có điểm hoàn thành: consumer tiếp tục chạy, kết quả tiếp tục cập nhật. Điều này đặt ra yêu cầu thiết kế khác: downstream cần xử lý được kết quả cập nhật liên tục, thay vì một snapshot tĩnh nhận được sau khi job kết thúc.
Bài tiếp theo: Log-based broker — Kafka giữ log và offset cho nhiều consumer
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên