Chương 23. Vì Sao Không Xử Lý Việc Lâu Trong Request?
Ở phần trước, ta đã học cách chọn phương thức giao tiếp:
- Cần kết quả ngay: HTTP/API.
- Việc lâu: queue.
- Nhiều bên cần biết: Pub/Sub.
- Frontend chờ kết quả: polling/SSE/WebSocket.
- Hệ thống ngoài báo lại: webhook.
Từ chương này, ta đi sâu vào một mảng rất thực tế:
> Worker, job nền và các việc xử lý lâu.
Đây là phần cực kỳ quan trọng trong hệ thống thật.
Vì rất nhiều hệ thống không chậm do thuật toán quá phức tạp.
Chúng chậm vì bắt request người dùng gánh quá nhiều việc:
- Gửi email.
- Gọi AI API.
- Tạo báo cáo.
- Resize ảnh.
- Render video.
- Import Excel.
- Export dữ liệu.
- Đồng bộ CRM.
- Gọi payment provider.
- Tính analytics.
Ban đầu làm vậy rất tiện:
User gửi request
Backend làm mọi thứ
Backend trả kết quả
Nhưng khi việc đó mất 10 giây, 30 giây, 90 giây hoặc vài phút, cách này bắt đầu gây hại.
Thông điệp chính của chương:
> Request nên làm việc cần thiết để xác nhận yêu cầu và trả lời nhanh. Việc lâu nên chuyển thành job nền.
---
23.1. Request là gì?
Request là một lần client gọi server và chờ response.
Ví dụ:
Frontend -> POST /submissions
Backend -> 202 Accepted
Hoặc:
Frontend -> GET /orders/123
Backend -> order detail
Trong trải nghiệm người dùng, request giống như người dùng hỏi:
Tôi bấm nút rồi, hệ thống phản hồi cho tôi đi.
Nếu server phản hồi nhanh, người dùng thấy hệ thống mượt.
Nếu server giữ request quá lâu, người dùng thấy:
- Trang quay mãi.
- Không biết có thành công không.
- Có thể bấm lại.
- Có thể reload.
- Có thể đóng tab.
- Có thể nghĩ hệ thống lỗi.
Request là khoảnh khắc người dùng đang nhìn vào hệ thống.
Đừng bắt họ chờ những việc không cần chờ.
---
23.2. Việc lâu là gì?
Việc lâu không có một con số tuyệt đối.
Nhưng trong web app, nếu một thao tác mất nhiều giây, ta đã phải bắt đầu suy nghĩ.
Ví dụ:
Nhanh:
- Validate input
- Lưu record đơn giản
- Lấy dữ liệu từ database có index
- Tạo job
Có thể lâu:
- Gọi API bên ngoài
- Gửi email
- Tạo PDF
- Export Excel lớn
- Import file nhiều dòng
- Resize ảnh lớn
- Render video
- Chấm bài bằng AI
- Crawl web
- Đồng bộ hệ thống ngoài
Không phải cứ hơn 1 giây là bắt buộc đưa vào queue.
Nhưng nếu việc có thể kéo dài, có thể timeout, có thể retry, hoặc không cần người dùng chờ kết quả cuối cùng, nó là ứng viên rất tốt cho job nền.
---
23.3. Ví dụ AI Judge: 90 giây trong request
Giả sử học viên nộp bài.
Backend gọi Gemini API để chấm.
Mỗi bài mất khoảng 1 phút 30 giây.
Thiết kế đơn giản nhất:
Frontend -> POST /submit
Backend -> gọi Gemini
Backend -> chờ 90 giây
Backend -> lưu điểm
Backend -> trả điểm
Nhìn thì dễ hiểu.
Nhưng có nhiều vấn đề:
- Request giữ quá lâu.
- Web server bị chiếm trong lúc chờ.
- Load balancer/proxy có thể timeout.
- Browser có thể timeout.
- User không biết đang xảy ra gì.
- User có thể bấm nộp lại.
- Nếu Gemini lỗi, retry trong request rất khó.
- Nếu nhiều học viên nộp cùng lúc, web server bị nghẽn.
Thiết kế tốt hơn:
Frontend -> POST /submissions
Backend:
lưu Submission
tạo GradingJob = PENDING
enqueue RunGradingJob
trả job_id
Worker:
lấy job
gọi Gemini
lưu kết quả
Frontend:
polling/SSE để xem kết quả
Người dùng không bị bắt chờ 90 giây trong một request.
Hệ thống cũng dễ kiểm soát concurrency và retry hơn.
---
23.4. Vấn đề không chỉ là timeout
Nhiều người nghĩ:
> Nếu request timeout sau 30 giây, ta chỉ cần tăng timeout lên 120 giây.
Đôi khi làm vậy có thể chữa cháy.
Nhưng nó không giải quyết gốc.
Vấn đề không chỉ là request bị timeout.
Vấn đề là:
- Request dài chiếm tài nguyên.
- User experience tệ.
- Retry khó an toàn.
- Lỗi khó phục hồi.
- Scale web server khó hơn.
- Một việc phụ có thể làm hỏng luồng chính.
- Không có trạng thái job rõ ràng.
Tăng timeout giống như cho người dùng đứng chờ lâu hơn ở quầy.
Nhưng nếu việc là "làm bánh trong bếp", giải pháp đúng là nhận đơn rồi đưa phiếu cho bếp.
Không phải bắt khách đứng ở quầy 30 phút.
---
23.5. Web server nên làm gì?
Web server nên tập trung vào việc xử lý request nhanh:
- Nhận request.
- Xác thực user.
- Validate input.
- Kiểm tra quyền.
- Lưu thay đổi cần thiết.
- Tạo job nếu cần.
- Trả response.
Ví dụ:
POST /submissions
Backend nên:
1. Kiểm tra user có quyền nộp bài.
2. Lưu submission.
3. Tạo grading job.
4. Đưa job vào queue.
5. Trả job_id.
Không nên:
1. Lưu submission.
2. Gọi Gemini 90 giây.
3. Parse result.
4. Gửi email.
5. Cập nhật analytics.
6. Mở bài học tiếp theo.
7. Trả response.
Những việc phía sau nên tách ra.
---
23.6. Worker nên làm gì?
Worker xử lý việc nền.
Ví dụ:
RunGradingJob
SendEmail
GenerateReport
ResizeImage
SyncToCrm
ImportCsv
Worker không phục vụ request người dùng trực tiếp.
Nó lấy job từ queue hoặc bảng job, rồi xử lý.
Điểm hay:
- Worker có thể chạy lâu hơn web request.
- Worker có thể retry.
- Worker có concurrency riêng.
- Worker có queue riêng.
- Worker có thể scale riêng.
- Worker có thể tạm dừng hoặc tăng số lượng.
Tức là ta tách:
Trải nghiệm nhận yêu cầu
khỏi:
Năng lực xử lý việc nặng
Đây là một tách biệt rất quan trọng.
---
23.7. Vì sao user không nên chờ tác vụ dài?
Người dùng không chỉ quan tâm việc cuối cùng có xong không.
Họ còn quan tâm hệ thống có phản hồi không.
Nếu bấm nút và chờ 90 giây, họ sẽ hỏi:
- Có bị treo không?
- Có nộp được chưa?
- Có nên bấm lại không?
- Nếu đóng tab thì có mất không?
- Nếu mạng rớt thì sao?
Cách tốt hơn là phản hồi nhanh:
Đã nhận bài. Hệ thống đang chấm.
Sau đó hiển thị trạng thái:
Đang chờ chấm
Đang chấm
Đã chấm xong
Chấm thất bại, đang thử lại
Người dùng có cảm giác hệ thống kiểm soát được việc đang làm.
Điều này tốt hơn rất nhiều so với một spinner quay vô tận.
---
23.8. Vì sao web server không nên bị giữ bởi việc nền?
Web server có số lượng worker/connection hữu hạn.
Ví dụ:
Web server có 8 worker.
Mỗi request chấm bài giữ 90 giây.
Nếu 8 học viên nộp bài cùng lúc, cả 8 worker bị chiếm.
Trong lúc đó, request khác có thể bị chờ:
- Login.
- Lấy danh sách bài học.
- Xem kết quả cũ.
- Admin thao tác.
Tức là một loại request lâu làm nghẽn cả web app.
Nếu chuyển chấm bài sang worker nền:
Web worker trả nhanh.
AI worker xử lý riêng.
Web app vẫn phản hồi tốt cho các request khác.
Đây là lý do tách web server và worker.
---
23.9. Ví dụ email: gửi ngay trong request có sao không?
User đăng ký tài khoản.
Ta muốn gửi email chào mừng.
Cách đơn giản:
POST /register
-> tạo user
-> gọi email provider
-> chờ provider trả lời
-> trả response
Nếu email provider chậm, đăng ký chậm.
Nếu email provider lỗi, đăng ký có nên thất bại không?
Thường là không.
Cách tốt hơn:
POST /register
-> tạo user
-> enqueue SendWelcomeEmail
-> trả response
Worker
-> gửi email
-> retry nếu lỗi tạm thời
Email là ví dụ kinh điển của job nền.
Đặc biệt với:
- Email xác nhận.
- Email thông báo.
- Email marketing.
- SMS.
- Push notification.
Nhưng nhớ:
Email reset password có thể cần ưu tiên cao hơn email marketing.
Đừng để chúng chung hàng nếu có nguy cơ nghẽn.
---
23.10. Ví dụ report: tạo báo cáo trong request
Admin muốn export báo cáo 1 triệu dòng.
Cách xấu:
GET /reports/export
-> query rất nhiều dữ liệu
-> tạo Excel
-> upload file
-> trả file
Request có thể mất vài phút.
Nếu timeout giữa chừng, admin không biết file có được tạo chưa.
Cách tốt hơn:
POST /report-jobs
-> tạo ReportJob = PENDING
-> enqueue GenerateReport
-> trả report_job_id
Worker
-> tạo file
-> upload storage
-> ReportJob = SUCCEEDED
Frontend
-> polling/SSE xem trạng thái
-> tải file khi xong
Thiết kế này có thêm chút công sức, nhưng vận hành tốt hơn nhiều.
---
23.11. Ví dụ import file
User upload file CSV có 100.000 dòng.
Không nên parse và ghi toàn bộ trong request nếu việc có thể lâu.
Thiết kế tốt hơn:
POST /imports
-> lưu file vào object storage
-> tạo ImportJob = PENDING
-> enqueue ProcessImport
-> trả import_job_id
Worker
-> đọc file
-> validate từng dòng
-> ghi dữ liệu
-> lưu lỗi từng dòng nếu có
-> cập nhật progress
Frontend hiển thị:
Đã nhận file
Đang xử lý 12%
Có 231 dòng lỗi
Import hoàn tất
Import/export là nhóm việc rất nên dùng job nền.
---
23.12. Ví dụ video và ảnh
User upload video.
Việc cần làm:
- Scan virus.
- Transcode nhiều độ phân giải.
- Tạo thumbnail.
- Upload lên storage/CDN.
- Cập nhật trạng thái.
Không nên làm hết trong request upload.
Request upload nên:
nhận file hoặc tạo signed upload URL
tạo media record
enqueue processing job
trả media_id
Worker xử lý video sau.
Với ảnh cũng vậy:
- Resize nhiều size.
- Tối ưu dung lượng.
- Tạo thumbnail.
Những việc này có thể chạy nền.
User không cần chờ tất cả size được tạo xong mới thấy upload đã được nhận.
---
23.13. Request dài làm retry khó hơn
Giả sử request gọi AI 90 giây.
Ở giây thứ 85, mạng giữa browser và backend bị đứt.
Frontend không nhận được response.
Nhưng backend có thể vẫn đang xử lý.
User bấm lại.
Điều gì xảy ra?
- Có tạo submission thứ hai không?
- Có chấm hai lần không?
- Có trừ quota hai lần không?
- Có lưu hai kết quả không?
Nếu không có job id và idempotency, rất dễ lỗi.
Khi dùng job nền:
POST /submissions
-> trả submission_id/job_id nhanh
Nếu frontend mất mạng, nó có thể gọi lại hoặc query trạng thái theo idempotency key.
Thiết kế rõ hơn nhiều.
---
23.14. Request dài làm lỗi khó phục hồi hơn
Nếu việc lâu chạy trong request và lỗi giữa chừng:
Tạo order xong
Gửi email xong
Gọi CRM lỗi
Analytics chưa ghi
Response chưa trả
Hệ thống đang ở trạng thái lưng chừng.
User thấy lỗi, nhưng một phần việc đã xảy ra.
Nếu tách thành job và event:
Order đã tạo là sự thật chính.
Email retry riêng.
CRM retry riêng.
Analytics retry riêng.
Lỗi phụ không làm luồng chính mập mờ.
Quan trọng là phải thiết kế trạng thái rõ:
- Việc chính đã thành công chưa?
- Việc phụ nào đang chờ?
- Việc phụ nào thất bại?
- Có retry không?
---
23.15. Request dài làm deploy khó hơn
Khi request có thể chạy vài phút, deploy trở nên rủi ro hơn.
Nếu server bị restart trong lúc request đang chạy:
- Request bị cắt.
- Việc có thể dang dở.
- User không biết trạng thái.
- Không có retry tự nhiên.
Với worker job:
- Worker có graceful shutdown.
- Job có timeout.
- Job có retry.
- Job có status.
- Job kẹt có thể phát hiện.
Điều này không tự động có.
Nhưng job system cho ta chỗ để thiết kế nó.
Request HTTP dài thường không phải chỗ tốt để quản lý lifecycle của việc lâu.
---
23.16. Request dài làm scale sai chỗ
Nếu chấm AI nằm trong web server, muốn xử lý nhiều bài hơn bạn phải scale web server.
Nhưng web server cũng phục vụ:
- Login.
- Xem bài học.
- Xem điểm.
- Admin.
- API khác.
Scale web server chỉ để tăng AI grading là không gọn.
Tốt hơn:
web servers: scale theo request web
ai workers: scale theo queue chấm bài
email workers: scale theo email queue
report workers: scale theo report queue
Mỗi loại workload có cách scale riêng.
Đây là lý do worker pool riêng rất quan trọng.
---
23.17. Workload khác nhau cần cách chạy khác nhau
Không phải việc lâu nào cũng giống nhau.
I/O-bound
Chờ mạng hoặc hệ thống ngoài:
- Gọi Gemini API.
- Gửi email.
- Gọi payment provider.
- Upload file.
Cần concurrency cao hơn, vì phần lớn thời gian là chờ.
CPU-bound
Ăn CPU thật:
- Render video.
- Nén ảnh lớn.
- Xử lý dữ liệu nặng.
- Tính toán phức tạp.
Cần process/machine mạnh hơn, không chỉ tăng async concurrency.
Database-bound
Nghẽn database:
- Export dữ liệu lớn.
- Query nặng.
- Import ghi nhiều dòng.
Cần tối ưu query, batch, index, giới hạn concurrency để không làm DB chết.
Tách job nền giúp ta cấu hình mỗi loại worker khác nhau.
---
23.18. Khi nào vẫn xử lý trong request được?
Không phải cái gì cũng phải đưa vào queue.
Xử lý trong request ổn khi:
- Việc ngắn.
- Người dùng cần kết quả ngay.
- Ít rủi ro timeout.
- Không cần retry phức tạp.
- Không gọi external API chậm.
- Không chiếm tài nguyên lớn.
Ví dụ:
- Validate form.
- Tạo record nhỏ.
- Lấy dữ liệu đã index.
- Cập nhật profile đơn giản.
- Tính giá đơn giản.
- Đổi mật khẩu.
Đưa mọi thứ vào queue cũng có thể làm hệ thống phức tạp không cần thiết.
Nguyên tắc không phải:
> Cứ việc gì cũng queue.
Nguyên tắc là:
> Việc nào làm request lâu, dễ lỗi, cần retry, hoặc không cần người dùng chờ thì nên cân nhắc job nền.
---
23.19. Khi nào bắt buộc phải trả kết quả ngay?
Có những việc không thể chỉ trả "đã nhận".
Ví dụ:
- Login.
- Kiểm tra quyền truy cập.
- Lấy dữ liệu để render màn hình.
- Validate mã OTP.
- Tạo payment URL để user redirect ngay.
- Kiểm tra giá cuối cùng tại checkout.
Những việc này nằm trên đường tương tác trực tiếp.
Nhưng ngay cả trong các luồng đó, vẫn có thể tách phần phụ.
Ví dụ checkout:
Cần tạo order/payment_url ngay.
Không cần gửi email/CRM/analytics ngay.
Vậy request làm phần cần thiết, phần còn lại đi nền.
---
23.20. 202 Accepted là gì?
Khi request chỉ nhận việc và xử lý sau, response thường có thể là:
202 Accepted
Nghĩa là:
> Hệ thống đã nhận yêu cầu, nhưng kết quả cuối chưa có ngay.
Ví dụ:
POST /submissions
Response:
202 Accepted
{
"submission_id": "sub_123",
"grading_job_id": "job_456",
"status": "PENDING"
}
Hoặc:
POST /report-jobs
Response:
202 Accepted
{
"report_job_id": "report_789",
"status": "PENDING"
}
202 giúp API nói thật:
Tôi đã nhận việc, chưa xong.
Tốt hơn việc giữ request đến khi xong hoặc trả 200 OK mơ hồ.
---
23.21. Cần có trạng thái job
Nếu xử lý nền, người dùng cần biết job đang ở đâu.
Tối thiểu:
PENDING
RUNNING
SUCCEEDED
FAILED
Có thể thêm:
RETRYING
CANCELLED
EXPIRED
Ví dụ:
GradingJob
- id
- status
- attempt_count
- created_at
- started_at
- completed_at
- failed_reason
- result
Không có job status, queue trở thành hộp đen.
Người dùng hỏi:
Bài của tôi đâu?
Team không trả lời được.
---
23.22. Cần có cách đọc kết quả
Sau khi trả job_id, frontend cần endpoint để đọc trạng thái.
Ví dụ:
GET /grading-jobs/job_456
Response:
{
"job_id": "job_456",
"status": "RUNNING"
}
Khi xong:
{
"job_id": "job_456",
"status": "SUCCEEDED",
"score": 8.5,
"feedback": "..."
}
Có thể dùng:
- Polling.
- SSE.
- WebSocket signal.
- Email/notification.
Nhưng dù dùng gì, vẫn nên có API đọc trạng thái hiện tại.
Vì client có thể refresh, mất mạng, hoặc bỏ lỡ realtime event.
---
23.23. Cần có timeout
Job nền cũng không được chạy mãi.
Nếu AI API treo, worker không nên bị giữ vô hạn.
Nếu import file bị kẹt, job không nên RUNNING mãi.
Cần timeout theo loại job:
Send email: vài giây đến vài chục giây
AI grading: có thể 2-3 phút
Report lớn: tùy kích thước
Video processing: dài hơn, cần heartbeat/progress
Timeout là dây an toàn.
Không có timeout, một lỗi lạ có thể giữ worker mãi.
---
23.24. Cần có retry
Việc nền thường cần retry.
Ví dụ:
- Email provider timeout.
- Gemini API trả 503.
- Payment gateway tạm lỗi.
- Database deadlock.
- Network chập chờn.
Nhưng retry phải có kiểm soát:
- Retry lỗi tạm thời.
- Không retry lỗi chắc chắn sai input.
- Có số lần tối đa.
- Có backoff.
- Có jitter.
- Có DLQ hoặc trạng thái failed.
Retry sai có thể làm hỏng dữ liệu.
Ví dụ:
- Hoàn tiền hai lần.
- Gửi email 20 lần.
- Chấm bài 3 lần và lưu kết quả khác nhau.
Vì vậy retry luôn đi cùng idempotency.
---
23.25. Cần có idempotency
Idempotency nghĩa là:
> Gọi lại cùng một việc nhiều lần vẫn không tạo kết quả sai hoặc trùng.
Trong job nền, điều này rất quan trọng vì:
- Worker có thể retry.
- Queue có thể deliver lại.
- User có thể bấm lại.
- Webhook có thể gửi lại.
Ví dụ:
RunGradingJob(job_123)
Nếu job đã SUCCEEDED, retry không nên chấm lại và ghi đè bừa.
Ví dụ:
SendEmail(email_456)
Nếu email đã gửi, retry cần biết có gửi lại hay bỏ qua.
Ví dụ:
RefundPayment(refund_789)
Retry tuyệt đối không được hoàn tiền hai lần.
Job nền không idempotent là một quả mìn.
---
23.26. Cần có quan sát
Khi việc chạy nền, nó không còn nằm trước mắt người dùng.
Vì vậy hệ thống phải quan sát được.
Cần biết:
- Có bao nhiêu job đang chờ?
- Job chờ bao lâu?
- Job chạy bao lâu?
- Tỉ lệ lỗi là bao nhiêu?
- Retry bao nhiêu lần?
- Có job nào kẹt
RUNNINGkhông? - Worker có đang sống không?
- Queue có đang dài ra không?
Ví dụ AI Judge:
queue_wait_time = 2 phút
processing_time = 90 giây
total_time = 3 phút 30 giây
Nếu chỉ biết "chấm bài chậm", chưa đủ.
Phải biết chậm vì chờ queue hay vì gọi Gemini chậm.
---
23.27. Cần có thông báo lỗi rõ cho người dùng
Nếu job thất bại, user cần biết.
Không nên để trạng thái:
Đang xử lý...
mãi mãi.
Ví dụ:
Chấm bài thất bại. Hệ thống sẽ thử lại.
Hoặc:
Không thể chấm bài lúc này. Vui lòng thử lại sau.
Tùy nghiệp vụ, có thể:
- Tự retry.
- Cho user bấm retry.
- Báo admin.
- Đưa vào hàng xử lý thủ công.
Job nền cần trạng thái thất bại rõ ràng.
Không có lỗi rõ, người dùng chỉ thấy hệ thống im lặng.
---
23.28. Việc phụ không nên làm hỏng việc chính
Một nguyên tắc rất thực tế:
> Nếu việc chính đã thành công, việc phụ lỗi không nên làm việc chính thất bại.
Ví dụ:
User đặt hàng thành công.
Email xác nhận lỗi.
Đơn hàng có nên bị hủy không?
Thường là không.
Ví dụ:
Chấm bài xong và lưu điểm thành công.
Analytics lỗi.
Kết quả chấm có nên bị mất không?
Không.
Vì vậy việc phụ nên tách:
- Email.
- Analytics.
- CRM.
- Notification.
- Search index.
Nhưng có những việc không phải phụ.
Ví dụ:
Trừ tiền khi checkout.
Không thể coi là phụ nếu đơn cần thanh toán trước.
Phải hiểu nghiệp vụ để tách đúng.
---
23.29. "Làm sau" không có nghĩa là "không quan trọng"
Một việc chạy nền vẫn có thể rất quan trọng.
Ví dụ:
- Ghi nhận doanh thu.
- Cấp quyền truy cập sau thanh toán.
- Hoàn tiền.
- Chấm bài thi.
- Gửi reset password email.
Chạy nền chỉ nói rằng:
> Không cần giữ request người dùng đến khi xong.
Không có nghĩa:
> Mất cũng được.
Việc nền quan trọng vẫn cần:
- Durable queue.
- Outbox.
- Retry.
- DLQ.
- Idempotency.
- Monitoring.
- Reconciliation.
Đừng nhầm "async" với "không cần chắc".
---
23.30. Khi nào chỉ cần background task đơn giản?
Một số framework có background task nhẹ.
Ví dụ:
Sau response, chạy một hàm gửi email.
Cách này có thể ổn cho việc rất nhỏ, không quan trọng lắm.
Nhưng cần cẩn thận:
- Process chết thì task mất.
- Không có retry tốt.
- Không có queue length.
- Không có DLQ.
- Không scale worker riêng.
Với việc quan trọng, nên dùng job/queue thật.
Ví dụ:
Không nên xử lý payment confirmation quan trọng bằng background task tạm bợ trong web process.
Nên có queue/job status/outbox rõ ràng.
---
23.31. Một mẫu thiết kế thực dụng
Với việc lâu, mẫu thường là:
1. Request đến.
2. Validate và kiểm tra quyền.
3. Lưu record chính.
4. Tạo job record.
5. Enqueue job hoặc ghi outbox.
6. Trả 202 + job_id.
7. Worker xử lý.
8. Worker cập nhật job status.
9. Frontend đọc trạng thái/kết quả.
10. Event phát ra cho các bên khác nếu cần.
Ví dụ:
POST /submissions
-> Submission
-> GradingJob
-> Queue
-> 202 Accepted
Đây là một mẫu rất đáng nhớ.
Nó xuất hiện lại trong rất nhiều hệ thống.
---
23.32. Bảng quyết định nhanh
| Tình huống | Xử lý trong request? | Job nền? | |---|---|---| | Login | Có | Không | | Lấy danh sách bài học | Có | Không | | Lưu bài nộp | Có | Một phần | | Chấm bài AI 90 giây | Không nên | Có | | Gửi email xác nhận | Không nên nếu không bắt buộc | Có | | Tạo báo cáo lớn | Không | Có | | Export Excel lớn | Không | Có | | Resize ảnh/video | Không | Có | | Gọi CRM | Không nên | Có | | Tính analytics | Không nên | Có | | Tạo payment URL | Thường có | Phần hậu xử lý có thể nền | | Payment webhook nhiều phản ứng | Nhận nhanh | Xử lý sau |
---
23.33. Những lỗi phổ biến
Lỗi 1: Tăng timeout thay vì tách job
Timeout dài hơn không giải quyết tài nguyên bị giữ và retry khó.
Lỗi 2: Chạy việc lâu trong web worker
Một vài request lâu có thể làm nghẽn toàn web app.
Lỗi 3: Không có job status
User và team không biết việc đang chờ, đang chạy hay đã chết.
Lỗi 4: Queue nhưng không idempotent
Retry làm dữ liệu trùng hoặc sai.
Lỗi 5: Việc phụ làm hỏng việc chính
Email/analytics lỗi làm request đặt hàng fail.
Lỗi 6: Không đo queue wait time
Không biết job chậm vì chờ queue hay xử lý chậm.
Lỗi 7: Dùng background task tạm cho việc quan trọng
Process chết là mất việc.
Lỗi 8: Không có API đọc trạng thái
Frontend không có cách đồng bộ lại khi mất realtime event.
---
23.34. Checklist trước khi đưa việc lâu vào request
Trước khi xử lý một việc trong request, hãy hỏi:
- Việc này thường mất bao lâu?
- Có thể chậm bất thường không?
- Có gọi API bên ngoài không?
- Người dùng có cần kết quả cuối ngay không?
- Nếu request timeout thì trạng thái ra sao?
- Nếu user bấm lại thì có tạo trùng không?
- Nếu server restart giữa chừng thì sao?
- Có cần retry không?
- Có cần idempotency không?
- Có cần progress/status không?
- Việc này có làm nghẽn web worker không?
- Có thể tách phần chính và phần phụ không?
Nếu nhiều câu trả lời làm bạn thấy bất an, hãy cân nhắc job nền.
---
23.35. Tóm tắt bằng AI Judge
Không nên:
Frontend
-> POST /submit
-> Backend gọi Gemini và chờ 90 giây
-> Backend gửi email, cập nhật progress, analytics
-> trả kết quả
Nên:
Frontend
-> POST /submissions
-> Backend lưu submission
-> tạo GradingJob(PENDING)
-> enqueue RunGradingJob
-> trả 202 + job_id
Worker
-> chuyển RUNNING
-> gọi Gemini
-> lưu score/feedback
-> chuyển SUCCEEDED
-> phát GradingCompleted
Frontend
-> polling/SSE xem trạng thái
Điểm quan trọng:
- Request trả nhanh.
- Worker xử lý việc lâu.
- Job có trạng thái.
- Retry/idempotency rõ.
- Frontend có cách xem kết quả.
- Các phản ứng phụ đi qua event/queue.
---
23.36. Kết luận của chương
Không xử lý việc lâu trong request không phải vì ta thích làm kiến trúc phức tạp.
Mà vì request dài gây rất nhiều vấn đề:
- Người dùng phải chờ.
- Web server bị giữ.
- Timeout dễ xảy ra.
- Retry khó an toàn.
- Lỗi khó phục hồi.
- Scale sai chỗ.
- Deploy khó hơn.
- Không có trạng thái rõ.
Job nền giúp ta tách:
Nhận yêu cầu nhanh
khỏi:
Xử lý việc lâu có kiểm soát
Thông điệp quan trọng nhất:
> Request nên xác nhận rằng hệ thống đã nhận việc. Worker mới là nơi phù hợp để làm việc lâu, dễ lỗi, cần retry, hoặc không cần người dùng chờ.
Ở chương tiếp theo, ta sẽ nói rõ hơn worker là gì, worker khác web server ở đâu, và khi nào cần nhiều queue, nhiều nhóm worker khác nhau.