Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 53 — Data Pipeline — Batch và Stream

Architecture Decision-Making Bài 53/60

Mở đầu — vì sao bài này quan trọng

Có một sự thật mà nhiều kiến trúc sư hệ thống nhận ra hơi muộn: kiến trúc dữ liệu (data pipeline) thường là thứ quyết định giới hạn thực sự của sản phẩm, chứ không phải kiến trúc ứng dụng. Bạn có thể có microservices đẹp đẽ, API design chuẩn mực, nhưng nếu báo cáo doanh thu cho ban giám đốc trễ 6 tiếng, hoặc hệ thống chống gian lận (fraud detection) phát hiện giao dịch bất thường sau khi tiền đã bị rút — thì toàn bộ kiến trúc "đẹp" đó cũng vô nghĩa.

Trong vai trò một người ra quyết định kiến trúc, bạn sẽ liên tục đứng trước một câu hỏi tưởng đơn giản nhưng cực kỳ tốn kém nếu chọn sai: dữ liệu này nên được xử lý theo lô (batch) hay theo luồng (stream)? Chọn stream cho một bài toán vốn chỉ cần batch, bạn đốt tiền vận hành và gánh một hệ thống phức tạp không cần thiết. Chọn batch cho một bài toán cần real-time, bạn đánh mất giá trị kinh doanh và phải làm lại từ đầu — một cuộc migration đau đớn.

Bài này không dạy bạn cách cấu hình Spark hay viết DAG trong Airflow (đó là kỹ năng công cụ). Bài này dạy bạn khung tư duy để quyết định khi nào batch, khi nào stream, khi nào cả hai — và cách trình bày quyết định đó cho team lẫn stakeholder một cách thuyết phục.

Khái niệm cốt lõi

Data pipeline, hiểu đơn giản, là chuỗi các bước đưa dữ liệu từ nguồn (source: database giao dịch, log, event từ app, API bên thứ ba) đến đích (destination: data warehouse, dashboard, mô hình ML, hệ thống ra quyết định), qua các bước biến đổi (transform) ở giữa. Câu hỏi kiến trúc lớn nhất là: dữ liệu di chuyển qua pipeline này theo nhịp nào?

Batch processing — xử lý theo lô

Batch xử lý một khối dữ liệu lớn tại một thời điểm, theo lịch cố định: mỗi giờ, mỗi đêm, mỗi tuần. Đặc trưng của batch:

  • Độ trễ (latency): phút đến giờ. Dữ liệu phát sinh lúc 10h sáng có thể chỉ xuất hiện trong báo cáo lúc 2h sáng hôm sau.
  • Ranh giới rõ ràng: mỗi lô có điểm bắt đầu và kết thúc xác định ("toàn bộ đơn hàng ngày 26/06").
  • Throughput cao, chi phí trên mỗi bản ghi thấp: vì gom lại xử lý một lần nên tận dụng được tài nguyên rất hiệu quả.
  • Dễ vận hành và debug: nếu lô đêm nay lỗi, bạn chạy lại (re-run) toàn bộ lô đó, kết quả xác định, không mất mát.
Công cụ điều phối (orchestration): Apache Airflow (phổ biến nhất, chuẩn de facto), Dagster (thiên về data-asset, kiểm soát chất lượng tốt hơn), Prefect (Pythonic, nhẹ nhàng cho team nhỏ). Những công cụ này không tự xử lý dữ liệu — chúng lập lịch và điều phối các bước, quản lý dependency giữa các tác vụ (task A xong mới chạy task B), retry khi lỗi, và cảnh báo.

Công cụ tính toán (compute): Apache Spark (xử lý phân tán trên bộ nhớ, thống trị mảng batch quy mô lớn), Hadoop/MapReduce (thế hệ cũ hơn, nay chủ yếu là legacy), hoặc đơn giản là các câu SQL chạy trực tiếp trên data warehouse như BigQuery, Snowflake, Redshift.

Stream processing — xử lý theo luồng

Stream xử lý dữ liệu ngay khi từng sự kiện phát sinh, liên tục, không có điểm kết thúc. Đặc trưng:

  • Độ trễ: mili-giây đến vài giây. Sự kiện xảy ra và được phản ánh gần như tức thì.
  • Vô hạn (unbounded): luồng không bao giờ "kết thúc"; bạn xử lý trong các cửa sổ thời gian (windows) — ví dụ "tổng giao dịch trong 5 phút gần nhất".
  • Trạng thái (stateful) phức tạp: để tính "trung bình 5 phút" bạn phải giữ trạng thái trong bộ nhớ và cập nhật liên tục — đây là nguồn gốc của phần lớn độ phức tạp.
  • Vận hành khó hơn nhiều: xử lý sự kiện đến trễ (late data), sự kiện đến sai thứ tự (out-of-order), đảm bảo exactly-once, khôi phục trạng thái khi node chết... đều là những bài toán khó.
Công cụ: Apache Kafka (nền tảng truyền tải sự kiện — event backbone), Apache Flink (chuẩn vàng cho stream processing stateful), Kafka Streams, Spark Structured Streaming (dạng micro-batch, gần như streaming), các managed service như AWS Kinesis, Google Cloud Dataflow.

Micro-batch — vùng xám ở giữa

Đừng nghĩ batch và stream là hai thái cực đối lập tuyệt đối. Có một vùng giữa gọi là micro-batch: gom dữ liệu thành các lô rất nhỏ (ví dụ mỗi 5-30 giây) rồi xử lý. Spark Structured Streaming về bản chất là micro-batch. Nó cho bạn độ trễ "gần real-time" (vài giây) với độ phức tạp thấp hơn stream thuần túy. Rất nhiều bài toán "tưởng cần stream" thực ra chỉ cần micro-batch — và đây là chỗ tiết kiệm chi phí lớn nếu bạn nhận ra sớm.

Câu hỏi quyết định cốt lõi

Khi đứng trước một bài toán, hãy hỏi: "Giá trị của dữ liệu này giảm đi bao nhiêu theo thời gian?" (data value decay).

  • Nếu giá trị không đổi dù dữ liệu đến sau vài giờ (báo cáo tài chính cuối ngày, phân tích hành vi khách hàng theo tháng) → batch.
  • Nếu giá trị sụp đổ chỉ sau vài giây (chặn giao dịch gian lận, giá cổ phiếu, điều phối tài xế) → stream.
  • Nếu ở giữa (dashboard vận hành cập nhật mỗi phút) → micro-batch.

Tình huống thực tế

Ví dụ 1 — Sàn TMĐT: báo cáo doanh thu và data warehouse (batch thắng)

Hãy tưởng tượng một sàn thương mại điện tử tầm trung ở Việt Nam, giả định tên là ShopViet, xử lý khoảng 200.000 đơn hàng/ngày. Đội data mới thành lập, và có một áp lực từ ban giám đốc: "Phải real-time hết, phải hiện đại." Đội định xây toàn bộ pipeline phân tích trên Kafka + Flink.

May mắn là kiến trúc sư trưởng dừng lại và hỏi đúng câu hỏi decay: các báo cáo mà ban giám đốc thực sự dùng là gì? Câu trả lời: doanh thu theo ngày, tỷ lệ hoàn trả theo tuần, top sản phẩm theo tháng, phân tích cohort khách hàng. Không có báo cáo nào cần độ trễ dưới 1 giờ. Ban giám đốc xem báo cáo lúc 9h sáng mỗi ngày.

Quyết định: pipeline batch với Airflow điều phối, chạy một job mỗi đêm lúc 2h sáng. Spark đọc dữ liệu đơn hàng từ database giao dịch (qua bản sao replica để không ảnh hưởng production), transform, ghi vào BigQuery. Chi phí vận hành khoảng vài triệu đồng/tháng, một kỹ sư part-time đủ để bảo trì.

Nếu đi theo Kafka + Flink: cần ít nhất 2-3 kỹ sư stream chuyên trách (hiếm và đắt ở thị trường Việt Nam), cụm hạ tầng chạy 24/7, và độ phức tạp vận hành cao gấp nhiều lần — để giải quyết một bài toán mà không ai cần real-time.

Bài học: Đừng để từ khóa "hiện đại" hay "real-time" dẫn dắt quyết định. Hãy để nhu cầu kinh doanh thực tế (giá trị dữ liệu giảm bao nhiêu theo thời gian) dẫn dắt. Trong đa số bài toán analytics, batch là lựa chọn đúng, rẻ, và bền vững.

Ví dụ 2 — Ứng dụng gọi xe: điều phối tài xế và chống gian lận (stream là bắt buộc)

Lấy bối cảnh một ứng dụng gọi xe kiểu Grab/Gojek/Be tại Đông Nam Á. Có hai bài toán mà batch không thể giải:

Điều phối tài xế: khi một khách bấm đặt xe ở quận 1, hệ thống phải tìm tài xế phù hợp trong bán kính 2km, tính giá động (surge pricing) dựa trên cung-cầu ngay lúc đó. Nếu dùng batch chạy mỗi 5 phút, thông tin vị trí tài xế đã lạc hậu hoàn toàn — tài xế bạn gán có thể đã đi mất. Giá trị dữ liệu vị trí giảm về 0 chỉ sau vài giây.

Chống gian lận: một tài khoản tạo hàng loạt cuốc xe giả để trục lợi khuyến mãi. Nếu phát hiện qua báo cáo batch sáng hôm sau, tiền khuyến mãi đã bị rút mất. Phải chặn trong lúc giao dịch đang diễn ra.

Kiến trúc: vị trí GPS của tài xế được đẩy liên tục vào Kafka (hàng trăm nghìn event/giây). Flink xử lý stream này với các cửa sổ trượt (sliding window), duy trì trạng thái về mật độ cung-cầu theo từng khu vực, và chạy quy tắc chống gian lận real-time. Kết quả (giá surge, quyết định chặn) phản hồi trong dưới 1 giây.

Ở đây, độ phức tạp và chi phí của stream là chính đáng và không thể tránh — vì bản chất bài toán yêu cầu độ trễ dưới giây. Đội chấp nhận đầu tư nhân sự chuyên môn cao, hạ tầng 24/7, và các cơ chế xử lý late/out-of-order data.

Bài học: Khi giá trị kinh doanh sụp đổ theo thời gian thực, stream không phải lựa chọn "cho sang" — nó là yêu cầu bắt buộc. Và khi đã chọn stream, hãy chấp nhận trọn gói cái giá về độ phức tạp vận hành.

Ví dụ 3 — Fintech: kiến trúc Lambda (kết hợp cả hai) và cái bẫy của nó

Một công ty fintech giả định tên PayNhanh, làm ví điện tử. Họ cần hai thứ tưởng mâu thuẫn: dashboard giao dịch cập nhật gần real-time cho đội vận hành (phát hiện sự cố cổng thanh toán ngay), báo cáo tài chính chính xác tuyệt đối cho kế toán, đối soát cuối ngày (không được sai một đồng).

Họ áp dụng kiến trúc Lambda: một speed layer (stream, dùng Kafka + Flink) cho dashboard gần real-time — chấp nhận độ chính xác "đủ tốt", có thể sai lệch nhỏ; và một batch layer chạy mỗi đêm — tính toán lại toàn bộ từ nguồn gốc, cho ra con số chính xác tuyệt đối dùng cho đối soát và báo cáo pháp lý. Batch layer "sửa lại" mọi sai lệch của speed layer.

Ban đầu hoạt động tốt. Nhưng sau một năm, đội nhận ra cái bẫy kinh điển của Lambda: họ phải viết và bảo trì logic nghiệp vụ hai lần — một bản cho stream (Flink), một bản cho batch (Spark). Mỗi khi thay đổi quy tắc tính phí, phải sửa hai nơi, và hai nơi dễ lệch nhau, gây bug khó truy. Chi phí bảo trì tăng gấp đôi.

Giải pháp họ chuyển sang là kiến trúc Kappa: chỉ dùng một đường stream duy nhất làm nguồn chân lý. Cần tính lại lịch sử? Họ replay lại toàn bộ event từ Kafka (giữ log lâu dài) qua cùng một logic stream. Không còn hai bản code. Đánh đổi: cần hạ tầng streaming đủ mạnh và kỷ luật giữ event log — nhưng loại bỏ được sự trùng lặp logic.

Bài học: Kiến trúc lai (Lambda) nghe hấp dẫn nhưng chi phí ẩn là logic bị nhân đôi. Trước khi chọn Lambda, hãy tự hỏi: batch layer có thật sự cần thiết không, hay Kappa/replay đã đủ? Nhiều đội chọn Lambda quá sớm rồi trả giá bằng nợ kỹ thuật.

Hướng dẫn từng bước

Đây là quy trình ra quyết định batch vs stream cho một bài toán cụ thể:

Bước 1 — Xác định yêu cầu độ trễ từ góc nhìn kinh doanh, không phải kỹ thuật. Hỏi stakeholder: "Nếu dữ liệu này đến trễ 1 phút thì sao? 1 giờ? 1 ngày?" Ghi lại con số latency tối đa chấp nhận được (SLA). Đây là đầu vào quan trọng nhất, và nó phải đến từ nghiệp vụ, không phải từ ước muốn "cho nhanh".

Bước 2 — Vẽ đường cong data value decay. Với latency SLA đã có, phân loại: giá trị không đổi trong ngày → batch; giảm mạnh trong phút → micro-batch; sụp đổ trong giây → stream.

Bước 3 — Ước lượng khối lượng và tần suất. Bao nhiêu bản ghi/ngày? Cao điểm bao nhiêu event/giây? Batch phù hợp với khối lớn theo lịch; stream phù hợp với dòng liên tục. Con số này ảnh hưởng lựa chọn compute (SQL thuần vs Spark vs Flink).

Bước 4 — Đánh giá năng lực đội ngũ. Đây là yếu tố thực tế thường bị bỏ qua. Đội của bạn có ai vận hành được Flink/Kafka ở production không? Ở thị trường Việt Nam, kỹ sư stream giỏi rất hiếm. Nếu không có, một pipeline stream sẽ trở thành gánh nặng vận hành và điểm lỗi thường trực. Đôi khi micro-batch là lựa chọn khôn ngoan chỉ vì nó phù hợp năng lực đội.

Bước 5 — Bắt đầu từ đơn giản nhất thỏa yêu cầu. Nguyên tắc vàng: chọn kiến trúc đơn giản nhất đáp ứng được SLA. Thứ tự ưu tiên tăng dần độ phức tạp: SQL trên warehouse → batch với Spark → micro-batch → stream thuần → Lambda/Kappa. Chỉ leo lên bậc phức tạp hơn khi bậc dưới thực sự không đủ.

Bước 6 — Thiết kế cho khả năng chạy lại (idempotency & reprocessing). Dù batch hay stream, pipeline phải cho phép chạy lại một khoảng dữ liệu mà không tạo bản ghi trùng. Điều này cứu bạn khi có bug hoặc dữ liệu nguồn thay đổi.

Bước 7 — Ghi lại quyết định thành ADR. Batch vs stream là một quyết định kiến trúc trọng yếu, đắt để đảo ngược. Hãy viết Architecture Decision Record: bối cảnh, các lựa chọn đã cân nhắc, latency SLA, quyết định, và hệ quả. Sáu tháng sau khi có người hỏi "sao hồi đó chọn batch?", ADR trả lời thay bạn.

Lỗi thường gặp & mẹo

Lỗi 1 — Chọn stream vì "nghe hiện đại". Đây là lỗi phổ biến nhất và tốn kém nhất. Stream mang theo độ phức tạp vận hành khổng lồ. Nếu nghiệp vụ không yêu cầu real-time, bạn đang trả giá cao cho thứ không ai dùng đến. Mẹo: luôn bắt đầu bằng câu hỏi decay, và luôn thử xem batch/micro-batch có đủ không trước.

Lỗi 2 — Nhầm "gần real-time" với "real-time thật". Nhiều dashboard "real-time" thực ra chỉ cần cập nhật mỗi 30 giây — đó là micro-batch, đơn giản hơn stream thuần nhiều lần. Mẹo: ép stakeholder nói con số cụ thể. "Real-time" là một từ vô nghĩa cho đến khi có con số latency đi kèm.

Lỗi 3 — Bỏ qua data quality trong batch. Batch dễ ru ngủ: job chạy xong, tưởng ổn, nhưng dữ liệu vào sai âm thầm. Mẹo: thêm bước kiểm tra chất lượng (data validation, ví dụ với Great Expectations hoặc kiểm tra ngay trong Dagster) — chặn dữ liệu bẩn trước khi nó lan vào warehouse.

Lỗi 4 — Quên xử lý late & out-of-order data trong stream. Trong thực tế, event từ mobile app đến trễ vì mất mạng là chuyện thường. Nếu không có chiến lược watermark, số liệu stream sẽ sai. Mẹo: dùng event-time (thời điểm sự kiện thực sự xảy ra) thay vì processing-time, và cấu hình watermark hợp lý.

Lỗi 5 — Nhân đôi logic khi làm Lambda mà không nhận ra. Như ví dụ PayNhanh. Mẹo: nếu buộc phải lai, hãy cố gắng chia sẻ tối đa logic nghiệp vụ giữa hai layer (ví dụ tách logic thành thư viện dùng chung), hoặc cân nhắc Kappa để chỉ có một đường.

Lỗi 6 — Coupling pipeline trực tiếp vào database production. Batch job đọc thẳng DB production có thể làm nghẽn hệ thống chính. Mẹo: luôn đọc qua replica hoặc dùng CDC (Change Data Capture) để tách tải phân tích khỏi tải giao dịch.

Bài tập thực hành

Bài tập 1 — Phân loại. Cho 5 bài toán sau, hãy quyết định batch, micro-batch hay stream, và ghi rõ latency SLA bạn suy ra: (a) báo cáo lương cuối tháng; (b) cảnh báo khi kho hết hàng bán chạy; (c) gợi ý sản phẩm "khách khác cũng mua"; (d) phát hiện thẻ tín dụng bị dùng gian lận; (e) dashboard số người đang online. Viết 1-2 câu lý giải cho mỗi.

Bài tập 2 — Viết mini-ADR. Chọn một trong 5 bài toán trên (hoặc một bài toán thật ở công ty bạn). Viết một ADR ngắn (khoảng 1 trang) gồm: Bối cảnh, Latency SLA, Các lựa chọn cân nhắc (ít nhất 2), Quyết định, Hệ quả (cả tích cực lẫn tiêu cực).

Bài tập 3 — Phản biện Lambda. Cho tình huống PayNhanh, hãy viết ra 3 câu hỏi bạn sẽ đặt cho đội để kiểm tra xem họ có thực sự cần Lambda không, hay Kappa/batch thuần đã đủ.

Bài tập 4 — Ước lượng chi phí. Với sàn ShopViet (200.000 đơn/ngày), hãy phác thảo (bằng con số ước lượng của riêng bạn) sự chênh lệch chi phí vận hành hằng năm giữa phương án batch với Airflow+Spark và phương án stream với Kafka+Flink, tính cả nhân sự. Mục tiêu là luyện thói quen định lượng đánh đổi.

Tóm tắt

Quyết định batch vs stream là một trong những quyết định kiến trúc dữ liệu tốn kém nhất, và nó nên được dẫn dắt bởi một câu hỏi kinh doanh: giá trị của dữ liệu này giảm bao nhiêu theo thời gian?

  • Batch: xử lý theo lô, độ trễ phút–giờ, throughput cao, chi phí thấp, dễ vận hành. Đúng cho phần lớn analytics, báo cáo, data warehouse. Công cụ: Airflow/Dagster/Prefect điều phối, Spark/SQL tính toán.
  • Stream: xử lý từng event tức thì, độ trễ mili-giây–giây, phức tạp và đắt để vận hành. Bắt buộc khi giá trị dữ liệu sụp đổ theo thời gian thực (chống gian lận, điều phối, giá động). Công cụ: Kafka + Flink.
  • Micro-batch: vùng giữa, cho độ trễ "gần real-time" với độ phức tạp thấp hơn stream thuần — thường là câu trả lời đúng cho các dashboard "tưởng cần real-time".
  • Lambda vs Kappa: kiến trúc lai giải quyết cả hai nhu cầu nhưng trả giá bằng logic nghiệp vụ bị nhân đôi; Kappa (một đường stream + replay) là hướng đơn giản hóa đáng cân nhắc.
Nguyên tắc cuối cùng, cũng là nguyên tắc quan trọng nhất: chọn kiến trúc đơn giản nhất đáp ứng được SLA thực tế của nghiệp vụ, cân nhắc năng lực đội ngũ, và ghi lại quyết định bằng ADR. Đừng để "hiện đại" hay "real-time" trở thành lý do — hãy để con số latency và giá trị kinh doanh quyết định.