513 words • 3 minute(s) to read

Xây dựng Online Judge từ con số 0

Posted on

Helu các bạn, mình là Quang, đang là một học sinh. Mình không dám tự nhận là full-stack developer nhưng cũng có kha khá kinh nghiệm với việc dev backend, frontend và CI/CD cũng như system admin. Hiện tại mình đang phát triển một Online Judge, tương tự như DMOJ, VNOJ như là một dự án cá nhân :D. Qua quá trình phát triển thì mình có một số trải nghiệm muốn chia sẻ với mọi người.

Ý tưởng

Ban đầu mình chỉ muốn phát triển một OJ đơn giản để chấm mấy bài thay cho Themis nhưng sau một thời gian code thì mình muốn tạo ra một sản phẩm để thay thế DMOJ/VNOJ (do mình lười học + không thích Python nên làm lại từ đầu luôn thay vì fork VNOJ 😝)

Techstack

Về techstack, mình khuyên các bạn nên xác định rõ mục đích, nhu cầu của mình, đừng để như mình, phải migrate 7749 lần do tech không phù hợp 😢

Backend

Về backend thì mình dùng Go cùng framework huma + echo cho HTTP server. Go có ưu điểm là multithreading ngon, spawn được nhiều goroutine và mình cũng có thời gian sử dụng Go khá lâu rồi nên đấy cũng là lí do mình chọn Go 😉. Còn huma và echo thì mình tình cờ tìm thấy ở awesome-go. huma bao gồm khá nhiều thứ như là SSE, controller, OpenAPI và validator. Còn echo thì có các middleware khá là tiện như Recover, BodyDump, …

ORM

Hiện tại mình đang sử dụng ent, trước đây mình có dùng bun nhưng hơi lười viết query bằng tay nên sau đó mình quyết định dùng ent luôn 😗. Nếu các bạn muốn ORM tương tự nhưng đơn giản và nhẹ hơn thì có thể tham khảo sqlc.

Judge - máy chấm

Mình thiết kế máy chấm là các microservice, với master-slave relationship. Sandbox cũng là thứ không thể thiếu khi chấm bài, để phòng tránh malicious code + privilege escalation. Ở đây mình dùng go-sandbox và cgroupv2 để isolate và kiểm soát tài nguyên của các bài nộp. Khi nhận được submission thì mình sẽ bỏ nó vào một queue và phân vào các CPU để chấm, do đó có thể v-scale được tốt hơn thay vì phải h-scale như DMOJ.

Message queue

Như mình đã nói ở trên, do máy chấm là microservice nên cần dùng message queue để giao tiếp giữa backend và máy chấm. Trước đây mình có sử dụng RabbitMQ nhưng mình gặp một vấn đề đó là race condition do channel không thread-safe nhưng mình lại cần nó để giao tiếp giữa các slave với master. Do đó mình chọn cách tự viết message queue bằng dRPC. Mỗi máy chấm là một client, nó sẽ kết nối đến main RPC ở backend bằng một stream để nhận các submission và một stream khác để thông báo kết quả chấm với backend.

Database

Mình chọn PostgreSQL làm DB do không biết nên chọn cái nào khác (và cũng do hiệu năng của nó khá ổn).

Implementation

Phần này mình khá lười viết quá nên mình sẽ cập nhật sau nhé 🐧