Skip to content

Instantly share code, notes, and snippets.

@dattp
Last active March 27, 2022 04:34
Show Gist options
  • Save dattp/6ede4b0cc53acb04d48d1734f25d66da to your computer and use it in GitHub Desktop.
Save dattp/6ede4b0cc53acb04d48d1734f25d66da to your computer and use it in GitHub Desktop.

Bài toán thống kê học sinh nghỉ học

đề bài:

  • thống kê số học sinh nghỉ học có phép, nghỉ không phép.
  • đầu ra: thống kê theo toàn bộ sở, phòng giáo dục, trường học.
  • ví dụ: nếu thống kê các trường cấp 3 của sở HN, thì trả về 1 ds các trường ở HN đã điểm danh dữ liệu bao gồm: tổng học sinh, hs nghỉ có phép, hs nghỉ không phép.
  • có 2 tính năng đang tác động đến thống kê này: đơn nghỉ học, và điểm danh lớp học.
  • Mỗi khi có đơn xin nghỉ phép từ PH => GV, GV duyệt đơn => hs nghỉ có phép.
  • Mỗi khi giáo viên điểm danh HS là nghỉ có phép => hs nghỉ có phép. Điểm danh học sinh nghỉ không phép => ds nghỉ không phép.

Nghiệp vụ cơ bản

  • tính năng đơn nghỉ phép: PH tạo đơn xin nghỉ phép cho hs, gửi đến GV, GV duyệt đơn hoặc huỷ đơn.
  • tính năng điểm danh: GV điểm danh 1 danh sách học sinh trong lớp, mỗi học sinh có 3 trạng thái: đi học, nghỉ có phép, nghỉ không phép.
  • 1 ngày có thể điểm danh nhiều lần, mỗi lần đc chọn 1 trạng thái điểm danh khác nhau.
  • case đặc biệt:
    • Nếu trc đó đã có đơn nghỉ phép, GV điểm danh nghỉ có phép => đơn đc duyệt.
    • Nếu trc đó đã có đơn nghỉ phép, GV điểm danh đi học => đơn bị huỷ.
    • Nếu trc đó đã có đơn nghỉ phép được duyệt, GV điểm danh đi học => đơn bị huỷ.
    • GV có thể điểm danh các ngày trong quá khứ => có thể thay đổi giá trị.

Lưu dữ liệu trong mongo.

  • bảng don_nghi_phep lưu dữ liệu về đơn xin nghỉ phép. Mỗi lần có 1 đơn nghỉ phép => 1 bản ghi. Hiện tại có 5.419.011 bản ghi.
  • bảng diem_danh_lop_hoc, mỗi 1 lần giáo viên điểm danh theo ds học sinh, 1 học sinh trong 1 ngày sẽ sinh ra 1 bản ghi (các lần điểm danh trong ngày sẽ nằm trong mảng trạng thái của bản ghi đó). Hiện tại có 192.119.364 bản ghi.

Xử lý vấn đề:

Ver1: những ngày đầu

  • mỗi khi có query thống kê => đọc trực tiếp ở 2 bảng trên => tổng hợp dữ liệu. Khi dữ liệu tăng lên quá nhiều => api response chậm

Ver2: Tách bảng thống kê ra riêng

  • Lưu trong bảng thong_ke_nghi_phep: Mỗi nhà trường trong 1 ngày sẽ có 1 bản ghi. Lưu giá trị số của hs nghỉ phép, hs nghỉ không phép.
  • Mỗi khi có hoạt động về đơn nghỉ phép hay điểm danh thì phải tính toán dữ liệu để cộng hoặc trừ lại dữ liệu.

Ver3: Khi phát triển nhiều tính năng hơn của nghỉ phép và điểm danh.

  • Đơn nghỉ phép có thể được huỷ (trừ dữ liệu trong bảng thống kê).
  • Khi đã có đơn nghỉ phép được duyệt (số hs nghỉ phép được +1), nhưng khi điểm danh học sinh đó là đi học => phải check lại trạng thái trước đó của học sinh đó, để biết dữ liệu cộng hay trừ.
  • Hoặc lần đểm danh thứ N gv điểm danh học sinh A là nghỉ học, nhưng đến lần N + i có thể điểm danh là đi học => phải check lại dữ liệu trước đó của A để biết là số liệu của học sinh A thay đổi => trừ đi cái số HS nghỉ học.
  • Khi phát sinh những nghiệp vụ mới => khó kiểm soát việc cộng trừ dữ liệu hơn.
    • Hướng xử lý đang nghĩ tới có 2 cách.
      • Kiểm soát hết các case để cộng trừ.
      • 1 job định kỳ (throttle) để quét hết bảng ghi trong ngày => tổng hợp dữ liệu để update vào bảng đọc.
@minhpq331
Copy link

minhpq331 commented Mar 27, 2022

Bài toán này được chia làm 2 phần như sau:

  1. Phần db hoạt động
    Xử lý nghiệp vụ liên quan đến điểm danh, nộp đơn xin nghỉ, lịch sử xin nghỉ phía app,...
    Phần này là phần data raw, cần xem xét tốc độ tăng lên của data (1 ngày phát sinh bao nhiêu bản ghi raw) để xem xét biến nó thành time-series data với kiến trúc data tăng theo thời gian và có tính hot-cold (data lâu sẽ cold, data mới sẽ hot). Phương pháp lưu trữ data dạng này là partitioning theo time (tạo collection mới theo từng tháng hoặc tuần,...). Đảo bảo tốc độ truy vấn cho data hot (gần đây) bằng việc giữ size collection nhỏ, size index nhỏ để fit trên ram.
    Nhìn chung kỹ thuật partitioning theo time chỉ phát sinh 1 vấn đề là việc query data giữa các partition sẽ phải thực hiện thủ công (nhưng cũng nên tránh). Do đó khi thiết kế app cần consider vấn đề này và tạo ra limit khi query data. Ví dụ xem lịch sử điểm danh bắt buộc phải theo tháng,...

  2. Phần db analytic
    Xử lý nghiệp vụ liên quan tới analytic như tính tổng theo trường, lớp, sở,...
    Đây là nghiệp vụ riêng biệt không liên quan tới phần hoạt động và có 2 hướng làm như sau:

  • Nếu sử dụng trực tiếp mongodb thì sẽ đi theo hướng pre-calculate như hiện tại. Ưu điểm là không phát sinh dữ liệu hay db mới. Nhược điểm là các yêu cầu dữ liệu phải stable, ít thay đổi và ổn định. Ngoài ra case thay đổi dữ liệu quá khứ là 1 case rất khó xử lý do việc tính tổng số không gồm số chi tiết.
  • Sử dụng 1 cơ sở dữ liệu quan hệ (ví dụ PostgreSQL) để tính toán data on-demand. Ưu điểm là query nhanh, phù hợp với yêu cầu thống kê, dễ mở rộng khi có yêu cầu thống kê số custom, đáp ứng được case thay đổi dữ liệu quá khứ mà ít gây sai số. Nhược điểm là duplicate dữ liệu và phát sinh 1 db khác nên vấn đề sẽ nằm ở quá trình sync dữ liệu. Với dạng db analytic riêng biệt thì có rất nhiều kỹ thuật tối ưu để tăng tốc kết quả đầu ra cả về kiến trúc lẫn query. Ngoài ra cũng hỗ trợ các query thống kê phức tạp đa chiều (theo trường, lớp, sở, ngày tháng, khung thời gian,...)

Với bài toán và các vấn đề hiện tại thì anh nghĩ có thể xem xét hướng sử dụng 1 database chuyên analytic cho việc tính toán thống kê chứ nếu yêu cầu thống kê thay đổi thì mongodb sẽ khó đáp ứng hết. Với mongo hiện tại thì sẽ áp dụng việc partitioning để tăng khả năng truy vấn data raw là được.

Hiện tại database của bọn anh cũng có sử dụng mongodb làm db hoạt động (user activity, lịch sử học các thứ). Tuy nhiên toàn bộ phần thống kê thì mongo không đáp ứng được do đó đã sync phần lớn data với 1 db khác là postgres để chuyên xử lý các query analytic.

Tham khảo kiến trúc: https://kipalog.com/posts/He-thong-analytic--suong-suong--tu-du-lieu-30-trieu-nguoi-dung

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment