1. 12:16 07/08/2026
Trong nhiều web/app, không phải việc gì cũng nên xử lý ngay trong request của người dùng. Nếu người dùng bấm “Đặt hàng”, “Xuất báo cáo”, “Gửi email xác nhận” hoặc “Tải ảnh lên”, việc bắt họ chờ toàn bộ quy trình chạy xong có thể làm trải nghiệm chậm, dễ timeout và khó mở rộng.
Một cách tiếp cận phổ biến là dùng background job: request chính chỉ ghi nhận yêu cầu, sau đó đẩy phần việc tốn thời gian sang hàng đợi để worker xử lý phía sau.
Ví dụ thực tế: khi người dùng đăng ký tài khoản
Thay vì làm tất cả trong một request:
- Tạo user trong database
- Gửi email xác nhận
- Ghi log marketing
- Đồng bộ sang CRM
- Tạo dữ liệu mặc định
Ta có thể tách ra:
- Request chính: validate dữ liệu, tạo user, trả về kết quả đăng ký thành công
- Background job: gửi email xác nhận, đồng bộ CRM, tạo dữ liệu phụ nếu cần
Cách này giúp màn hình phản hồi nhanh hơn và giảm rủi ro việc gửi email chậm làm hỏng cả quá trình đăng ký.
Khi nào nên dùng background job?
Nên cân nhắc background job cho các việc:
- Tốn thời gian: xuất file Excel/PDF, xử lý ảnh/video, tính toán báo cáo
- Không cần kết quả ngay lập tức: gửi email, push notification, đồng bộ hệ thống ngoài
- Có thể thử lại nếu thất bại: gọi API bên thứ ba, gửi webhook
- Chạy định kỳ: dọn dữ liệu tạm, cập nhật trạng thái, tổng hợp số liệu
Không nên đẩy bừa mọi thứ vào background job. Những thao tác cần phản hồi tức thì và ảnh hưởng trực tiếp đến quyết định của người dùng, như kiểm tra số dư trước khi thanh toán, thường vẫn cần xử lý đồng bộ hoặc ít nhất phải thiết kế trạng thái rất rõ ràng.
Một luồng xử lý cơ bản
Một mô hình đơn giản thường gồm:
- App server: nhận request từ người dùng
- Queue: nơi lưu danh sách công việc cần làm
- Worker: tiến trình riêng lấy job từ queue ra xử lý
- Database/log: ghi trạng thái, kết quả, lỗi nếu có
Ví dụ: người dùng bấm “Xuất báo cáo”. Server tạo một bản ghi report với trạng thái “đang xử lý”, đẩy job vào queue, rồi trả về thông báo: “Báo cáo đang được tạo, bạn sẽ nhận được khi hoàn tất”. Worker xử lý file, lưu file, đổi trạng thái thành “hoàn tất”, sau đó gửi thông báo hoặc email cho người dùng.
Những lỗi thiết kế thường gặp
1. Nghĩ rằng job luôn chạy thành công
Background job có thể thất bại vì mất mạng, API ngoài lỗi, dữ liệu không hợp lệ hoặc worker bị dừng giữa chừng. Vì vậy cần có cơ chế retry, ghi lỗi và theo dõi job thất bại. Với các job quan trọng, nên có màn hình hoặc công cụ nội bộ để kiểm tra và chạy lại.
2. Không thiết kế job idempotent
Một job có thể bị chạy lại. Nếu job “gửi email hóa đơn” retry ba lần, bạn có thể vô tình gửi ba email giống nhau. Nếu job “cộng điểm thưởng” bị chạy hai lần, dữ liệu sẽ sai.
Hãy thiết kế để chạy lại không gây hỏng dữ liệu. Ví dụ:
- Lưu trạng thái “đã gửi email” trước khi gửi lại
- Dùng mã giao dịch duy nhất
- Kiểm tra bản ghi đã tồn tại trước khi tạo mới
- Tách rõ bước nào có thể lặp và bước nào không
3. Không giới hạn kích thước và thời gian chạy
Một job xử lý quá nhiều dữ liệu có thể chiếm worker rất lâu. Thay vì tạo một job “xử lý toàn bộ 1 triệu bản ghi”, nên chia nhỏ thành nhiều job theo lô. Điều này giúp retry dễ hơn, theo dõi rõ hơn và tránh làm nghẽn hàng đợi.
4. Không thông báo trạng thái cho người dùng
Nếu người dùng bấm “Xuất báo cáo” rồi không thấy gì xảy ra, họ có thể bấm lại nhiều lần. Hãy hiển thị trạng thái rõ ràng: đang chờ, đang xử lý, hoàn tất, thất bại. Nếu thất bại, nên có hướng dẫn như “Thử lại” hoặc “Liên hệ hỗ trợ”.
Lời khuyên có thể áp dụng
- Đặt tên job rõ nghĩa, ví dụ SendWelcomeEmailJob, GenerateMonthlyReportJob.
- Lưu thông tin tối thiểu cần thiết trong job, thường là ID bản ghi thay vì nhét cả object lớn vào queue.
- Có retry nhưng đừng retry vô hạn. Với lỗi do dữ liệu sai, retry nhiều lần chỉ làm tốn tài nguyên.
- Phân loại queue theo mức độ ưu tiên. Job gửi OTP không nên xếp chung sau hàng nghìn job xuất báo cáo nặng.
- Ghi log đủ để biết job nào chạy, chạy bao lâu, thất bại vì sao.
- Theo dõi số lượng job đang chờ. Queue tăng liên tục là dấu hiệu worker không xử lý kịp hoặc có lỗi hệ thống.
Kết luận
Background job không chỉ là kỹ thuật “cho đỡ chậm”, mà là một cách thiết kế luồng xử lý hợp lý hơn. Khi dùng đúng, nó giúp web/app phản hồi nhanh, dễ mở rộng và chịu lỗi tốt hơn. Nhưng để an toàn, cần nghĩ trước về retry, idempotency, trạng thái người dùng và khả năng quan sát hệ thống. Một hàng đợi tốt không biến lỗi thành biến mất; nó giúp ta xử lý công việc phía sau một cách có kiểm soát.