1. 11:04 07/08/2026
Trong phát triển web/app, nhiều lỗi không đến từ code sai mà đến từ những tình huống “lưng chừng”: API bên thứ ba phản hồi chậm, mạng người dùng chập chờn, service nội bộ mất vài giây mới trả lời, hoặc request đã xử lý xong nhưng client không nhận được kết quả. Nếu không thiết kế timeout và retry cẩn thận, hệ thống có thể vừa chậm, vừa tạo ra lỗi dây chuyền, thậm chí nhân đôi hành động như tạo đơn, gửi email hoặc trừ tồn kho.
Timeout là gì và vì sao không nên để mặc định?
Timeout là giới hạn thời gian chờ một thao tác hoàn tất. Ví dụ: frontend gọi API lấy danh sách sản phẩm, backend gọi service thanh toán, hoặc server kết nối database.
Một lỗi phổ biến là không cấu hình timeout rõ ràng, dẫn đến request treo quá lâu. Người dùng nhìn thấy màn hình loading mãi, còn server thì giữ tài nguyên cho các kết nối không có hy vọng hoàn tất. Khi lượng request tăng, những request “treo” này có thể làm cạn thread, connection pool hoặc memory.
Lời khuyên thực tế:
- Với thao tác người dùng đang chờ trực tiếp, nên có timeout tương đối ngắn và thông báo rõ ràng.
- Với tác vụ nặng, cân nhắc đưa vào background job thay vì bắt người dùng chờ.
- Timeout giữa các service nội bộ nên nhỏ hơn timeout tổng của request bên ngoài, để còn thời gian xử lý lỗi và trả phản hồi hợp lý.
- Đừng dùng một timeout duy nhất cho mọi thứ. Gọi search, gọi thanh toán, upload file và gửi email có đặc điểm rất khác nhau.
Retry không phải lúc nào cũng tốt
Retry nghĩa là thử lại khi thao tác thất bại. Nghe có vẻ đơn giản, nhưng retry sai cách có thể khiến hệ thống tệ hơn. Nếu một service đang quá tải mà tất cả client đều lập tức gọi lại 3 lần, tải thực tế có thể tăng gấp nhiều lần.
Nên retry với các lỗi có khả năng tạm thời, ví dụ:
- Mạng chập chờn.
- Request timeout.
- Service trả lỗi 502, 503, 504.
- Kết nối tạm thời bị reset.
Không nên retry một cách mù quáng với:
- Lỗi validate dữ liệu, ví dụ thiếu email hoặc sai định dạng.
- Lỗi quyền truy cập như 401, 403.
- Lỗi nghiệp vụ rõ ràng như số dư không đủ.
- Các thao tác không an toàn nếu chưa có cơ chế idempotency.
Ví dụ thực tế: retry khi tạo đơn hàng
Giả sử app gọi API tạo đơn hàng. Client gửi request, server đã tạo đơn thành công nhưng phản hồi bị timeout trên đường về. Nếu client tự động retry mà không có khóa idempotency, server có thể tạo thêm một đơn giống hệt.
Cách xử lý tốt hơn:
- Client gửi kèm một idempotency key cho thao tác tạo đơn.
- Server lưu lại kết quả ứng với key đó trong một khoảng thời gian phù hợp.
- Nếu client retry với cùng key, server trả lại kết quả cũ thay vì tạo đơn mới.
Như vậy retry trở nên an toàn hơn, đặc biệt với các thao tác tạo mới, thanh toán, cộng điểm, gửi thông báo hoặc thay đổi dữ liệu quan trọng.
Dùng exponential backoff và jitter
Thay vì retry ngay lập tức theo kiểu: thử lần 1, thất bại, thử lại ngay, thất bại, thử lại ngay, bạn nên tăng dần thời gian chờ giữa các lần thử. Đây gọi là exponential backoff.
Ví dụ:
- Lần 1 thất bại: chờ 500ms.
- Lần 2 thất bại: chờ 1s.
- Lần 3 thất bại: chờ 2s.
Jitter là thêm một độ lệch ngẫu nhiên nhỏ vào thời gian chờ. Mục đích là tránh việc nhiều client cùng retry chính xác tại một thời điểm, gây ra “làn sóng” request mới.
Ví dụ thay vì tất cả cùng chờ đúng 1 giây, mỗi client có thể chờ trong khoảng 800ms đến 1200ms.
Frontend cũng cần timeout và retry hợp lý
Không chỉ backend mới cần quan tâm. Ở frontend, nếu người dùng bấm “Lưu” và mạng yếu, giao diện cần có phản hồi phù hợp.
Một số gợi ý:
- Hiển thị trạng thái đang xử lý, tránh để người dùng bấm nhiều lần.
- Nếu timeout, nói rõ: “Kết nối đang chậm, vui lòng thử lại” thay vì chỉ báo lỗi chung chung.
- Với thao tác an toàn như tải lại danh sách, có thể cho phép retry.
- Với thao tác quan trọng như thanh toán, không nên chỉ đơn giản hiện nút “Thử lại” nếu chưa kiểm tra trạng thái giao dịch.
Backend cần phân biệt timeout ở nhiều lớp
Một request từ người dùng có thể đi qua nhiều bước: API gateway, backend chính, database, cache, service bên thứ ba. Nếu mỗi lớp đều chờ quá lâu, tổng thời gian có thể vượt xa mức chấp nhận được.
Nên xác định “ngân sách thời gian” cho request. Ví dụ một API cần trả lời trong khoảng 3 giây, bạn có thể phân bổ:
- 300ms cho xác thực và kiểm tra dữ liệu.
- 800ms cho truy vấn database.
- 1000ms cho gọi service ngoài.
- Phần còn lại để tổng hợp dữ liệu và xử lý lỗi.
Con số cụ thể tùy hệ thống, nhưng tư duy quan trọng là: mỗi bước phải biết mình được phép chờ bao lâu.
Ghi log đủ để hiểu retry đang xảy ra
Khi có retry, log và metric rất quan trọng. Nếu chỉ nhìn thấy request cuối cùng thành công, bạn có thể không biết trước đó hệ thống đã thất bại 2 lần. Điều này che giấu vấn đề hiệu năng hoặc độ ổn định.
Nên ghi nhận:
- Request đã retry bao nhiêu lần.
- Lý do retry là timeout, lỗi mạng hay mã lỗi từ server.
- Tổng thời gian xử lý sau tất cả lần thử.
- Service nào thường xuyên gây timeout.
Nhưng cũng cần tránh log quá nhiều dữ liệu nhạy cảm như token, mật khẩu, thông tin thanh toán hoặc dữ liệu cá nhân không cần thiết.
Kết luận
Timeout và retry là hai cơ chế nhỏ nhưng ảnh hưởng lớn đến trải nghiệm người dùng và độ ổn định của hệ thống. Timeout giúp hệ thống không chờ vô hạn. Retry giúp vượt qua lỗi tạm thời. Nhưng nếu dùng thiếu kiểm soát, chúng có thể làm tăng tải, che giấu lỗi thật hoặc gây trùng lặp dữ liệu.
Một thiết kế tốt nên trả lời được các câu hỏi: thao tác này được chờ tối đa bao lâu, lỗi nào được thử lại, thử lại mấy lần, khoảng cách giữa các lần là bao nhiêu, và retry có an toàn với dữ liệu hay không. Khi trả lời rõ những câu hỏi đó, web/app của bạn sẽ ổn định hơn nhiều trong các tình huống mạng và service không hoàn hảo ngoài thực tế.