Làm rõ: bác sĩ có lịch làm việc lặp theo tuần, mỗi ca chia thành slot 15-30 phút, có thể nghỉ đột xuất, và bệnh nhân hủy khá nhiều. Tải rất thấp (vài chục request/giây) nhưng yêu cầu tính đúng đắn cao — đây là bài về mô hình dữ liệu và tính đồng thời, không phải về scale.
Data model — điểm quyết định là chọn "vật thể hoá" slot:
schedules(doctor_id, weekday, start_time, end_time, slot_minutes)
slots(id, doctor_id, start_at, end_at, status,
unique (doctor_id, start_at))
appointments(id, slot_id unique, patient_id, status, created_at)Sinh trước slots cho 30-60 ngày tới bằng job hằng ngày thay vì tính động từ schedules mỗi lần đọc. Có bản ghi cụ thể thì mới đặt được ràng buộc duy nhất.
Chống đặt trùng: đừng dựa vào kiểm tra "còn trống không" ở tầng ứng dụng — luôn có khoảng trống giữa lúc kiểm tra và lúc ghi. Cách chắc chắn là để DB ép ràng buộc:
- unique (slot_id) trên appointments → người thứ hai nhận lỗi vi phạm ràng buộc và được báo "slot vừa có người đặt".
- Hoặc UPDATE slots SET status = 'booked' WHERE id = ? AND status = 'open' rồi kiểm tra số dòng bị ảnh hưởng — cập nhật có điều kiện, không cần khoá tường minh.
Các chi tiết dễ bị hỏi thêm:
- Múi giờ: lưu timestamptz, hiển thị theo giờ địa phương. Lưu giờ local trần là lỗi kinh điển.
- Giữ chỗ tạm khi bệnh nhân đang điền thông tin: trạng thái held kèm hạn, job dọn trả slot về open.
- Hủy và no-show: giữ lịch sử trạng thái để làm báo cáo, đừng xoá bản ghi.
- Nhắc lịch qua SMS/Zalo → sự kiện đẩy vào queue, không gửi đồng bộ trong request đặt lịch.
Đánh đổi: sinh trước slot tốn dung lượng và phải chạy job bù khi bác sĩ đổi lịch, nhưng đổi lại có ràng buộc duy nhất ở tầng DB — đó là cách duy nhất bảo đảm không double-booking mà không cần khoá phân tán.