DataOps nâng cao 9 — Streaming & Real-time DataOps

13 thg 7, 2026 2 lượt xem
#kafka
#data-engineering
#streaming
#real-time
#dataops

Vì sao streaming làm khó DataOps

Cả series này bàn về DataOps cho pipeline batch: chạy theo lịch, xử lý một mẻ dữ liệu hữu hạn, xong thì tắt. Streaming đảo ngược mọi giả định đó. Một job streaming chạy 24/7, xử lý một luồng vô tận không có điểm "kết thúc", trạng thái (state) tồn tại giữa các sự kiện, và bạn không thể "chạy lại từ đầu" một cách ngây thơ như batch. Mọi kỷ luật DataOps ta đã dựng — test, observability, CI/CD, contract, incident — đều phải viết lại cho bối cảnh này.

Bốn khác biệt cốt lõi khiến streaming khó:

Khía cạnhBatchStreaming
Vòng đờichạy rồi tắt, có bắt đầu/kết thúcchạy mãi, không có "xong"
Dữ liệutập hữu hạn, biết trước kích thướcluồng vô tận, không biết khi nào hết
Testso output với input đã biếtphải bơm sự kiện theo thời gian, có state
Deployghi đè code, chạy mẻ sauphải nâng cấp mà không mất state đang chạy

Batch có một luxury lớn: nếu mẻ hôm nay sai, bạn sửa code và chạy lại mẻ đó — dữ liệu cũ vẫn nằm nguyên trong kho. Streaming không có cái đó. Sự kiện chảy qua rồi thì đã tác động (đã cập nhật số dư nghi ngờ, đã bắn cảnh báo gian lận). Sai một khoảnh khắc là sai trên production trực tiếp, và "chạy lại" nghĩa là replay — phát lại lịch sử sự kiện, một thao tác phải thiết kế chủ đích chứ không có sẵn.

Bối cảnh ngân hàng làm sức ép này rõ nhất: giám sát giao dịch và gian lận real-time. Mỗi giao dịch thẻ/chuyển khoản phải được chấm điểm rủi ro trong mili-giây để chặn kịp. Pipeline này không được sai, không được nghẽn, không được mất sự kiện — nhưng vẫn phải có đủ chất lượng dữ liệu và khả năng điều tra bằng replay khi có nghi vấn.

Kiến trúc streaming DataOps

Xương sống của mọi pipeline real-time hiện đại là bốn tầng: nguồn → message broker (Kafka) → tầng xử lý → sink, với data quality và observability gắn xuyên suốt.

Nguồn phát sự kiện: ứng dụng đẩy trực tiếp, hoặc CDC (Change Data Capture) bắt thay đổi từ database vận hành. CDC là nguồn streaming phổ biến nhất trong ngân hàng — mọi INSERT/UPDATE ở core banking thành một luồng sự kiện — nên Kafka & streaming ingestion coi CDC là ca dùng trung tâm.

Kafka (kiến trúc Kafka) là bộ đệm bền (durable log) ngồi giữa: nó tách nguồn khỏi nơi xử lý, giữ sự kiện lại N ngày (retention) để có thể phát lại, và phân vùng (partition) để mở rộng ngang. Chính khả năng giữ lại và phát lại log của Kafka là thứ biến "chạy lại" của streaming thành khả thi.

Tầng xử lý biến đổi luồng: Apache Flink (streaming-native, state mạnh, exactly-once tốt), Spark Structured Streaming (micro-batch, hợp đội đã dùng Spark), hoặc Kafka Streams (thư viện nhẹ, chạy trong app JVM). Đây cũng là nơi kiểm chất lượng inline.

Sink là đích: lakehouse cho phân tích, ClickHouse cho truy vấn thời gian thực, hoặc OLTP cho hệ thống vận hành ăn kết quả.

Đảm bảo đúng đắn: nền tảng không thể thiếu

Ba vấn đề đúng đắn quyết định pipeline streaming có tin được hay không.

Exactly-once & idempotent

Trong hệ phân tán, khi một node chết và tái xử lý, một sự kiện có thể bị xử lý hai lần. Với dữ liệu tài chính, đếm một giao dịch hai lần là thảm hoạ. Có ba mức đảm bảo: at-most-once (có thể mất), at-least-once (không mất nhưng có thể trùng), và exactly-once (mỗi sự kiện tác động đúng một lần).

Flink đạt exactly-once bằng checkpoint phối hợp state với offset Kafka; khi khôi phục, nó tua state và offset về cùng một thời điểm nhất quán. Nhưng exactly-once đầu-cuối còn cần sink hợp tác — hoặc sink hỗ trợ transaction (two-phase commit), hoặc bạn thiết kế ghi idempotent: ghi cùng một sự kiện nhiều lần cho cùng kết quả, thường bằng khoá tự nhiên (transaction_id) và UPSERT/dedup. Idempotency là "dây an toàn" thực dụng khi exactly-once tuyệt đối quá đắt.

Schema evolution với schema registry

Nguồn sẽ đổi schema — thêm trường, đổi kiểu. Trong batch bạn thấy khi chạy; trong streaming, một producer đẩy schema mới có thể làm gãy mọi consumer đang chạy tức thì. Giải pháp là Schema Registry (Confluent/Apicurio) quản lý schema Avro hoặc Protobuf tập trung, ép quy tắc tương thích (compatibility): chỉ cho phép thay đổi backward/forward compatible (thêm trường có default thì được, xoá trường bắt buộc thì bị chặn ngay lúc publish). Đây chính là data contract (Quality & Contracts) áp cho streaming — thực thi tại điểm ghi, không phải phát hiện muộn ở downstream.

Late & out-of-order data — watermark

Sự kiện tới không đúng thứ tựmuộn là bình thường: điện thoại mất sóng, gửi giao dịch chậm 5 phút. Nếu bạn gộp "doanh số theo phút", sự kiện muộn thuộc phút nào? Event time (lúc việc xảy ra) khác processing time (lúc hệ thống nhận). Streaming engine dùng watermark — một mốc "ta tin đã nhận hết sự kiện có event-time ≤ T" — để quyết định khi nào đóng cửa sổ (window) và phát kết quả. Đặt watermark quá chặt thì bỏ sót sự kiện muộn; quá lỏng thì kết quả trễ. Cân bằng này là quyết định kỹ thuật quan trọng, không có mặc định đúng cho mọi ca.

Streaming data quality — khó hơn batch nhiều

Trong batch, kiểm chất lượng là bước tách biệt: mẻ chạy xong, chạy test, fail thì chặn không cho publish (như DataOps 5 mô tả). Streaming không có "sau khi chạy xong" — luồng không bao giờ dừng. Nên DQ phải inline, trên luồng, không được dừng luồng.

Nguyên tắc streaming DQ:

  • Contract/schema check lúc ingest. Mỗi sự kiện được validate ngay khi vào (schema registry ở tầng broker, + business rule ở tầng xử lý): kiểu đúng, trường bắt buộc có, giá trị trong miền (amount > 0, currency hợp lệ).
  • Dead-letter queue (DLQ) cho bản ghi xấu. Bản ghi hỏng không làm gãy job và không lọt vào sink. Nó được định tuyến sang một topic riêng (DLQ) kèm lý do lỗi, để điều tra/sửa/replay sau. Luồng chính vẫn chảy.
  • Gắn cờ & cảnh báo real-time. Bất thường (null rate của trường khoá tăng vọt, tỷ lệ vào DLQ tăng) bắn cảnh báo ngay, không chờ cuối ngày.
  • Không dừng luồng. Đây là khác biệt tư tưởng lớn nhất so với batch: batch DQ được phép "chặn cứng"; streaming DQ cô lập cái xấu (DLQ) và cảnh báo, nhưng để cái tốt tiếp tục chảy. Dừng luồng vì vài bản ghi xấu nghĩa là ngừng chấm điểm gian lận cho tất cả giao dịch — tệ hơn nhiều.

Minh hoạ định tuyến DQ inline (pseudocode, không phải SQL chạy được):

for mỗi event trong stream:
    ok, reason = validate(event)          # schema + business rule
    if ok:
        emit_to(sink, normalize(event))   # đường chính
    else:
        emit_to(dead_letter_topic,        # cô lập, không dừng luồng
                event.with(error=reason, ts=now))
    metrics.record(ok, reason)            # feed observability & alert
Batch DQStreaming DQ
Thời điểmsau khi mẻ chạy xonginline, khi sự kiện đi qua
Xử lý lỗifail mẻ, chặn publishđịnh tuyến DLQ, luồng vẫn chảy
Phản hồibáo cáo cuối mẻcảnh báo real-time
Tư duy"chặn cứng""cô lập + cảnh báo"

Testing streaming — vì sao và làm sao

Test streaming khó vì logic phụ thuộc thời gian và state: kết quả của một cửa sổ phụ thuộc thứ tự và thời điểm sự kiện tới. Ba tầng test:

  • Unit test cho operator. Test một hàm/toán tử (map, filter, aggregate) bằng cách bơm một chuỗi sự kiện có event-time cụ thể (kể cả sự kiện muộn) và assert output. Flink/Spark có test harness bơm được cả watermark để kiểm hành vi cửa sổ.
  • Integration với test topic. Dựng một Kafka thật (thường bằng Testcontainers), tạo topic test riêng, cho job chạy end-to-end trên vài bản ghi biết trước, đọc sink kiểm kết quả. Đây là nơi bắt lỗi serialize, schema, kết nối.
  • Replay / regression. Lấy một lát log production đã ẩn danh, phát lại qua job phiên bản mới, so kết quả với baseline. Vừa test hồi quy vừa kiểm hiệu năng dưới tải thật.

Observability streaming

Observability của DataOps 6 vẫn áp dụng, nhưng streaming có bộ chỉ số riêng phải theo dõi:

Chỉ sốÝ nghĩaBáo động khi
Consumer lagsố sự kiện consumer còn tụt sau producerlag tăng liên tục = xử lý không kịp, sắp nghẽn
Throughputsự kiện/giây vào và ratụt đột ngột = nguồn hoặc job có vấn đề
Độ trễ end-to-endtừ lúc sinh sự kiện tới lúc vào sinkvượt SLA (vd fraud phải < 500ms)
Checkpoint healthtần suất & thời gian checkpoint thành côngcheckpoint chậm/fail = state khó khôi phục, exactly-once lung lay
Tỷ lệ DLQ% bản ghi vào dead-lettertăng vọt = nguồn đổi định dạng / lỗi upstream

Consumer lag là tín hiệu số một: nó là "freshness" của thế giới streaming. Lag ổn định ≈ 0 nghĩa là bạn theo kịp real-time; lag tăng dần nghĩa là đang tích luỹ nợ và độ trễ sẽ phình. Checkpoint health là đặc thù streaming không có ở batch: checkpoint là thứ cho phép khôi phục và exactly-once, nên checkpoint fail âm thầm là rủi ro nghiêm trọng cần alert.

Deploy & versioning job streaming

Đây là chỗ streaming DataOps khác batch nhất. Bạn không thể chỉ "ghi đè code": job đang giữ state (đếm dồn, cửa sổ đang mở, dedup cache). Restart ngây thơ là mất state.

  • Savepoint (Flink). Trước khi nâng cấp, chụp một savepoint — snapshot toàn bộ state + offset. Tắt job cũ, khởi động code mới từ savepoint đó: state được nạp lại, job tiếp tục đúng chỗ đã dừng. Đây là cơ chế "nâng cấp không mất state".
  • Blue-green cho streaming. Chạy song song job mới (green) đọc cùng nguồn, đối chiếu output với job cũ (blue) trên một khoảng, rồi cắt chuyển. Chi phí gấp đôi trong lúc chuyển nhưng an toàn cho pipeline quan trọng.
  • Backfill/reprocess bằng replay. Nhờ Kafka giữ log, "chạy lại" = reset consumer offset về quá khứ và cho job xử lý lại từ đó. Đây là câu trả lời của streaming cho "backfill mẻ" của batch — và là công cụ điều tra: khi nghi ngờ một khoảng giao dịch, phát lại chính lát log đó qua job (hoặc job debug) để tái dựng.
# Nâng cấp job Flink không mất state (minh hoạ CLI, không phải SQL)
flink stop --savepointPath s3://savepoints/fraud/  <job-id>     # chụp state + tắt
flink run  --fromSavepoint s3://savepoints/fraud/sp-2026-... \
           fraud-scoring-v2.jar                                 # code mới, nạp lại state

Lambda vs Kappa & streaming lakehouse

Hai kiến trúc tham chiếu cho hệ vừa cần real-time vừa cần lịch sử đầy đủ:

  • Lambda — chạy hai đường song song: batch layer (chính xác, chậm, xử lý lại toàn bộ) và speed layer (nhanh, gần đúng, real-time), rồi hợp nhất khi phục vụ. Ưu: kết quả batch luôn "chuẩn vàng". Nhược: duy trì hai codebase cho cùng logic — nguồn cơn bug lệch nhau.
  • Kappachỉ một đường streaming. Cần "chạy lại lịch sử"? Replay từ Kafka qua chính pipeline streaming đó. Một codebase, đơn giản hơn, và là hướng chủ đạo hiện nay nhờ Kafka retention dài + engine đủ mạnh.

Streaming lakehouse thu hẹp khoảng cách batch/stream: bảng Delta/Iceberg vừa nhận ghi streaming vừa cho truy vấn như bảng thường; dbt đã có streaming tables/materialized view cho phép định nghĩa biến đổi bằng SQL khai báo mà chạy tăng dần liên tục — mang mô hình dbt/ELT quen thuộc sang gần real-time, làm mờ ranh giới với batch.

Khi nào THỰC SỰ cần real-time

Real-time không miễn phí: vận hành 24/7, on-call, state, exactly-once, chi phí hạ tầng đều cao hơn batch nhiều. Đừng chọn streaming vì "nghe hiện đại". Hỏi: quyết định có phải xảy ra trong vài giây không?

Nhu cầuĐộ trễ chấp nhậnChọn
Chặn giao dịch gian lậnmili-giâyStreaming thật (bắt buộc)
Cảnh báo số dư bất thườnggiây–phútStreaming / near-real-time
Dashboard vận hànhvài phútMicro-batch (mỗi 1–5 phút)
Báo cáo dư nợ hằng ngàygiờBatch đêm

Rất nhiều nhu cầu "real-time" thực chất là near-real-timemicro-batch mỗi vài phút giải quyết được với chi phí thấp hơn nhiều — không state phức tạp, không on-call 24/7, không exactly-once. So sánh tổng chi phí sở hữu trước khi cam kết streaming là một quyết định FinOps thực thụ; chọn real-time cho thứ chỉ cần 5 phút là đốt tiền và công vận hành vô ích.

Đối chiếu: kiểm bất thường theo cửa sổ thời gian (batch)

Để thấy rõ streaming DQ khác batch DQ thế nào, dưới đây là cách batch làm cùng ý tưởng "phát hiện đột biến giao dịch theo cửa sổ thời gian" — nhưng chạy một lần trên dữ liệu tĩnh thay vì inline trên luồng. Câu này gom giao dịch theo từng giờ, so với trung bình trượt của các giờ trước, và gắn cờ giờ nào lệch quá 50% (ép ::numeric khi ROUND). Trong streaming, cùng logic này chạy liên tục trên cửa sổ event-time với watermark:

-- ▶ Chạy được
WITH hourly AS (
  SELECT date_trunc('hour', created_at) AS h,
         COUNT(*)                        AS n_txn,
         SUM(amount)                     AS total_amount
  FROM transactions
  GROUP BY date_trunc('hour', created_at)
),
scored AS (
  SELECT
    h, n_txn, total_amount,
    AVG(n_txn) OVER (ORDER BY h ROWS BETWEEN 6 PRECEDING AND 1 PRECEDING) AS baseline
  FROM hourly
)
SELECT
  h, n_txn,
  ROUND(baseline::numeric, 1)                                           AS baseline_6h,
  ROUND((100.0 * (n_txn - baseline) / NULLIF(baseline, 0))::numeric, 1) AS pct_diff,
  CASE
    WHEN baseline IS NULL THEN 'no-baseline'
    WHEN ABS(n_txn - baseline) > 0.50 * baseline THEN 'SPIKE'
    ELSE 'ok'
  END                                                                  AS flag
FROM scored
ORDER BY h DESC;

Điểm mấu chốt: batch chạy query này sau khi dữ liệu đã nằm yên; streaming phải quyết định "giờ này đã đủ dữ liệu để kết luận chưa" bằng watermark, và phát cảnh báo ngay khi cửa sổ đóng — không đợi tới cuối ngày.

Use case thực tế

Bối cảnh: NCB xây pipeline giám sát gian lận giao dịch thẻ real-time. Mục tiêu: chấm điểm rủi ro mỗi giao dịch trong < 500ms để chặn kịp, đồng thời đảm bảo chất lượng dữ liệu và điều tra được bằng replay khi có tranh chấp/khiếu nại.

Kiến trúc & DataOps áp dụng:

  1. Nguồn → Kafka. Cổng thanh toán và CDC từ core (Debezium) đẩy sự kiện giao dịch vào topic txn.events (phân vùng theo card_id để cùng thẻ vào cùng partition, giữ thứ tự). Schema Avro quản trong Schema Registry, chế độ backward-compatible — producer thêm trường mới không làm gãy consumer cũ.
  2. Xử lý (Flink) + DQ inline. Job Flink giữ state theo thẻ (số giao dịch/phút, địa điểm gần nhất), tính điểm rủi ro trên cửa sổ event-time với watermark 10 giây (chấp sự kiện muộn tới 10s). DQ inline: giao dịch thiếu amount/currency sai → DLQ txn.deadletter kèm lý do, không dừng luồng. Exactly-once qua checkpoint mỗi 30s, sink UPSERT theo transaction_id (idempotent).
  3. Sink kép. Điểm rủi ro + quyết định chặn ghi vào OLTP (hệ vận hành phản hồi ngay) và vào ClickHouse (phân tích real-time cho đội giám sát).
  4. Observability. Bảng theo dõi consumer lag, độ trễ end-to-end (P99 phải < 500ms), checkpoint health, tỷ lệ DLQ. Lag tăng dần hoặc tỷ lệ DLQ vọt → alert on-call.
  5. Deploy. Nâng model v2: chụp savepoint, chạy blue-green đối chiếu điểm v1/v2 trên 1 giờ live, khớp thì cắt chuyển — không mất state đếm theo thẻ.

Sự cố & replay: một khiếu nại nghi ngờ chuỗi 8 giao dịch bị chặn oan trong khung 14h00–14h15 hôm trước. Đội reset offset của một job điều tra về đúng khoảng đó trên txn.events, replay qua job có log chi tiết, tái dựng chính xác điểm rủi ro và lý do từng quyết định — dựng lại được bằng chứng vì Kafka còn giữ log (retention 7 ngày). Kết quả: xác định 2/8 giao dịch bị chặn do một rule quá nhạy, tinh chỉnh ngưỡng, deploy bằng savepoint. Không giao dịch nào bị xử lý hai lần (idempotent), và toàn bộ điều tra làm được mà không đụng vào luồng production đang chạy.

Ghi nhớ

  • Streaming đảo giả định của batch: chạy 24/7, dữ liệu vô tận, có state, "chạy lại" = replay chứ không chạy lại mẻ. Mọi kỷ luật DataOps phải viết lại cho bối cảnh này.
  • Kiến trúc: nguồn (thường là CDC) → Kafka (log bền, giữ để replay) → xử lý Flink/Spark Streaming → sink (lakehouse/ClickHouse/OLTP).
  • Đúng đắn là nền: exactly-once + idempotent (đừng đếm giao dịch hai lần), schema registry (Avro/Protobuf) ép tương thích, watermark xử lý dữ liệu muộn/lệch thứ tự theo event-time.
  • Streaming DQ = cô lập + cảnh báo, KHÔNG dừng luồng: contract check lúc ingest, dead-letter queue cho bản ghi xấu, alert real-time — khác batch "chặn cứng".
  • Testing: unit test operator (bơm event có event-time), integration với test topic, replay lát log production để hồi quy.
  • Observability riêng của streaming: consumer lag (số một), throughput, độ trễ end-to-end, checkpoint health, tỷ lệ DLQ.
  • Deploy không mất state: savepoint + blue-green; backfill/điều tra = replay offset từ Kafka.
  • Lambda (hai đường, hai codebase) vs Kappa (một đường streaming + replay); streaming lakehouse & dbt streaming tables làm mờ ranh giới batch/stream.
  • Chỉ chọn real-time khi quyết định phải xảy ra trong vài giây; phần lớn nhu cầu chỉ cần micro-batch/near-real-time — rẻ hơn nhiều, một quyết định FinOps.

Bài viết liên quan

CSV/JSON/Parquet/Avro, lưu trữ theo hàng vs cột, nén, và OLTP vs OLAP.

13 thg 7, 2026 8

Data Engineering là gì, vai trò trong vòng đời dữ liệu, và bức tranh hệ sinh thái công cụ.

13 thg 7, 2026 5

Vì sao xử lý phân tán, mô hình Spark (RDD/DataFrame), lazy evaluation, shuffle và tối ưu.

13 thg 7, 2026 5

Đảm bảo chất lượng & minh bạch dữ liệu bằng dbt: data tests dựng sẵn (unique, not_null, relationships, accepted_values), singular test, unit test, và tài liệu tự sinh kèm lineage graph.

13 thg 7, 2026 5