Chương 65. Realtime State Synchronization
Ở chương trước, ta nói về tiền, ledger và reconciliation.
Đó là vùng hệ thống cần đúng tuyệt đối và có lịch sử rõ.
Chương này chuyển sang một kiểu bài toán khác:
Nhiều người, nhiều thiết bị, nhiều service cùng nhìn hoặc sửa một trạng thái.
Ví dụ:
Chat.
Typing indicator.
Presence.
Delivery receipt.
Dashboard realtime.
Collaborative editing.
Live grading status.
Online/offline sync.
Nghe đơn giản:
Có thay đổi thì đẩy xuống client.
Nhưng production sẽ hỏi:
Nếu user offline thì sao?
Nếu message đến trùng thì sao?
Nếu event đến sai thứ tự thì sao?
Nếu hai người sửa cùng lúc thì sao?
Nếu một client reconnect sau 10 phút thì lấy lại từ đâu?
Nếu gửi realtime thất bại thì dữ liệu có mất không?
Nếu chỉ cần cập nhật mỗi 30 giây thì có cần realtime không?
Thông điệp chính của chương:
> Realtime không chỉ là WebSocket. Realtime là bài toán đồng bộ trạng thái giữa nhiều bên trong điều kiện mạng không hoàn hảo, event có thể trùng, lệch thứ tự, đến muộn, và client có thể offline bất cứ lúc nào.
---
65.1. Một tình huống: lớp học đang nộp bài
Hãy quay lại AI Judge.
Giáo viên mở dashboard của một assignment.
Họ muốn thấy:
Ai đã nộp.
Ai đang được chấm.
Ai đã có điểm.
Ai bị lỗi cần review.
Queue còn bao nhiêu bài.
Học sinh nào vừa submit.
Nếu giáo viên phải refresh liên tục, trải nghiệm rất kém.
Ta muốn dashboard tự cập nhật.
Luồng có thể là:
Học sinh submit bài.
Submission Service lưu bài.
Grading Worker bắt đầu chấm.
Kết quả chấm xong.
Dashboard giáo viên cập nhật realtime.
Nhìn thì đơn giản.
Nhưng thực tế:
Giáo viên có thể mở nhiều tab.
Mạng có thể rớt.
Worker event có thể đến muộn.
Client có thể nhận event duplicate.
Dashboard có thể load từ snapshot cũ rồi nhận event mới.
Nếu không thiết kế đúng, dashboard có thể hiện:
Bài đã graded rồi lại nhảy về grading.
Số bài đã nộp bị đếm double.
User offline quay lại thấy thiếu vài bài.
Một tab thấy trạng thái khác tab kia.
Đây là bài toán realtime state synchronization.
---
65.2. Trạng thái chung là gì?
Trạng thái chung là dữ liệu mà nhiều bên cùng quan tâm.
Ví dụ trong chat:
Tin nhắn nào đã gửi.
Ai đang online.
Ai đang gõ.
Ai đã đọc.
Ví dụ trong AI Judge:
Submission status.
Grading result.
Queue progress.
Teacher review state.
Assignment dashboard counts.
Rubric đang được chỉnh.
Khi chỉ một người dùng, một thiết bị, một request, mọi thứ dễ hơn.
Khi nhiều bên cùng xem/sửa, ta phải trả lời:
Nguồn sự thật là gì?
Client có được tự đoán trạng thái không?
Server gửi event gì?
Event có lưu lại không?
Client mất kết nối thì catch up thế nào?
Nếu hai thay đổi xung đột thì ai thắng?
Realtime state không phải chỉ là dữ liệu mới nhất.
Nó là quá trình đưa nhiều bản sao trạng thái về gần nhau nhất có thể.
---
65.3. Realtime khác polling thế nào?
Polling là client hỏi server định kỳ:
Mỗi 10 giây hỏi: có gì mới không?
Realtime push là server chủ động đẩy sự kiện xuống client:
Có message mới.
Server gửi ngay cho client đang kết nối.
Polling đơn giản hơn.
Realtime phản ứng nhanh hơn.
Nhưng realtime phức tạp hơn vì cần:
Connection dài.
Reconnect.
Fan-out.
Ordering.
Backpressure.
Presence.
Delivery guarantee.
Catch-up khi offline.
Không nên mặc định:
Cứ realtime là tốt hơn.
Nếu dashboard chỉ cần cập nhật mỗi 30 giây, polling có thể đủ tốt.
Nếu chat, typing, live collaboration, hoặc đấu giá realtime, polling có thể không đủ.
Chọn realtime là chọn thêm complexity.
Nên có lý do rõ.
---
65.4. Các công nghệ realtime thường gặp
Một số cách phổ biến:
Polling.
Long polling.
Server-Sent Events.
WebSocket.
WebRTC data channel.
Push notification.
Message broker phía backend.
Polling:
Dễ làm, hợp với dữ liệu không cần ngay lập tức.
Long polling:
Client hỏi, server giữ request đến khi có dữ liệu hoặc timeout.
Server-Sent Events, thường gọi SSE:
Server đẩy event một chiều xuống browser.
Hợp với feed/update một chiều.
WebSocket:
Kết nối hai chiều, hợp với chat, collaboration, realtime control.
Push notification:
Đánh thức user/thiết bị khi app không mở.
Không thay thế state sync đầy đủ.
Công nghệ chỉ là phương tiện.
Câu hỏi chính vẫn là:
Trạng thái nào cần đồng bộ?
Độ trễ chấp nhận là bao nhiêu?
Client offline thì sao?
Event có cần lưu không?
---
65.5. Presence là gì?
Presence là trạng thái ai đang online, active, idle, offline.
Ví dụ:
Giáo viên A đang online.
Học sinh B đang xem assignment.
Support agent đang ở trong tenant này.
Presence nghe đơn giản nhưng khó hơn tưởng tượng.
Vì online không phải trạng thái tuyệt đối.
Ví dụ:
Client mất mạng.
Browser tab bị sleep.
Mobile app chạy nền.
WebSocket chưa đóng ngay.
User mở nhiều thiết bị.
Server chưa nhận disconnect.
Do đó presence thường là trạng thái gần đúng.
Ta có thể dùng:
Heartbeat.
Last seen timestamp.
Connection count.
TTL.
Ví dụ:
Nếu client không heartbeat 30 giây, xem như offline.
Presence không nên dùng cho quyết định quan trọng kiểu:
User offline nên tự động hủy bài thi.
Vì presence có thể sai tạm thời.
Nó hợp hơn cho trải nghiệm:
Ai đang xem.
Ai đang hoạt động.
Ai vừa online.
---
65.6. Typing indicator
Typing indicator là:
User đang gõ...
Nó là trạng thái realtime nhưng không cần bền vững.
Nếu typing event mất, không sao.
Nếu đến muộn, có thể bỏ.
Nếu user offline, indicator tự hết hạn.
Typing thường dùng TTL:
User bắt đầu gõ -> gửi typing_started.
Nếu sau 5 giây không có update, tự tắt.
User gửi message -> tắt typing.
Điểm quan trọng:
Không phải mọi realtime event đều cần lưu database.
Typing là ephemeral state.
Tin nhắn chat là persistent state.
Submission result là persistent state.
Phân biệt ephemeral và persistent giúp kiến trúc gọn hơn.
Đừng lưu mọi typing event vào database dài hạn.
Nhưng cũng đừng coi message quan trọng như typing event có mất cũng được.
---
65.7. Delivery receipt và read receipt
Delivery receipt là xác nhận tin nhắn/event đã được giao đến thiết bị hoặc client.
Read receipt là xác nhận user đã đọc.
Ví dụ chat:
sent -> delivered -> read
Trong AI Judge, có thể có biến thể:
Notification đã gửi.
Giáo viên đã xem bài cần review.
Học sinh đã xem feedback.
Receipt nghe đơn giản nhưng có nhiều câu hỏi:
Delivered đến server hay đến thiết bị?
Read khi mở màn hình hay khi thật sự nhìn thấy?
Một user có nhiều thiết bị thì tính thế nào?
Receipt có cần lưu bền vững không?
Receipt có cần realtime không?
Không nên dùng receipt mơ hồ cho nghiệp vụ quan trọng.
Ví dụ:
Học sinh đã "đọc" feedback
cần định nghĩa rõ:
Mở trang feedback?
Scroll đến feedback?
Click xác nhận?
Realtime status cần semantic rõ.
Nếu không, mọi người sẽ hiểu khác nhau.
---
65.8. Fan-out là gì?
Fan-out là gửi một event đến nhiều người nhận.
Ví dụ:
Một học sinh nộp bài.
Dashboard của giáo viên cập nhật.
Admin monitor cập nhật.
Analytics consumer nhận event.
Notification service nhận event.
Trong chat:
Một user gửi message vào lớp.
Tất cả thành viên lớp nhận message.
Fan-out có hai kiểu thường gặp:
Fan-out on write.
Fan-out on read.
Fan-out on write:
Khi message tạo ra, hệ thống ghi/gửi bản cho từng người nhận.
Fan-out on read:
Khi user mở app, hệ thống query message chung và lọc theo quyền.
Fan-out on write nhanh khi đọc, nhưng tốn khi nhóm lớn.
Fan-out on read gọn khi viết, nhưng đọc có thể nặng.
Chọn cách nào phụ thuộc vào:
Số người nhận.
Tần suất viết.
Tần suất đọc.
Yêu cầu latency.
Permission.
Lưu trữ.
---
65.9. Fan-out trong lớp học lớn
Giả sử một lớp có 2.000 học sinh.
Giáo viên gửi thông báo:
Deadline được gia hạn 30 phút.
Nếu fan-out on write:
Tạo 2.000 notification records.
Ưu điểm:
Mỗi học sinh đọc nhanh.
Receipt dễ theo dõi.
Nhược điểm:
Ghi nhiều record.
Nếu lớp 100.000 người thì rất nặng.
Nếu fan-out on read:
Lưu một announcement.
Khi học sinh mở app, query announcement của lớp.
Ưu điểm:
Ghi nhẹ.
Nhược điểm:
Read path phức tạp hơn.
Receipt cá nhân cần thêm bảng.
Không có đáp án cố định.
Nhưng phải hiểu fan-out là chi phí thật.
Một message gửi cho một người và một message gửi cho một triệu người là hai bài toán khác nhau.
---
65.10. Ordering là gì?
Ordering là thứ tự event/message.
Trong chat, thứ tự rất quan trọng:
A: Câu 1
B: Trả lời câu 1
A: Câu 2
Nếu client thấy:
A: Câu 2
B: Trả lời câu 1
A: Câu 1
trải nghiệm rất rối.
Trong AI Judge, ordering cũng quan trọng:
queued -> grading -> graded
Không nên hiển thị:
graded -> grading
vì event cũ đến muộn.
Ordering khó vì:
Nhiều server.
Nhiều partition.
Network delay.
Retry.
Client offline.
Clock lệch.
Không nên chỉ dựa vào timestamp client.
Client clock có thể sai.
Thường cần server sequence, version, hoặc event position.
---
65.11. Server sequence number
Một cách quản lý ordering là server gán sequence number.
Ví dụ trong conversation:
message_seq = 101
message_seq = 102
message_seq = 103
Client nhận message có thể sắp xếp theo message_seq.
Nếu client thấy thiếu:
Đã có 101, nhận 103, thiếu 102.
nó có thể gọi API catch-up:
GET /messages?after_seq=101
Với submission state, có thể dùng version:
submission_version = 7
Event cũ version 6 đến sau version 7 thì bỏ qua.
Sequence/version giúp chống event đi lùi.
Nhưng cần rõ phạm vi:
Sequence theo conversation?
Theo tenant?
Theo assignment?
Theo resource?
Global sequence cho toàn hệ thống thường khó và không cần.
Sequence theo stream/resource thường thực tế hơn.
---
65.12. Event ordering và duplicate event
Realtime systems thường phải chịu:
Event đến trùng.
Event đến muộn.
Event đến sai thứ tự.
Điều này giống chương event-driven testing, nhưng ở đây client cũng bị ảnh hưởng.
Ví dụ:
Client nhận grading.completed hai lần.
Không nên tăng graded_count hai lần.
Ví dụ:
Client nhận grading.started sau grading.completed.
Không nên đổi status về grading.
Client nên xử lý idempotent:
Mỗi event có event_id.
Mỗi state update có version.
Nếu event đã xử lý, bỏ qua.
Nếu version cũ hơn current, bỏ qua.
Backend cũng phải idempotent.
Không nên đặt toàn bộ gánh nặng lên frontend.
Realtime tốt thường có cả:
Server state đúng.
Event có id/version.
Client reducer idempotent.
Catch-up API.
---
65.13. Snapshot + events
Một pattern rất thực dụng:
Client load snapshot hiện tại.
Sau đó nhận realtime events để cập nhật.
Ví dụ dashboard:
GET /assignment/A/dashboard
-> trả snapshot: counts, statuses, last_event_seq
Sau đó client subscribe:
assignment.A.events after last_event_seq
Nếu client mất kết nối, nó reconnect:
Tôi đã thấy đến seq 120.
Cho tôi events sau 120.
Nếu gap quá lớn hoặc events đã hết retention:
Client reload snapshot.
Snapshot + events giúp tránh hai cực đoan:
Chỉ snapshot -> không realtime.
Chỉ events -> client mới không biết trạng thái ban đầu.
Đây là pattern rất hay cho realtime UI.
---
65.14. Online/offline sync
Online/offline sync là khi client có thể mất mạng rồi quay lại.
Ví dụ:
Học sinh đang làm bài trên tablet.
Mạng mất.
Học sinh tiếp tục gõ.
Mạng có lại.
App đồng bộ bài làm lên server.
Hoặc:
Giáo viên mở dashboard.
Mất mạng 5 phút.
Kết nối lại.
Dashboard phải cập nhật các submission đã thay đổi.
Offline sync cần trả lời:
Client có được sửa offline không?
Sửa gì được?
Lưu local thế nào?
Khi reconnect gửi lên ra sao?
Nếu server cũng đã thay đổi thì xử lý xung đột thế nào?
User có thấy trạng thái pending/sync failed không?
Không phải app nào cần offline write.
Nhưng gần như mọi app realtime cần xử lý reconnect.
Mất mạng là bình thường.
App tốt không hoảng khi mất mạng.
---
65.15. Pending state
Khi client gửi một thay đổi nhưng server chưa xác nhận, UI nên có pending state.
Ví dụ chat:
User gửi message.
Message hiện ngay với trạng thái sending.
Server ack.
Message chuyển sent.
Nếu fail, hiện retry.
Ví dụ AI Judge:
Học sinh bấm submit.
UI hiện "Đang gửi".
Server lưu thành công.
UI hiện "Đã nhận bài".
Job chấm chạy sau.
Pending state giúp user không bấm lại vô ích.
Nhưng cần rõ:
Thay đổi đã lưu trên server chưa?
Hay chỉ mới optimistic trên client?
Nếu fail thì rollback UI thế nào?
Optimistic UI làm app nhanh hơn.
Nhưng nếu dùng sai, user tưởng hành động đã thành công trong khi server chưa nhận.
Với hành động quan trọng như nộp bài, cần rất cẩn thận.
---
65.16. Optimistic update
Optimistic update là client cập nhật UI trước khi server xác nhận.
Ví dụ:
User bấm like.
UI tăng like ngay.
Server xác nhận sau.
Hợp với hành động nhỏ, dễ rollback.
Không hợp với mọi thứ.
Ví dụ nộp bài thi:
Không nên hiển thị "Đã nộp chính thức" nếu server chưa lưu.
Có thể hiển thị:
Đang gửi...
rồi chỉ hiển thị:
Đã nhận bài
khi server xác nhận.
Với realtime state, optimistic update cần:
client_operation_id.
pending state.
server ack.
rollback/retry.
dedupe khi server event quay lại.
Nếu không, client có thể hiển thị trùng hoặc sai khi event thật đến.
---
65.17. Conflict resolution là gì?
Conflict xảy ra khi nhiều bên sửa cùng một dữ liệu theo cách không thể tự động ghép đơn giản.
Ví dụ:
Giáo viên A sửa rubric câu 1 thành 5 điểm.
Giáo viên B cùng lúc sửa rubric câu 1 thành 6 điểm.
Hoặc:
Học sinh offline sửa bài.
Trong lúc đó server đã nhận version mới từ thiết bị khác.
Conflict resolution là cách xử lý xung đột.
Có nhiều chiến lược:
Last write wins.
Server wins.
Client wins.
Merge tự động.
Yêu cầu user chọn.
Operational Transform.
CRDT.
Không có chiến lược nào luôn đúng.
Tùy dữ liệu.
Với điểm chính thức, không nên last write wins mù.
Với typing indicator, mất cũng không sao.
Với document collaborative editing, cần merge tinh vi hơn.
---
65.18. Last write wins
Last write wins nghĩa là thay đổi đến sau ghi đè thay đổi trước.
Ví dụ:
Version mới nhất thắng.
Ưu điểm:
Dễ làm.
Dễ hiểu.
Nhược điểm:
Có thể mất dữ liệu.
Ví dụ:
Teacher A sửa feedback đoạn đầu.
Teacher B sửa feedback đoạn cuối.
Nếu last write wins, một người có thể ghi đè toàn bộ sửa của người kia.
Last write wins hợp với dữ liệu đơn giản:
User status message.
Draft title ít quan trọng.
Presence metadata.
Không hợp với:
Tài liệu dài.
Rubric quan trọng.
Điểm chính thức.
Giao dịch tiền.
Nếu dùng last write wins, phải biết mình chấp nhận mất update trong trường hợp nào.
Không nên dùng vì lười nghĩ conflict.
---
65.19. Version check và optimistic locking
Một cách xử lý conflict phổ biến là version check.
Ví dụ record rubric có:
version = 7
Client A đọc version 7.
Client B cũng đọc version 7.
Client A update thành version 8.
Client B gửi update dựa trên version 7.
Server thấy:
current version = 8
client expected version = 7
Server reject:
409 Conflict
Client B phải reload và merge.
Optimistic locking không tự giải quyết conflict.
Nó phát hiện conflict.
Đây là bước rất quan trọng.
Phát hiện conflict tốt hơn ghi đè âm thầm.
Với dữ liệu quan trọng, reject và yêu cầu user xử lý có thể tốt hơn tự đoán.
---
65.20. Operational Transform ở mức khái niệm
Operational Transform, thường gọi OT, dùng nhiều trong collaborative editing.
Ý tưởng:
Thay vì chỉ gửi trạng thái cuối, client gửi operation.
Ví dụ:
Insert "abc" tại vị trí 5.
Delete 3 ký tự tại vị trí 10.
Nếu hai người cùng sửa, hệ thống biến đổi operation để chúng áp dụng đúng trên trạng thái đã thay đổi.
Ví dụ:
User A insert 3 ký tự trước vị trí của User B.
Operation của User B cần dịch vị trí +3.
OT mạnh nhưng phức tạp.
Nó hợp với:
Google Docs style collaborative editing.
Text editor realtime.
Document editing.
Không nên tự viết OT từ đầu nếu không thật sự cần.
Với nhiều ứng dụng, version check hoặc merge đơn giản đủ dùng.
Mục tiêu ở đây là hiểu:
Collaborative editing thật sự là bài toán khó.
Không phải chỉ WebSocket là xong.
---
65.21. CRDT ở mức khái niệm
CRDT là Conflict-free Replicated Data Type.
Hiểu đơn giản:
Các bản sao dữ liệu có thể được cập nhật độc lập,
sau đó merge lại mà không cần conflict thủ công,
và cuối cùng hội tụ về cùng một trạng thái.
CRDT hữu ích cho:
Collaborative editing.
Offline-first apps.
Distributed systems.
Counters, sets, maps đặc biệt.
Ví dụ đơn giản:
Hai client offline cùng thêm item vào danh sách.
Khi online lại, merge để có cả hai item.
CRDT không phải phép màu.
Nó có chi phí:
Dữ liệu metadata nhiều hơn.
Mental model khó hơn.
Không phải mọi business rule hợp với auto-merge.
Với điểm số chính thức hoặc tiền, CRDT thường không phải lựa chọn đúng.
Với collaborative notes/drafts, nó có thể rất hợp.
Điểm cần nhớ:
OT và CRDT là giải pháp cho một lớp bài toán khó, không phải thứ cần dùng cho mọi realtime app.
---
65.22. Persistence cho realtime message/state
Một câu hỏi quan trọng:
Event realtime có cần lưu không?
Câu trả lời tùy loại event.
Không cần lưu lâu:
typing.
cursor position.
presence heartbeat.
temporary hover state.
Cần lưu:
Chat message.
Submission status.
Grading result.
Notification.
Delivery receipt nếu nghiệp vụ cần.
Document operations nếu cần replay.
Nếu event quan trọng mà không lưu, client offline sẽ mất.
Ví dụ:
Giáo viên offline lúc grading.completed.
Nếu event chỉ đẩy qua WebSocket và không lưu ở đâu,
giáo viên reconnect sẽ không biết đã có điểm.
Giải pháp:
Persistent state trong database.
Event log hoặc changelog có retention.
Catch-up API.
Snapshot reload.
Realtime push là đường truyền nhanh.
Database/event log mới là nơi đảm bảo trạng thái không mất.
---
65.23. Reconnect và catch-up
Client realtime sẽ mất kết nối.
Do đó reconnect flow phải được thiết kế.
Ví dụ:
Client kết nối với last_seen_seq = 120.
Server trả events 121..130.
Client áp dụng.
Nếu events 121..130 không còn, server yêu cầu reload snapshot.
Flow tốt:
Connect.
Authenticate.
Subscribe đúng tenant/resource.
Send last seen position.
Receive missed events hoặc snapshot-required.
Resume realtime.
Nếu không có catch-up, client sẽ có trạng thái thiếu.
Nếu cứ reload toàn bộ sau mỗi reconnect, đơn giản nhưng có thể tốn.
Tùy use case.
Với chat, catch-up theo message sequence rất hữu ích.
Với dashboard nhỏ, reload snapshot có thể đủ.
Thiết kế reconnect là một phần của realtime, không phải ngoại lệ.
---
65.24. Backpressure trong realtime
Nếu server gửi event nhanh hơn client xử lý, cần backpressure.
Ví dụ:
Một dashboard nhận 10.000 updates/phút.
Browser không xử lý kịp.
UI lag.
Memory tăng.
Connection nghẽn.
Cách xử lý:
Coalesce updates.
Throttle.
Debounce.
Gửi snapshot thay vì từng event nhỏ.
Drop event ephemeral.
Batch events.
Giới hạn subscription.
Ví dụ:
Queue count thay đổi 100 lần/giây.
Dashboard không cần nhận đủ 100 event.
Chỉ cần cập nhật mỗi 1 giây.
Không phải event nào cũng có giá trị như nhau.
Message chat cần giữ.
Typing event có thể drop.
Progress update có thể gộp.
Realtime tốt không phải gửi mọi thứ ngay lập tức.
Realtime tốt là gửi đúng mức mà client và user cần.
---
65.25. Scaling WebSocket/SSE
Khi có nhiều connection realtime, server phải scale khác API stateless.
API stateless:
Request đến, xử lý, trả response.
WebSocket:
Connection giữ lâu.
Server phải quản lý nhiều kết nối.
Các câu hỏi:
User đang connected ở server nào?
Gửi event đến user qua server nào?
Nếu server chết, client reconnect ra sao?
Load balancer có hỗ trợ connection dài không?
Có cần sticky session không?
Presence lưu ở đâu?
Pub/sub nội bộ dùng gì?
Một kiến trúc phổ biến:
App services publish event vào broker/pubsub.
Realtime gateway subscribe.
Gateway gửi event đến connected clients.
Connection registry biết user/resource đang ở đâu.
Không nên để mọi service tự quản lý WebSocket riêng nếu hệ thống lớn.
Realtime gateway giúp tách concerns.
---
65.26. Authorization cho realtime subscription
Realtime subscription cũng cần permission.
Ví dụ:
User subscribe assignment A events.
Server phải kiểm tra:
User có thuộc tenant này không?
User có quyền xem assignment A không?
User có role phù hợp không?
Không nên chỉ kiểm tra lúc load page rồi cho subscribe tự do.
Mỗi subscription phải có scope rõ.
Ví dụ channel:
tenant:school_a:assignment:ass_123
User tenant B không được subscribe.
Nếu permission thay đổi trong lúc connection còn mở, cần xử lý:
Revoke subscription.
Disconnect.
Re-check định kỳ.
Token expiry.
Realtime là một đường dữ liệu riêng.
Nó phải có security ngang API.
Không phải vì dùng WebSocket mà bỏ qua authorization.
---
65.27. Realtime trong multi-tenant system
Trong hệ thống multi-tenant, realtime cần tenant-aware từ đầu.
Event nên có:
tenant_id.
resource_type.
resource_id.
event_id.
sequence/version.
Channel/topic cũng nên có tenant scope.
Ví dụ:
school_a.assignment.ass_123.dashboard
Không nên publish event chung chung:
assignment.ass_123.dashboard
nếu id có thể trùng hoặc permission phụ thuộc tenant.
Presence cũng cần scope:
User online trong tenant nào?
Đang xem lớp nào?
Đang active ở workspace nào?
Với AI agent, tool realtime cũng cần tenant context.
Ví dụ agent đang theo dõi rollout prompt cho tenant A không được nhận event tenant B.
Tenant isolation phải xuyên qua realtime layer.
---
65.28. Khi nào realtime thật sự cần?
Realtime đáng dùng khi:
Người dùng cần phản ứng trong vài giây hoặc dưới giây.
Nhiều người cùng phối hợp.
Trạng thái thay đổi thường xuyên.
Polling gây tải lớn hoặc trải nghiệm kém.
Tính năng cốt lõi là tương tác live.
Ví dụ:
Chat.
Typing indicator.
Live collaboration.
Live support.
Trading/bidding.
Game realtime.
Operational dashboard cần phản ứng nhanh.
Trong AI Judge, realtime đáng dùng cho:
Live grading progress trong kỳ thi.
Dashboard assignment đang diễn ra.
Chat/support giữa giáo viên và học sinh.
Collaborative rubric editing nếu có.
Nhưng không phải mọi thứ cần realtime.
Ví dụ:
Báo cáo tổng kết ngày.
Billing usage.
Analytics chậm vài phút.
Danh sách assignment ít đổi.
Polling hoặc refresh theo demand có thể đủ.
Realtime nên giải quyết nhu cầu thật, không phải để app trông hiện đại.
---
65.29. Khi nào polling đủ tốt?
Polling đủ tốt khi:
Độ trễ vài giây đến vài phút chấp nhận được.
Dữ liệu không đổi liên tục.
Số client không quá lớn.
Luồng không cần tương tác hai chiều.
Mất một update trung gian không quan trọng.
Ví dụ:
Trang lịch sử hóa đơn polling mỗi 30 giây khi đang xử lý payment.
Dashboard admin cập nhật mỗi 1 phút.
Export job status polling mỗi 5 giây.
AI grading result polling đến khi complete.
Polling có ưu điểm:
Dễ debug.
Dễ cache.
Dễ scale hơn connection dài.
Ít trạng thái connection.
Nhược điểm:
Không tức thì.
Có thể lãng phí request nếu polling quá thường.
Một cách thực dụng:
Bắt đầu bằng polling nếu đủ.
Chuyển realtime khi có nhu cầu rõ.
Đừng bắt đầu bằng WebSocket cho mọi màn hình chỉ vì thích realtime.
---
65.30. AI Judge: thiết kế realtime thực dụng
Với AI Judge, ta có thể chia realtime theo mức cần thiết.
Submission/grading status:
Persistent state trong database.
Event có submission_id, status, version.
Dashboard load snapshot.
Realtime event cập nhật status.
Reconnect thì catch-up hoặc reload snapshot.
Queue progress:
Không cần gửi từng job update.
Gộp metrics mỗi vài giây.
Hiển thị oldest job age, queued count, processing count.
Typing/chat:
Message lưu bền vững.
Typing ephemeral TTL.
Delivery/read receipt nếu thật sự cần.
Rubric collaboration:
Nếu chỉ một người sửa, version check đủ.
Nếu nhiều người cùng sửa realtime, cân nhắc OT/CRDT hoặc dùng thư viện có sẵn.
Offline submit:
Nếu hỗ trợ, cần local draft, pending state, upload retry, server ack rõ.
Không hiển thị đã nộp chính thức trước khi server xác nhận.
Realtime gateway:
Tenant-aware subscription.
Event id/version.
Auth check.
Connection registry.
Pub/sub backend.
Thiết kế này không quá hào nhoáng, nhưng đáng tin.
---
65.31. Những sai lầm phổ biến
Sai lầm thứ nhất:
Nghĩ WebSocket tự giải quyết realtime.
WebSocket chỉ là đường truyền. State sync mới là bài toán chính.
Sai lầm thứ hai:
Không có catch-up khi reconnect.
Client mất event và trạng thái lệch.
Sai lầm thứ ba:
Không phân biệt ephemeral và persistent event.
Typing bị lưu quá mức, message quan trọng lại không có lịch sử đủ.
Sai lầm thứ tư:
Không có event id/version.
Duplicate và out-of-order làm UI sai.
Sai lầm thứ năm:
Tin timestamp client để ordering.
Clock client không đáng tin cho ordering quan trọng.
Sai lầm thứ sáu:
Không kiểm tra permission cho subscription.
Realtime channel có thể lộ dữ liệu như API.
Sai lầm thứ bảy:
Dùng last write wins cho dữ liệu quan trọng mà không biết.
Update của user có thể bị mất âm thầm.
Sai lầm thứ tám:
Realtime hóa mọi thứ.
Hệ thống phức tạp hơn trong khi polling đã đủ.
---
65.32. Checklist realtime state synchronization
Khi thiết kế realtime, hãy hỏi:
- Trạng thái nào cần realtime?
- Trạng thái nào chỉ cần polling?
- Event nào ephemeral?
- Event nào persistent?
- Source of truth nằm ở đâu?
- Client load snapshot ban đầu thế nào?
- Client reconnect catch-up thế nào?
- Event có event_id không?
- State update có version/sequence không?
- Duplicate event xử lý thế nào?
- Out-of-order event xử lý thế nào?
- Client offline có được write không?
- Pending state hiển thị thế nào?
- Conflict resolution là gì?
- Có cần OT/CRDT không hay version check đủ?
- Fan-out theo write hay read?
- Subscription có tenant/permission check không?
- Có backpressure/coalescing không?
- Connection scale bằng cách nào?
- Realtime event có audit/log cần thiết không?
- Polling có đủ tốt không?
Nếu nhiều câu trả lời là "chưa biết", realtime layer có thể hoạt động trong demo nhưng dễ lệch state trong production.
---
65.33. Bảng nhìn nhanh
| Khái niệm | Hiểu đơn giản | |---|---| | Realtime state sync | Đồng bộ trạng thái giữa nhiều client/service gần thời gian thật | | Presence | Ai đang online/active/idle | | Typing indicator | Trạng thái tạm thời "đang gõ" | | Delivery receipt | Xác nhận đã giao đến client/thiết bị | | Read receipt | Xác nhận user đã đọc/xem | | Fan-out | Gửi một event đến nhiều người nhận | | Ordering | Đảm bảo thứ tự event/message có ý nghĩa | | Sequence/version | Số thứ tự để chống thiếu/lệch/event cũ | | Snapshot + events | Load trạng thái hiện tại rồi áp dụng event mới | | Offline sync | Đồng bộ lại sau khi client mất mạng | | Conflict resolution | Cách xử lý khi nhiều bên sửa cùng dữ liệu | | OT | Biến đổi operation để collaborative editing đúng hơn | | CRDT | Kiểu dữ liệu có thể merge tự động và hội tụ | | Backpressure | Không gửi nhanh hơn client/hệ thống xử lý được |
---
65.34. Kết luận của chương
Realtime State Synchronization không chỉ là chọn WebSocket hay SSE.
Đó là bài toán thiết kế trạng thái chung:
Ai là source of truth?
Event nào cần lưu?
Event nào có thể mất?
Ordering đảm bảo ra sao?
Client reconnect thế nào?
Conflict xử lý thế nào?
Permission realtime enforce ở đâu?
Presence, typing và progress updates thường là ephemeral.
Message, submission status, grading result và document changes thường cần persistence.
Event cần id/version để chống duplicate và out-of-order.
Offline/reconnect cần snapshot, catch-up hoặc reload rõ ràng.
Collaborative editing thật sự có thể cần OT/CRDT, nhưng nhiều hệ thống chỉ cần version check và conflict UI.
Thông điệp cần nhớ:
> Realtime tốt không phải là gửi mọi thứ ngay lập tức. Realtime tốt là giữ các bản sao trạng thái đủ gần nhau, không mất dữ liệu quan trọng, không lộ dữ liệu sai người, và biết phục hồi khi mạng không hoàn hảo.
Ở chương tiếp theo, ta sẽ nói về Extensibility và Integration Boundary: public API, partner API, webhook, plugin, sandbox, versioning, rate limit và khi nào mở platform là quá sớm.