Kiến trúc hệ thống thi trắc nghiệm trực tuyến chống gian lận và chịu tải lớn
Hướng dẫn kiến trúc thi trực tuyến chống gian lận và chịu tải lớn: autosave, xáo trộn câu hỏi, tab switching, submission receipt và xử lý 10.000 lượt nộp.
ShanverseCập nhật 11 phút đọc— lượt xemĐang tải lượt xem
Online Assessments
EdTech
Scalability
1. Mô hình tải: đừng chỉ nhìn trung bình
Giả sử 10.000 sinh viên làm bài 60 phút:
Autosave mỗi 15 giây tạo trung bình khoảng 667 request/giây.
Nếu tất cả submit trong 10 giây cuối, riêng submission đạt 1.000 request/giây.
Khi API chậm, client retry đồng loạt có thể nhân tải lên nhiều lần.
Deadline tạo synchronized burst; đây là bài toán khác hẳn traffic web thông thường.
Thiết kế phải chịu ba loại tải: steady autosave, burst submit, và retry/reconnect storm. Kiểm thử theo burst chứ không chỉ ramp-up đều.
2. Domain và state machine
Bình luận
ExamVersion bất biến sau khi publish.
AttemptManifest chốt danh sách/thứ tự câu và lựa chọn cho một thí sinh.
AnswerRevisions lưu các delta đã accepted.
IntegrityEvents là tín hiệu có context, không phải verdict.
SubmissionReceipt chứng minh server đã nhận bài đúng hạn.
Server giữ thời gian chính thức. Client countdown chỉ là hiển thị.
3. Kiến trúc tổng thể
Mermaid
Loading diagram…
Tách control plane (tạo/publish đề, phân quyền) khỏi data plane (start, autosave, submit). Trong giờ thi, ưu tiên data plane và vô hiệu hóa report nặng, export hoặc tác vụ admin không cần thiết.
4. Attempt manifest bất biến
Khi bắt đầu bài, server tạo manifest:
Manifest được lưu trước khi trả câu hỏi. Nếu client reconnect, server trả lại đúng manifest, không shuffle lại. Nội dung trả về không chứa đáp án đúng, rubric nội bộ hoặc câu ngoài danh sách được giao.
5. Xáo trộn câu hỏi đúng cách
Xáo trộn không chỉ gọi Math.random() trên client. Quy trình server-side:
Chốt ExamVersion và pool rules.
Lọc câu theo môn, độ khó, learning objective và constraint.
Dùng CSPRNG hoặc seed được tạo an toàn.
Chạy Fisher–Yates trên bản sao danh sách.
Lưu IDs và thứ tự vào manifest.
Xáo trộn lựa chọn, trừ các option được đánh dấu cố định như “Tất cả đáp án trên”.
Chấm bằng question/option ID, không chấm theo vị trí A/B/C/D.
Cân bằng đề quan trọng hơn ngẫu nhiên tuyệt đối. Có thể tạo các blueprint tương đương, theo dõi exposure rate và chạy phân tích độ khó sau kỳ thi. Seed/manifest phải audit được nhưng không lộ trước khi thi.
6. Autosave: local-first nhưng server-authoritative
Client giữ draft trong memory và IndexedDB. Mỗi thay đổi tạo delta có sequence tăng dần:
Autosave theo các trigger:
debounce 1–2 giây sau khi đổi đáp án;
flush theo batch 10–20 giây;
flush khi chuyển câu;
cố gắng flush khi visibilitychange sang hidden;
flush trước submit.
Không gửi toàn bộ bài mỗi lần. Batch các patch chưa acknowledged và giới hạn payload. UI phải hiển thị Đang lưu, Đã lưu lúc..., hoặc Mất kết nối — đã lưu trên thiết bị.
7. Idempotency và ordering
Retry không được tạo revision trùng hoặc ghi đè đáp án mới bằng packet cũ. Database có unique (attempt_id, client_id, client_sequence) hoặc revision_id.
Trong cùng transaction, cập nhật projection latest_answers chỉ khi sequence mới hơn. Server trả acceptedThroughSequence để client xóa outbox cục bộ. Không dùng timestamp client để quyết định revision mới nhất.
Nếu nhiều tab cùng mở, cấp clientId riêng và policy rõ: cho phép handoff có version vector, hoặc chỉ một active lease. Không âm thầm để last-write-wins giữa hai tab.
8. sendBeacon không phải chiến lược autosave chính
visibilitychange cho biết tài liệu chuyển hidden khi đổi tab, thu nhỏ cửa sổ hoặc chuyển app. Đây là cơ hội flush, nhưng không đảm bảo browser sẽ tiếp tục chạy lâu.
navigator.sendBeacon() phù hợp payload POST nhỏ khi page sắp rời đi và có giới hạn payload thực tế; nó không thay thế autosave định kỳ có acknowledgement. Với auth header hoặc response, dùng fetch(..., { keepalive: true }) khi phù hợp, vẫn phải giữ payload nhỏ. Nguồn sự thật là các save đã được server ACK; local IndexedDB là lớp phục hồi.
9. Tab switching detection: signal, không phải bằng chứng
Page Visibility API không biết người dùng đã xem gì. hidden có thể do:
đổi tab hoặc ứng dụng;
cuộc gọi/hệ thống che màn hình;
trình đọc màn hình, công cụ hỗ trợ;
trình duyệt mobile khóa/tắt màn hình;
giám thị yêu cầu mở tài liệu được phép.
Do đó:
log cả hidden/visible và duration;
batch sự kiện, không gửi từng event đồng bộ;
cho thí sinh thấy policy trước khi thi;
hỗ trợ open-book và accommodation;
không tự động trừ điểm/hủy bài;
chỉ flag pattern để human review cùng bằng chứng khác;
đặt retention và quyền truy cập log.
Không cố vượt sandbox trình duyệt để theo dõi ứng dụng khác. Fullscreen loss, blur, clipboard hoặc devtools detection đều dễ sai và bị bypass; dùng như telemetry hạn chế, không như security boundary.
10. Submission protocol có receipt
Submit phải idempotent và nhỏ:
Quy trình:
Client flush pending patches.
Gửi submit với idempotency key.
Server lock attempt ngắn, kiểm tra state và deadline.
Commit submitted_at, receipt và outbox event trong một transaction.
Trả receipt ngay.
Scoring chạy bất đồng bộ.
Nếu một số patch chưa tới nhưng request đúng deadline, có thể trả pending_reconciliation, nhận phần thiếu trong grace window được ràng buộc bằng sequence/hash, rồi finalize. Policy này phải được công bố và test kỹ.
11. Server-side deadline và auto-finalize
Không phụ thuộc client bấm nút. Scheduler/worker quét attempt đến hạn và finalize theo các revision server đã nhận. Client được sync thời gian qua API và hiển thị countdown dựa trên deadlineAt; không tin đồng hồ thiết bị.
Grace period dành cho network transit, không kéo dài thời gian làm bài. Có thể chấp nhận request đến sau vài giây nếu token/request được phát trước deadline theo protocol đã thiết kế, nhưng không tin timestamp tự khai từ client.
12. Chịu burst 10.000 lượt nộp
Mục tiêu submission endpoint là ghi receipt bền vững thật nhanh, không chấm điểm inline.
Pre-scale API, DB pool và load balancer trước giờ kết thúc.
CDN phục vụ JS, ảnh và nội dung tĩnh.
Dùng prepared statement, payload nhỏ, transaction ngắn.
Không cập nhật một hot counter/row cho toàn kỳ thi.
Partition/routing attempt theo exam/session khi dữ liệu chứng minh cần.
Queue scoring, email và analytics; giữ submission path độc lập.
Ưu tiên submit/autosave hơn dashboard/report.
Dùng bounded queues, admission control và load shedding cho traffic không thiết yếu.
10.000 request trong một giây là burst khác hẳn 10.000 request trong một phút. Capacity plan theo peak concurrency, connection setup, transaction latency và retry amplification.
13. Retry an toàn
Client retry autosave/submit với exponential backoff + full jitter, nhưng deadline cần profile riêng:
Không retry mọi lỗi: 4xx policy/validation không tự retry.
429/503 đọc Retry-After nếu có.
Có retry budget để tránh khuếch đại overload.
Một idempotency key giữ nguyên qua mọi lần retry.
Sau timeout submit, gọi GET /attempts/:id/receipt trước khi gửi lại.
Server rate-limit theo exam và account nhưng dành capacity cho thao tác submit hợp lệ. Không để bot traffic hoặc report admin chiếm hết pool.
14. PostgreSQL schema và contention
Mỗi attempt ghi vào row riêng, tránh lock chung. Index theo access path: (exam_id, submitted_at), (attempt_id, client_id, client_sequence). Report chạy read replica/warehouse sau khi thi, không scan bảng chính trong lúc burst. Connection pool phải bounded; nhiều connection hơn không luôn làm database nhanh hơn.
Chỉ trả câu trong manifest, theo section unlock rule.
Không gửi đáp án đúng hoặc explanation trước khi đóng bài.
Authorization object-level cho mọi attempt/question.
Rate limit scraping; watermark bản đề khi phù hợp.
CSP, CSRF defense, XSS-safe rich content và signed asset URL.
Audit publish đề, thay đổi thời gian, override và regrade.
Mã hóa dữ liệu, backup/restore test và retention rõ ràng.
Randomization giảm chia sẻ đáp án nhưng không thay thế thiết kế câu hỏi tốt. Dùng nhiều phiên bản tương đương, giới hạn thời gian hợp lý và đánh giá theo reasoning khi có thể.
16. Scoring và regrade
Chấm điểm dựa trên immutable ExamVersion, manifest và answer revision đã finalize. Worker dùng idempotency key (attemptId, scoringPolicyVersion). Lưu breakdown, rule version và model/manual decision nếu có.
Khi phát hiện câu lỗi, tạo RegradeRun mới, không ghi đè score cũ. Kết quả có before/after, actor, reason và audit trail. Không để queue duplicate phát hai lần thông báo hoặc chứng nhận.
17. Observability và load test
Theo dõi:
autosave ACK p95/p99, pending patches và conflict rate;
submit acceptance latency, receipt errors và reconciliation rate;
DB pool saturation, lock wait, WAL/IO và transaction errors;
queue depth, oldest scoring job và outbox lag;
retry rate, 429/503, request amplification;
integrity-event volume và false-positive review outcome.
Load scenario:
10.000 login/start gần đồng thời.
Autosave đều với jitter và payload thực tế.
Mất mạng 5 phút rồi reconnect storm.
10.000 submit trong 1, 10 và 60 giây.
Timeout gây retry với và không có jitter.
Mất một API node, Redis node hoặc DB failover.
Scoring queue chậm nhưng receipt path vẫn hoạt động.
Invariant quan trọng: không mất answer đã ACK, mỗi attempt có tối đa một final submission, retry trả cùng receipt, và score có thể tái tạo.
18. Accessibility, privacy và fairness
Keyboard navigation, screen reader, zoom, contrast và extra-time accommodation.
Không coi assistive technology là dấu hiệu gian lận.
Đồng hồ và cảnh báo không chỉ dựa vào màu/animation.
Cho phép support xử lý mất điện/mạng với audit rõ.
Thu tối thiểu telemetry; không record camera/màn hình nếu không cần và chưa có cơ sở phù hợp.
Cho thí sinh quy trình khiếu nại, tải receipt và xác nhận trạng thái bài.
Lộ trình triển khai
ExamVersion + AttemptManifest bất biến.
Local draft + autosave delta + server ACK.
Idempotent submit + durable receipt.
Server deadline + auto-finalize.
Randomization blueprint và audit.
Integrity signals + human review.
Async scoring, outbox và regrade.
Peak load/chaos tests và operational runbook.
Production checklist
Đề đã publish là bất biến; mỗi attempt có manifest cố định.
Autosave dùng delta, sequence, idempotency và ACK.
IndexedDB giữ pending changes khi mất mạng.
Submit trả durable receipt trước khi chấm.
Deadline và status do server quyết định.
Có auto-finalize khi client biến mất.
Tab-switch chỉ là signal, không tự động kết tội.
Scoring/regrade có version và audit.
Đã test 10.000 submit burst và retry storm.
Report/admin traffic không tranh capacity với submit.
Kết luận
Kiến trúc tốt nhất đặt độ bền dữ liệu và tính có thể kiểm toán ở trung tâm: manifest bất biến, autosave có ACK, submit idempotent với receipt, deadline phía server và scoring bất đồng bộ. Các biện pháp chống gian lận chỉ đáng tin khi minh bạch, có ngữ cảnh, tôn trọng accessibility và luôn có human review.
Hướng dẫn thiết kế gamification bằng microservices cho ứng dụng học tập: learning streak, XP, leaderboard, badges, transactional outbox, Redis Sorted Sets và anti-cheat.