Chương 20. Lưu Lịch Sử Sự Kiện: Event Streaming

Ở chương 19, ta nói về Pub/Sub:

> Một sự kiện xảy ra, nhiều bên cùng có thể nghe và phản ứng.

Chương này nói về một phiên bản mạnh hơn trong nhiều hệ thống lớn:

> Sự kiện không chỉ được phát đi, mà còn được lưu lại thành một dòng lịch sử để nhiều consumer đọc, đọc lại, phân tích và xây read model.

Đó là Event Streaming.

Ví dụ:

OrderPlaced
PaymentSucceeded
GradingCompleted
DeliveryFailed
LessonCompleted
SearchPerformed

Các event này được ghi vào một dòng log.

Consumer có thể đọc:

  • Từ hiện tại.
  • Từ một thời điểm trong quá khứ.
  • Từ đầu topic.
  • Từ offset đã đọc dở.

Điểm khác biệt lớn:

> Pub/Sub thường nhấn mạnh việc phát sự kiện cho nhiều bên nghe. Event Streaming nhấn mạnh việc sự kiện trở thành một dòng dữ liệu có lịch sử.

Công cụ nổi tiếng nhất trong nhóm này là Kafka, nhưng ý tưởng không chỉ thuộc về Kafka.

Một số hệ thống khác cũng có tinh thần streaming/log:

  • Kafka.
  • Redpanda.
  • Pulsar.
  • Kinesis.
  • Google Pub/Sub với retention.
  • NATS JetStream.
  • Redis Streams ở mức nhỏ hơn.

Chương này không phải để học cú pháp Kafka.

Mục tiêu là hiểu:

  • Event streaming giải quyết vấn đề gì.
  • Khác Queue/Pub/Sub ở đâu.
  • Khi nào đáng dùng.
  • Khi nào chưa nên dùng.
  • Vì sao replay rất mạnh nhưng cũng nguy hiểm.
  • Partition, offset, consumer lag là gì.
  • Event streaming giúp analytics, search, read model, audit, integration ra sao.

---

20.1. Ví dụ quán bánh: sổ nhật ký mọi việc đã xảy ra

Ở quán bánh, Pub/Sub giống loa thông báo:

Đơn #123 đã được đặt.
Thanh toán #456 đã thành công.
Bánh #789 đã làm xong.

Ai đang nghe thì phản ứng.

Event Streaming giống một cuốn sổ nhật ký không xóa ngay:

10:00 OrderPlaced order_123
10:01 PaymentSucceeded payment_456
10:20 KitchenTicketCompleted ticket_789
10:40 DeliveryStarted delivery_555
11:10 DeliveryFailed delivery_555
11:12 SupportCaseCreated case_999

Cuốn sổ này cho phép:

  • Bếp đọc các đơn mới từ lúc nó dừng.
  • Kế toán đọc lại payment từ hôm qua.
  • Analytics tính lại doanh thu.
  • Search rebuild dữ liệu.
  • Quản lý điều tra vì sao đơn bị trễ.
  • Một hệ thống mới bắt đầu đọc từ tháng trước.

Sự kiện không chỉ là tín hiệu tức thời.

Nó trở thành nguồn dữ liệu lịch sử.

---

20.2. Event Streaming là gì?

Event Streaming là mô hình lưu và xử lý dòng sự kiện liên tục.

Mô hình đơn giản:

Producers -> Event Stream/Log -> Consumers

Producer ghi event vào stream.

Stream lưu event theo thứ tự trong một khoảng thời gian hoặc rất lâu.

Consumer đọc event theo tốc độ của mình.

Ví dụ:

Payment Service
-> ghi PaymentSucceeded vào payment.events

Consumers:
- Ordering đọc để cập nhật order
- Accounting đọc để ghi doanh thu
- Analytics đọc để tính báo cáo
- Fraud đọc để phát hiện bất thường

Điểm quan trọng:

> Consumer không nhất thiết phải online đúng thời điểm event được ghi.

Nếu stream còn giữ event, consumer có thể đọc lại sau.

Đây là khác biệt rất lớn so với Pub/Sub tạm thời.

---

20.3. Event log là gì?

Event log là chuỗi event được append thêm ở cuối.

Nói dễ hiểu:

> Event log giống một cuốn sổ chỉ ghi thêm dòng mới, không sửa dòng cũ tùy tiện.

Ví dụ:

offset 0: OrderPlaced(order_1)
offset 1: PaymentSucceeded(order_1)
offset 2: OrderPlaced(order_2)
offset 3: GradingCompleted(job_9)
offset 4: DeliveryFailed(order_1)

Mỗi event có vị trí trong log, thường gọi là offset.

Consumer nhớ mình đã đọc đến đâu.

Ví dụ:

Analytics consumer đã đọc đến offset 4.
Notification consumer mới đọc đến offset 2.

Mỗi consumer có thể đi với tốc độ riêng.

Nếu consumer dừng, nó có thể chạy lại từ offset cũ.

---

20.4. Offset là gì?

Offset là vị trí của event trong stream hoặc partition.

Ví dụ:

offset 1001: OrderPlaced
offset 1002: PaymentSucceeded
offset 1003: GradingCompleted

Consumer đọc event và lưu lại:

Tôi đã xử lý đến offset 1003.

Nếu consumer restart, nó đọc tiếp từ offset 1004.

Offset giúp event streaming khác với kiểu message biến mất sau khi đọc.

Message không nhất thiết bị xóa ngay khi một consumer đọc xong.

Nhiều consumer có thể đọc cùng event ở những thời điểm khác nhau.

---

20.5. Event Streaming khác Queue ở đâu?

Queue thường dùng để chia công việc cho worker.

Một job thường được một worker trong nhóm xử lý.

Event Streaming là log sự kiện nhiều consumer có thể đọc độc lập.

So sánh:

| Câu hỏi | Queue | Event Streaming | |---|---|---| | Message đại diện cho gì? | Job cần làm | Event đã xảy ra | | Sau khi worker xử lý xong | Message thường biến mất với nhóm đó | Event vẫn có thể còn trong log | | Consumer mới đọc được event cũ không? | Thường không, nếu job đã xử lý | Có thể, nếu event còn retention | | Replay có tự nhiên không? | Không phải mục tiêu chính | Rất tự nhiên | | Dùng cho | Job nền, retry, concurrency | Dòng sự kiện, analytics, read model, integration | | Ví dụ | RunGradingJob | GradingCompleted |

Queue trả lời:

> Ai sẽ làm việc này?

Event Streaming trả lời:

> Những sự kiện nào đã xảy ra, và ai muốn đọc dòng lịch sử đó?

---

20.6. Event Streaming khác Pub/Sub ở đâu?

Pub/Sub và Event Streaming rất gần nhau.

Khác biệt chủ yếu nằm ở mức độ lưu lịch sử và khả năng đọc lại.

Pub/Sub:

Event xảy ra -> gửi cho subscriber

Event Streaming:

Event xảy ra -> ghi vào log -> nhiều consumer đọc theo offset

Pub/Sub bền cũng có thể lưu message một thời gian.

Event Streaming thường coi log event là trung tâm:

  • Event được lưu có thứ tự.
  • Consumer đọc theo offset.
  • Có retention.
  • Có replay.
  • Có partition.
  • Có consumer lag.
  • Có thể xây pipeline dữ liệu.

Nói ngắn:

> Pub/Sub là cái loa. Event Streaming là cái loa có kèm kho lưu lịch sử và mỗi người nghe có bookmark riêng.

---

20.7. Khi nào Event Streaming hữu ích?

Event Streaming hữu ích khi:

  • Có nhiều event liên tục.
  • Nhiều consumer cần đọc cùng một dòng event.
  • Consumer mới cần đọc lại event cũ.
  • Cần replay để rebuild read model.
  • Cần analytics gần realtime.
  • Cần audit hoặc lịch sử thay đổi.
  • Cần tích hợp nhiều hệ thống qua event log.
  • Cần xử lý dữ liệu theo dòng.

Ví dụ:

E-commerce:
- OrderPlaced
- PaymentSucceeded
- ProductViewed
- CartAbandoned
Education/AI Judge:
- SubmissionCreated
- GradingStarted
- GradingCompleted
- LessonCompleted
Finance:
- WalletDebited
- WalletCredited
- PaymentCaptured
- RefundIssued

Nếu event có giá trị lâu dài, streaming đáng cân nhắc.

---

20.8. Khi nào chưa nên dùng Event Streaming?

Chưa nên dùng event streaming nếu:

  • Hệ thống nhỏ, ít event.
  • Chỉ cần vài job nền đơn giản.
  • Chỉ có một consumer.
  • Không cần replay.
  • Team chưa vận hành được broker.
  • Chưa có schema/version/idempotency.
  • Dùng streaming chỉ vì công nghệ nghe hay.

Ví dụ:

Một website nhỏ cần gửi email sau khi user đăng ký.

Queue là đủ.

Không cần Kafka.

Một hệ thống CRUD admin đơn giản.

Không cần event streaming.

Event Streaming mạnh, nhưng kéo theo:

  • Hạ tầng.
  • Monitoring.
  • Schema management.
  • Lag.
  • Partition.
  • Replay safety.
  • Data governance.

Nếu chưa có nhu cầu thật, nó có thể làm hệ thống nặng không cần thiết.

---

20.9. Retention là gì?

Retention là thời gian hoặc dung lượng mà stream giữ event.

Ví dụ:

Giữ event 7 ngày.
Giữ event 30 ngày.
Giữ event 1 năm.
Giữ tối đa 1TB.

Sau thời gian đó, event cũ có thể bị xóa.

Retention quyết định:

  • Consumer offline bao lâu vẫn đọc lại được.
  • Có thể replay bao xa.
  • Chi phí lưu trữ.
  • Rủi ro dữ liệu cá nhân tồn tại lâu.

Ví dụ:

Analytics muốn replay dữ liệu 90 ngày.

Nhưng stream chỉ retention 7 ngày.

Không đủ.

Có thể cần:

  • Retention dài hơn.
  • Lưu event sang data lake.
  • Snapshot/read model.
  • Archive.

Retention là quyết định kiến trúc, không phải chi tiết phụ.

---

20.10. Replay là gì?

Replay là đọc lại event cũ.

Ví dụ:

Search index bị lỗi.

Ta có thể:

Xóa index cũ
Đọc lại ProductPublished/ProductUpdated từ stream
Build lại index

Analytics tính sai doanh thu.

Ta có thể:

Sửa code consumer
Đọc lại PaymentSucceeded/RefundIssued từ tháng trước
Tính lại báo cáo

Consumer mới được thêm vào.

Nó có thể:

Đọc event từ đầu tháng
Xây dữ liệu ban đầu
Sau đó đọc tiếp event mới

Replay là một trong những sức mạnh lớn nhất của event streaming.

Nhưng replay cũng rất nguy hiểm nếu handler có side effect thật.

---

20.11. Replay nguy hiểm ở đâu?

Nếu replay event vào handler gửi email, bạn có thể gửi lại email cũ cho hàng nghìn khách.

Nếu replay event vào handler gọi payment, bạn có thể tạo side effect tài chính sai.

Nếu replay event vào handler tạo notification, user có thể nhận lại thông báo cũ.

Vì vậy cần phân biệt consumer:

Consumer build read model:
  replay thường ổn.

Consumer analytics:
  replay thường ổn nếu dedup tốt.

Consumer gửi email/SMS:
  replay phải có chế độ đặc biệt hoặc không replay.

Consumer payment/refund:
  cực kỳ cẩn thận.

Một cách tốt:

  • Handler side effect thật không tự động replay.
  • Replay pipeline riêng cho read model/analytics.
  • Có flag replay_mode.
  • Có idempotency và dedup.
  • Có môi trường thử trước.

Replay mạnh như dao sắc.

Dùng đúng thì cứu hệ thống.

Dùng sai thì tạo sự cố lớn.

---

20.12. Partition là gì?

Stream lớn thường được chia thành partition.

Partition là một phần của topic/log.

Ví dụ:

Topic: payment.events
Partition 0
Partition 1
Partition 2

Event được phân vào partition dựa trên key.

Ví dụ key là payment_id hoặc order_id.

Mục đích:

  • Tăng throughput.
  • Cho nhiều consumer xử lý song song.
  • Giữ thứ tự trong phạm vi một key nếu thiết kế đúng.

Điểm quan trọng:

> Thứ tự thường chỉ được đảm bảo trong một partition, không phải toàn bộ topic.

Nếu event của cùng một order cần đúng thứ tự, hãy dùng order_id làm key để chúng vào cùng partition.

---

20.13. Key là gì?

Key là giá trị dùng để quyết định event đi vào partition nào.

Ví dụ:

OrderPlaced(order_id=order_123) key = order_123
OrderPaid(order_id=order_123) key = order_123
OrderCancelled(order_id=order_123) key = order_123

Nếu cùng key vào cùng partition, consumer đọc được đúng thứ tự cho order đó.

Nếu key lung tung, event có thể vào nhiều partition khác nhau.

Khi đó thứ tự cho cùng một aggregate không còn chắc.

Chọn key rất quan trọng.

Ví dụ:

  • Order event: key = order_id.
  • Payment event: key = payment_id hoặc order_id tùy consumer cần gì.
  • User activity: key = user_id.
  • Wallet event: key = wallet_id.
  • Grading event: key = grading_job_id hoặc student_id tùy mục tiêu.

Key ảnh hưởng đến ordering, load balancing, và hotspot.

---

20.14. Hot partition là gì?

Hot partition xảy ra khi quá nhiều event dồn vào một partition.

Ví dụ key là tenant_id.

Một tenant rất lớn tạo 80% event.

Tất cả event của tenant đó vào một partition.

Partition đó quá tải, trong khi partition khác rảnh.

Cách giảm:

  • Chọn key phân bố đều hơn.
  • Thêm partition nếu phù hợp.
  • Shard key cho tenant lớn.
  • Tách topic riêng cho tenant rất lớn.
  • Thiết kế consumer chịu được load.

Chọn key là trade-off:

  • Key theo aggregate giúp ordering tốt.
  • Key phân bố đều giúp throughput tốt.

Không có đáp án chung.

Phải biết mục tiêu của stream.

---

20.15. Consumer group trong Event Streaming

Consumer group là nhóm consumer cùng đọc một topic.

Trong cùng group, mỗi partition thường được giao cho một consumer tại một thời điểm.

Ví dụ:

Topic payment.events có 6 partition.
Accounting consumer group có 3 instances.
Mỗi instance xử lý khoảng 2 partition.

Một event được xử lý một lần trong mỗi group.

Nhưng nhiều group khác nhau đều có thể đọc cùng event:

payment.events
-> accounting-group
-> notification-group
-> analytics-group

Mỗi group có offset riêng.

Analytics có thể chậm hơn Notification.

Accounting có thể dừng rồi đọc tiếp.

Đây là mô hình rất mạnh cho nhiều consumer độc lập.

---

20.16. Consumer lag là gì?

Consumer lag là khoảng cách giữa event mới nhất trong stream và event consumer đã xử lý.

Ví dụ:

Latest offset: 1,000,000
Accounting offset: 999,500
Lag = 500 events

Hoặc tính theo thời gian:

Event mới nhất lúc 10:00
Consumer đang xử lý event lúc 09:50
Lag = 10 phút

Lag cho biết consumer có theo kịp không.

Lag cao có thể do:

  • Consumer ít instance.
  • Xử lý mỗi event chậm.
  • Downstream database chậm.
  • Lỗi retry nhiều.
  • Partition bị hot.
  • Broker quá tải.

Mỗi consumer có mức lag chấp nhận khác nhau.

Analytics lag 10 phút có thể ổn.

Accounting lag 10 phút có thể đáng lo.

Realtime notification lag 10 phút là rất tệ.

---

20.17. Event Streaming và analytics

Analytics là một trong những use case mạnh nhất của event streaming.

Ví dụ hệ thống học online phát:

LessonStarted
LessonCompleted
QuizSubmitted
GradingCompleted
SearchPerformed
CoursePurchased

Analytics consumer đọc stream và tính:

  • Tỷ lệ hoàn thành bài học.
  • Thời gian chấm trung bình.
  • Chi phí AI mỗi lớp.
  • Tỷ lệ học viên bỏ giữa chừng.
  • Bài nào khó nhất.
  • Lúc nào hệ thống tải cao.

Điểm hay:

> Hệ thống nghiệp vụ không cần gọi trực tiếp analytics trong từng request.

Nó chỉ phát event.

Analytics đọc dòng sự kiện.

Nếu cách tính sai, có thể sửa code và replay.

---

20.18. Event Streaming và read model

Read model là mô hình dữ liệu phục vụ đọc nhanh.

Ví dụ admin muốn xem một màn hình tổng hợp:

Order
Payment
Delivery
Support
Refund

Nếu mỗi lần mở màn hình phải gọi 5 service, có thể chậm.

Thay vào đó, ta có thể xây read model từ event:

OrderPlaced
PaymentSucceeded
DeliveryStarted
DeliveryFailed
RefundIssued
SupportCaseCreated

Consumer đọc các event này và cập nhật:

order_admin_view

Frontend chỉ cần:

GET /admin/orders/{id}

Ưu điểm:

  • Đọc nhanh.
  • Ít gọi dây chuyền.
  • Màn hình phù hợp nhu cầu.

Nhược điểm:

  • Dữ liệu có thể trễ.
  • Cần xử lý event trùng.
  • Cần rebuild nếu read model lỗi.

Event streaming rất hợp để xây read model vì có thể replay.

---

20.19. Event Streaming và search index

Search index cũng là read model.

Ví dụ Catalog phát:

ProductCreated
ProductPublished
ProductUpdated
ProductDeleted

Search consumer đọc event và cập nhật index.

Nếu index lỗi hoặc muốn đổi mapping, có thể:

Tạo index mới
Replay product events
Switch alias sang index mới

Hoặc rebuild từ database nếu stream không đủ lịch sử.

Event streaming giúp search index theo kịp thay đổi gần realtime.

Nhưng search index không nên là nguồn sự thật chính.

Nguồn sự thật vẫn là Catalog.

---

20.20. Event Streaming và data lake

Nhiều công ty đưa event stream vào data lake hoặc data warehouse.

Ví dụ:

Application events -> Kafka -> Data lake -> BI/ML

Hoặc:

Payment events -> Stream -> Warehouse -> Finance report

Điều này giúp:

  • Phân tích lịch sử.
  • Làm dashboard.
  • Train model recommendation.
  • Phát hiện gian lận.
  • Tính chi phí.
  • Audit.

Nhưng cần chú ý:

  • Dữ liệu nhạy cảm.
  • Schema evolution.
  • Retention.
  • Quyền truy cập.
  • Chất lượng dữ liệu.
  • Dedup.

Event streaming không tự biến dữ liệu thành đúng.

Nó chỉ vận chuyển dòng dữ liệu tốt hơn.

---

20.21. Event Streaming và audit

Event stream có thể hỗ trợ audit, nhưng không phải lúc nào cũng thay audit log.

Domain event:

OrderCancelled
RefundIssued
UserRoleChanged

Audit log cần thêm:

  • Ai làm.
  • Làm từ IP nào.
  • Trước đó giá trị là gì.
  • Sau đó giá trị là gì.
  • Lý do.
  • Request id.

Event stream có thể là nguồn tốt để audit một phần.

Nhưng nếu yêu cầu audit nghiêm ngặt, cần thiết kế audit riêng:

  • Không cho sửa.
  • Retention dài.
  • Truy cập hạn chế.
  • Có đầy đủ actor/context.

Đừng mặc định rằng có Kafka là có audit đầy đủ.

---

20.22. Event Streaming và Event Sourcing

Event Streaming và Event Sourcing khác nhau.

Event Streaming:

> Event được lưu thành stream để nhiều consumer đọc.

Event Sourcing:

> Trạng thái chính của aggregate được dựng từ chuỗi event.

Ví dụ event streaming bình thường:

orders table lưu trạng thái hiện tại
ordering.events lưu OrderPlaced/OrderCancelled cho consumer

Ví dụ event sourcing:

Order state = replay:
- OrderCreated
- ItemAdded
- OrderConfirmed
- PaymentRecorded
- OrderDelivered

Không cần Event Sourcing để dùng Event Streaming.

Phần lớn hệ thống thực dụng có thể:

  • Lưu trạng thái hiện tại trong database.
  • Phát event ra stream.
  • Dùng stream cho analytics/read model/integration.

Event Sourcing mạnh nhưng phức tạp hơn nhiều.

Đừng nhảy vào nếu chưa có lý do rõ.

---

20.23. Event Streaming và CDC

CDC là Change Data Capture.

Nó lấy thay đổi từ database và biến thành dòng event/change.

Ví dụ:

orders table insert/update
-> CDC tool đọc binlog/WAL
-> publish change event

Công cụ thường gặp:

  • Debezium.
  • Database replication log.
  • Cloud CDC services.

CDC hữu ích khi:

  • Muốn stream thay đổi database mà không sửa nhiều app code.
  • Muốn đồng bộ sang data warehouse/search.
  • Muốn tách hệ thống cũ.

Nhưng CDC event thường là data event kỹ thuật:

orders row updated

Không giống domain event:

OrderCancelled

orders.status đổi không nói rõ vì sao.

CDC tốt cho data pipeline.

Domain event tốt cho nghiệp vụ.

Có thể dùng cả hai, nhưng đừng nhầm ý nghĩa.

---

20.24. Event Streaming và schema

Trong streaming, schema cực kỳ quan trọng.

Vì event có thể được lưu lâu và đọc bởi nhiều consumer.

Nếu producer đổi field bừa, consumer có thể vỡ.

Ví dụ:

amount: 350000

Ban đầu là VND.

Sau đổi thành cents hoặc decimal string mà không version.

Analytics và Accounting tính sai.

Cần:

  • Schema rõ.
  • Version.
  • Compatibility rule.
  • Producer validation.
  • Consumer tolerance.
  • Event catalog.
  • Có thể dùng JSON Schema, Avro, Protobuf.

Với streaming, lỗi schema có thể lan rất rộng vì event đi vào log và ở đó lâu.

---

20.25. Schema Registry

Schema Registry là nơi lưu và kiểm tra schema event.

Nó giúp:

  • Producer publish đúng schema.
  • Consumer biết schema để đọc.
  • Kiểm tra thay đổi có compatible không.
  • Quản lý version.

Ví dụ:

OrderPlaced v1
OrderPlaced v2
PaymentSucceeded v1
GradingCompleted v3

Không phải hệ thống nào cũng cần schema registry ngay.

Nhưng khi:

  • Nhiều team.
  • Nhiều topic.
  • Nhiều consumer.
  • Event retention dài.
  • Event dùng cho data platform.

thì schema registry hoặc ít nhất event catalog có kiểm soát là rất đáng giá.

---

20.26. Exactly once có thật không?

Nhiều công cụ nói về exactly-once semantics.

Nhưng trong thiết kế ứng dụng, nên rất thận trọng.

Thực tế thường phải giả định:

> Event có thể được xử lý hơn một lần.

Vì:

  • Consumer xử lý xong nhưng commit offset thất bại.
  • Producer retry publish.
  • Transaction giữa app và broker phức tạp.
  • Replay cố ý.
  • Downstream side effect không exactly-once.

Vì vậy vẫn cần:

  • Idempotency.
  • Dedup bằng event_id.
  • Unique constraint.
  • State check.
  • Transaction cục bộ rõ.

Đừng dựa hoàn toàn vào lời hứa exactly-once của broker để bỏ qua idempotency nghiệp vụ.

---

20.27. At-least-once, at-most-once, exactly-once

Ba khái niệm này hay gặp.

At-most-once

Event được xử lý tối đa một lần.

Có thể mất.

Không xử lý trùng, nhưng có thể bỏ lỡ.

At-least-once

Event được xử lý ít nhất một lần.

Có thể trùng.

Không dễ mất, nhưng phải chịu duplicate.

Exactly-once

Lý tưởng là xử lý đúng một lần.

Trong thực tế, thường chỉ đúng trong phạm vi hẹp của một hệ thống/công cụ.

Với nghiệp vụ thật, vẫn nên thiết kế idempotent.

Phần lớn event-driven system thực dụng chọn:

At-least-once + idempotent consumer

Đây là cách suy nghĩ an toàn.

---

20.28. Idempotent consumer

Idempotent consumer là consumer xử lý event trùng mà không tạo lỗi.

Ví dụ:

PaymentSucceeded(event_id=evt_1, payment_id=pay_1)

Accounting nhận hai lần.

Không được ghi doanh thu hai lần.

Cách làm:

processed_events
- consumer_name
- event_id

Hoặc unique constraint:

unique(revenue_record.payment_id)

Hoặc state check:

if payment already recognized:
  skip

Tùy nghiệp vụ, chọn cách phù hợp.

Điểm chính:

> Consumer phải tự bảo vệ mình trước duplicate.

---

20.29. Ordering trong Event Streaming

Event streaming thường đảm bảo thứ tự trong một partition.

Không đảm bảo thứ tự toàn hệ thống.

Ví dụ:

Partition 0:
  OrderPlaced(order_1)
  OrderPaid(order_1)

Partition 1:
  OrderPlaced(order_2)
  OrderPaid(order_2)

Không nên hỏi event nào toàn hệ thống xảy ra trước nếu chúng ở partition khác nhau, trừ khi có timestamp và logic riêng.

Với cùng aggregate, nên dùng cùng key để event vào cùng partition.

Ví dụ:

key = order_id

Như vậy event của cùng order giữ thứ tự.

Nhưng nếu một consumer cần join event từ nhiều stream, thứ tự trở nên phức tạp hơn.

Lúc đó cần:

  • Timestamp.
  • Watermark.
  • Window.
  • State store.
  • Logic xử lý event đến muộn.

Đó là thế giới stream processing nâng cao.

---

20.30. Stream processing là gì?

Stream processing là xử lý dữ liệu khi nó đang chảy qua stream.

Ví dụ:

PaymentSucceeded events
-> tính doanh thu theo phút
-> phát hiện spike bất thường
-> cập nhật dashboard realtime

Hoặc:

UserActivity events
-> tính top sản phẩm đang hot
-> cập nhật recommendation

Stream processing có thể dùng:

  • Kafka Streams.
  • Flink.
  • Spark Streaming.
  • Beam.
  • KSQL.
  • Consumer tự viết đơn giản.

Không cần công cụ lớn cho mọi thứ.

Nếu chỉ cần đọc event và update database, consumer thường là đủ.

Nếu cần window, join, aggregation lớn, stream processing framework có thể cần thiết.

---

20.31. Window là gì?

Window là khung thời gian để tính toán trên stream.

Ví dụ:

Số đơn hàng mỗi 5 phút.
Doanh thu mỗi 1 giờ.
Số bài chấm AI mỗi 10 phút.
Tỷ lệ lỗi payment mỗi 1 phút.

Vì event đến liên tục, ta phải gom theo cửa sổ thời gian.

Ví dụ:

10:00 - 10:05: 120 orders
10:05 - 10:10: 150 orders

Window quan trọng trong analytics realtime, monitoring, fraud detection.

Nhưng với hệ thống nghiệp vụ thông thường, có thể chưa cần học sâu ngay.

Chỉ cần biết event streaming mở ra khả năng này.

---

20.32. Late event là gì?

Late event là event đến muộn hơn dự kiến.

Ví dụ:

Payment xảy ra lúc 10:00 nhưng do network/broker lag, analytics nhận lúc 10:10.

Nếu analytics đã đóng window 10:00-10:05, event này tính vào đâu?

Đây là bài toán stream processing thật.

Cách xử lý:

  • Dùng event time thay vì processing time.
  • Cho phép lateness.
  • Recalculate window.
  • Có correction event.
  • Chấp nhận sai số tạm thời.

Không phải hệ thống nào cũng cần xử lý late event phức tạp.

Nhưng nếu làm realtime analytics, phải biết nó tồn tại.

---

20.33. Event time và processing time

Event time:

> Thời điểm sự kiện thật sự xảy ra.

Processing time:

> Thời điểm consumer xử lý event.

Ví dụ:

Payment xảy ra lúc 10:00
Consumer xử lý lúc 10:03

Event time = 10:00.

Processing time = 10:03.

Báo cáo doanh thu thường nên dùng event time.

Monitoring consumer lag dùng processing time và thời điểm xử lý.

Nếu không phân biệt hai thứ này, số liệu dễ sai.

Event nên có field:

occurred_at

Không chỉ dựa vào thời điểm message được nhận.

---

20.34. Compaction là gì?

Một số stream hỗ trợ log compaction.

Ý tưởng:

> Giữ lại bản mới nhất cho mỗi key, thay vì giữ mọi event mãi.

Ví dụ topic user-profile:

key=user_1 value=name A
key=user_1 value=name B
key=user_1 value=name C

Sau compaction, có thể chỉ còn bản mới nhất:

key=user_1 value=name C

Compaction hữu ích cho:

  • State snapshot.
  • Cache rebuild.
  • Bảng dữ liệu dạng key-value.

Nhưng nó không phù hợp nếu bạn cần lịch sử đầy đủ.

Ví dụ ledger tài chính không nên compact mất event cũ nếu cần audit.

Retention và compaction phải phù hợp mục tiêu dữ liệu.

---

20.35. Snapshot

Nếu replay từ đầu quá lâu, có thể dùng snapshot.

Ví dụ:

Read model đã xử lý 1 tỷ event.

Khi rebuild, đọc lại từ đầu mất nhiều giờ.

Cách làm:

Lưu snapshot state tại offset X.
Khi restart, load snapshot.
Đọc tiếp event từ offset X+1.

Snapshot giúp:

  • Khởi động nhanh hơn.
  • Rebuild nhanh hơn.
  • Giảm chi phí replay.

Nhưng snapshot cũng cần quản lý:

  • Snapshot tương ứng schema nào?
  • Snapshot ở offset nào?
  • Nếu snapshot lỗi thì sao?
  • Có thể rebuild từ event gốc không?

Snapshot là tối ưu, không thay thế event log đúng.

---

20.36. Event Streaming và dữ liệu nhạy cảm

Event stream có thể giữ dữ liệu lâu.

Vì vậy dữ liệu nhạy cảm trong event rất nguy hiểm.

Không nên đưa vào stream nếu không cần:

  • Password.
  • Token.
  • Secret.
  • Full card number.
  • Dữ liệu cá nhân quá chi tiết.
  • Nội dung riêng tư lớn.

Nếu bắt buộc có dữ liệu nhạy cảm:

  • Mã hóa.
  • Giới hạn quyền đọc topic.
  • Mask/redact.
  • Retention ngắn.
  • Tách topic bảo mật cao.
  • Có policy xóa/anonymize.

Event log không giống biến tạm trong memory.

Một khi dữ liệu vào stream và được nhiều consumer đọc, rất khó thu hồi.

---

20.37. Quyền được quên và event log

Nếu hệ thống phải tuân thủ yêu cầu xóa dữ liệu cá nhân, event log là vấn đề cần nghĩ sớm.

Ví dụ event chứa:

customer_email
phone
delivery_address

Sau này user yêu cầu xóa dữ liệu.

Nếu event retention dài hoặc archive sang data lake, xử lý không đơn giản.

Cách giảm rủi ro:

  • Event chỉ chứa id, không chứa PII nếu không cần.
  • Lưu PII ở service sở hữu, có thể xóa/anonymize.
  • Dùng tokenization.
  • Có retention policy.
  • Có công cụ redact event/archive nếu luật yêu cầu.

Thiết kế event schema phải nghĩ đến privacy, không chỉ kỹ thuật.

---

20.38. Event Streaming và cost

Event streaming có chi phí:

  • Broker cluster.
  • Storage retention.
  • Network.
  • Consumer compute.
  • Monitoring.
  • Data transfer sang warehouse.
  • Nhân sự vận hành.

Nếu hệ thống nhỏ, chi phí này có thể lớn hơn lợi ích.

Managed service giảm vận hành nhưng vẫn có cost.

Kafka tự quản mạnh nhưng không hề nhẹ.

Một quyết định thực dụng:

> Đừng đưa Kafka vào chỉ vì "công ty lớn dùng". Hãy đưa vào khi dòng event, replay, nhiều consumer, analytics hoặc scale thật sự cần.

---

20.39. Kafka là gì trong bức tranh này?

Kafka là một nền tảng event streaming phổ biến.

Nó cung cấp:

  • Topic.
  • Partition.
  • Durable log.
  • Consumer group.
  • Offset.
  • Retention.
  • Replay.
  • Throughput cao.

Kafka rất mạnh cho:

  • Event log lớn.
  • Data pipeline.
  • Analytics realtime.
  • Nhiều consumer độc lập.
  • Stream processing.

Nhưng Kafka cũng đòi hỏi:

  • Hiểu partition/key.
  • Monitoring lag.
  • Schema management.
  • Broker operations.
  • Capacity planning.
  • Security.

Kafka không phải queue đơn giản cho mọi job nền.

Nếu chỉ cần chạy vài job background, Celery/RQ/SQS/Cloud Tasks có thể phù hợp hơn.

---

20.40. Redis Streams, Pub/Sub, Kafka: phân biệt nhanh

| Công cụ/kiểu | Điểm mạnh | Điểm cần cẩn thận | |---|---|---| | Redis Pub/Sub | Realtime signal rất đơn giản | Không bền cho subscriber offline | | Redis Streams | Stream nhẹ, consumer group, lưu message | Vận hành và scale khác Kafka, cần hiểu pending/ack | | RabbitMQ | Queue/routing tốt | Không phải event log dài hạn kiểu Kafka | | Kafka/Redpanda | Durable log, replay, throughput cao | Phức tạp hơn, cần schema/ops tốt | | SQS/SNS | Managed queue/pub-sub thực dụng | Replay lịch sử không giống Kafka | | Google Pub/Sub | Managed pub-sub, retention nhất định | Cần hiểu ack, retry, ordering key |

Không có công cụ thắng mọi trường hợp.

Chọn theo nhu cầu:

  • Job nền: queue.
  • Realtime signal: pub/sub tạm thời.
  • Event quan trọng nhiều consumer: durable pub/sub.
  • Event log/replay/analytics lớn: event streaming.

---

20.41. AI Judge với Event Streaming

AI Judge có thể bắt đầu bằng queue là đủ.

Nhưng khi hệ thống lớn hơn, event streaming có thể hữu ích.

Các event:

SubmissionCreated
GradingJobQueued
GradingStarted
GradingCompleted
GradingFailed
FeedbackViewed
LessonCompleted

Stream này có thể phục vụ:

  • Analytics: thời gian chấm trung bình, tỷ lệ lỗi, chi phí AI.
  • Learning: cập nhật tiến độ.
  • Notification: báo kết quả.
  • Product: xem học viên dùng feedback ra sao.
  • Quality: phát hiện rubric nào gây lỗi nhiều.
  • Finance: tính cost theo lớp/trường.

Ví dụ:

GradingCompleted
- grading_job_id
- submission_id
- student_id
- assignment_id
- score
- max_score
- model_name
- token_usage
- cost_estimate
- duration_ms
- occurred_at

Nếu sau này muốn tính lại chi phí theo công thức mới, có thể replay stream vào analytics.

Nhưng nếu chỉ có vài trăm bài/ngày, chưa chắc cần Kafka.

Có thể bắt đầu bằng:

  • Database job table.
  • Queue worker.
  • Outbox events.
  • Analytics table đơn giản.

Khi event volume và nhu cầu replay tăng, mới nâng cấp.

---

20.42. Event Streaming không thay thế database

Một hiểu nhầm:

> Có event stream rồi thì không cần database.

Không đúng trong đa số hệ thống thực dụng.

Database vẫn là nơi tốt để:

  • Query trạng thái hiện tại.
  • Transaction.
  • Constraint.
  • Index theo nghiệp vụ.
  • CRUD admin.
  • Lock/concurrency.

Event stream tốt để:

  • Phát lịch sử thay đổi.
  • Cho nhiều consumer đọc.
  • Replay.
  • Analytics.
  • Read model.
  • Integration.

Ví dụ:

GradingJob table:
  trạng thái hiện tại của job

grading.events stream:
  lịch sử SubmissionCreated/GradingCompleted/GradingFailed

Hai thứ bổ sung nhau.

Không cần cực đoan.

---

20.43. Event Streaming không thay thế Queue

Kafka có thể dùng như queue trong vài trường hợp, nhưng về tư duy, queue và streaming vẫn khác.

Nếu bạn cần:

Chạy một job nền và retry nếu lỗi

queue truyền thống thường dễ hơn.

Nếu bạn cần:

Lưu dòng event để nhiều consumer đọc/replay

event streaming phù hợp hơn.

Ví dụ AI Judge:

RunGradingJob(job_id)

đây là job.

Queue phù hợp.

GradingCompleted

đây là event.

Streaming/PubSub phù hợp nếu nhiều consumer cần đọc.

Đừng ép mọi thứ vào một công cụ chỉ vì công cụ đó mạnh.

---

20.44. Event Streaming không thay thế thiết kế domain event

Nếu event đặt tên kém:

DataUpdated
StatusChanged
TableRowChanged

thì dùng Kafka cũng không làm hệ thống dễ hiểu hơn.

Event streaming khuếch đại cả điều tốt lẫn điều xấu.

Event tốt:

PaymentSucceeded
RefundIssued
GradingCompleted
OrderCancelled

Consumer hiểu ý nghĩa.

Event xấu:

ObjectModified

Consumer phải đoán.

Trước khi nghĩ đến streaming platform, hãy thiết kế event catalog rõ.

---

20.45. Những lỗi phổ biến khi dùng Event Streaming

Lỗi 1: Dùng Kafka cho mọi thứ

Job nhỏ, luồng đơn giản, hệ thống chưa cần replay nhưng vẫn đưa Kafka vào.

Lỗi 2: Không hiểu partition/key

Event cần thứ tự lại đi vào nhiều partition khác nhau.

Lỗi 3: Không theo dõi consumer lag

Event vẫn ghi đều, nhưng consumer chậm hàng giờ không ai biết.

Lỗi 4: Consumer không idempotent

Replay hoặc duplicate làm ghi dữ liệu trùng.

Lỗi 5: Replay side effect thật

Gửi lại email cũ, gọi lại API ngoài, tạo lại notification không mong muốn.

Lỗi 6: Không quản lý schema

Producer đổi payload làm consumer vỡ.

Lỗi 7: Đưa PII vào stream quá rộng

Dữ liệu cá nhân lan sang nhiều consumer/data lake.

Lỗi 8: Nghĩ stream là nguồn sự thật nhưng không thiết kế như nguồn sự thật

Nếu muốn event sourcing, phải thiết kế rất khác.

Lỗi 9: Retention quá ngắn so với nhu cầu replay

Khi cần rebuild thì event đã bị xóa.

Lỗi 10: Không có event catalog

Không ai biết event nào quan trọng, ai phát, ai đọc.

---

20.46. Checklist thiết kế Event Streaming

Trước khi dùng event streaming, hãy hỏi:

  • Stream này chứa loại event gì?
  • Event có ý nghĩa nghiệp vụ không?
  • Producer là ai?
  • Consumer hiện tại là ai?
  • Consumer tương lai có thể là ai?
  • Có cần replay không?
  • Cần replay bao xa?
  • Retention bao lâu?
  • Topic được chia thế nào?
  • Key là gì?
  • Có cần ordering theo aggregate không?
  • Có nguy cơ hot partition không?
  • Schema/version quản lý ra sao?
  • Event có chứa dữ liệu nhạy cảm không?
  • Consumer có idempotent không?
  • Replay có gây side effect không?
  • Consumer lag đo thế nào?
  • DLQ hoặc error handling ra sao?
  • Có cần archive sang data lake không?
  • Có event catalog không?
  • Cost và vận hành có xứng đáng không?

Nếu câu trả lời còn mơ hồ, bắt đầu bằng queue/outbox/PubSub đơn giản hơn có thể tốt hơn.

---

20.47. Bảng chọn nhanh

| Tình huống | Event Streaming có phù hợp không? | |---|---| | Gửi một email sau đăng ký | Thường không cần | | Chạy job AI grading | Queue phù hợp hơn | | Phát GradingCompleted cho nhiều bên | Pub/Sub/stream phù hợp | | Tính analytics từ hàng triệu event | Rất phù hợp | | Rebuild search index từ lịch sử event | Phù hợp nếu retention đủ | | Cần consumer mới đọc lại event cũ | Phù hợp | | CRUD admin nhỏ | Không cần | | Payment events cho accounting/fraud/analytics | Phù hợp nhưng phải thiết kế chặt | | Realtime typing indicator | Pub/Sub tạm thời phù hợp hơn | | Data pipeline sang warehouse | Rất phù hợp |

---

20.48. Tóm tắt bằng một luồng

Luồng AI Judge có thể tiến hóa như sau.

Giai đoạn đầu:

SubmissionCreated
-> tạo GradingJob
-> Queue RunGradingJob
-> lưu kết quả

Giai đoạn có nhiều consumer:

GradingCompleted
-> Pub/Sub cho Learning, Notification, Analytics

Giai đoạn cần lịch sử và phân tích:

grading.events stream
-> analytics consumer
-> learning progress read model
-> notification consumer
-> cost dashboard
-> quality monitoring

Khi đó event không chỉ là tín hiệu.

Nó là dòng dữ liệu lịch sử của hệ thống học tập.

Nhưng vẫn cần nhớ:

  • Job chấm bài vẫn nên là queue.
  • Trạng thái hiện tại vẫn nên có database.
  • Event quan trọng vẫn cần outbox.
  • Consumer vẫn phải idempotent.
  • Schema và privacy phải nghiêm túc.

---

20.49. Kết luận của chương

Event Streaming là cách lưu và xử lý dòng sự kiện có lịch sử.

Nó mạnh hơn Pub/Sub đơn giản ở chỗ:

  • Event được lưu trong log.
  • Consumer đọc theo offset.
  • Consumer mới có thể đọc lại event cũ.
  • Có thể replay.
  • Có thể xây read model, analytics, search index.
  • Có thể làm nền cho data pipeline và stream processing.

Nhưng nó cũng phức tạp hơn:

  • Phải hiểu retention.
  • Phải chọn partition key.
  • Phải đo consumer lag.
  • Phải quản lý schema.
  • Phải xử lý duplicate.
  • Phải cẩn thận với replay.
  • Phải kiểm soát dữ liệu nhạy cảm.
  • Phải tính cost và vận hành.

Thông điệp quan trọng nhất:

> Event Streaming không phải là queue phiên bản xịn hơn. Nó là cách biến sự kiện thành dòng lịch sử có thể đọc lại, xử lý lại và dùng cho nhiều mục đích dữ liệu khác nhau.

Ở chương tiếp theo, ta sẽ nói về Webhook, Polling, SSE và WebSocket: bốn cách rất hay gặp khi hệ thống cần cập nhật trạng thái hoặc giao tiếp với client/service theo thời gian.