Các nhà phát triển ứng dụng chụp ảnh sự kiện hiện đã có một danh sách kiểm tra cụ thể để duy trì việc tải lên từ trình duyệt trong các địa điểm đông đúc. Một người dùng có thể chuyển đổi giữa Wi-Fi và mạng di động trong khi hàng chục thiết bị khác đang tranh giành cùng một điểm phát sóng. Hướng dẫn này chỉ ra cách ngăn chặn việc một bức ảnh bị biến mất sau khi thông báo "tải lên hoàn tất" xuất hiện, ngay cả khi khách mời khóa điện thoại hoặc mạng gặp sự cố chập chờn.
Tại sao việc tải lên thông thường lại thất bại tại các đám cưới và lễ hội
Trong văn phòng, một máy tính xách tay kết nối với đường truyền Ethernet ổn định và một người dùng duy nhất nhấn "gửi". Tại một tiệc cưới hay lễ hội âm nhạc, cùng một hành động đó có thể gây ra một loạt vấn đề: khách mời đi từ sảnh lễ cưới ra bãi đậu xe, bộ định tuyến bị quá tải bởi hàng trăm điện thoại, hoặc điện thoại mất Wi-Fi và chuyển sang mạng di động. Trình duyệt có thể đã truyền mọi byte dữ liệu đến máy chủ, nhưng máy chủ vẫn chưa lưu tệp vào bộ nhớ. Nếu giao diện người dùng (UI) thông báo thành công ngay khi thanh tiến trình đạt 100%, khách mời có thể xóa ảnh, khiến người tổ chức bị mất tệp.
Chi phí ẩn của việc "chỉ cần tải lên"
Một cách tiếp cận ngây thơ coi việc tải lên là một yêu cầu HTTP POST duy nhất. Nó hoạt động khi kết nối ổn định, nhưng trên một mạng bị nghẽn, mỗi lần gián đoạn sẽ buộc toàn bộ tệp phải bắt đầu lại từ đầu. Người dùng cảm thấy thất vọng và băng thông sẽ tăng đột biến khi hàng chục điện thoại cùng thử lại một lúc. Việc chia nhỏ tệp thành các phân đoạn (chunks) và theo dõi từng phần sẽ làm tăng độ phức tạp, nhưng đổi lại là một quá trình truyền tải có thể dự đoán được, ít tiêu tốn tài nguyên và có thể sống sót qua các lần chuyển đổi mạng.
Xây dựng hệ thống tải lên theo phân đoạn và có thể tiếp tục
Dưới đây là công thức thực hành từng bước.
1. Tạo ID tải lên trước khi bất kỳ dữ liệu nào rời khỏi trình duyệt
Tạo một mã định danh duy nhất (UUID) cục bộ và gửi nó đến máy chủ trong yêu cầu đầu tiên. Máy chủ sẽ ghi lại một phiên làm việc (session) dưới ID đó. Nếu trình duyệt sau đó thử lại do hết thời gian chờ (timeout), nó sẽ bao gồm cùng một UUID, giúp máy chủ nhận diện được phiên làm việc và tránh tạo mục nhập trùng lặp. Điều này giúp quy trình có tính lũy đẳng (idempotent) — việc lặp lại cùng một yêu cầu sẽ không gây ra tác động bất lợi nào.
2. Chia nhỏ tệp thành các phân đoạn từ 5 – 10 MB
Kích thước phân đoạn là một sự đánh đổi. Các phân đoạn nhỏ (dưới 1 MB) làm tăng số lượng yêu cầu HTTP và chi phí tiêu tốn cho header đi kèm. Các phân đoạn quá lớn khiến bất kỳ sự gián đoạn nào cũng trở nên tốn kém vì máy khách phải gửi lại một phần lớn dữ liệu. Đối với ảnh thông thường và video ngắn, mức 5 – 10 MB là sự cân bằng hợp lý: mỗi yêu cầu hoàn thành đủ nhanh để giữ cho UI phản hồi tốt, trong khi số lượng yêu cầu vẫn ở mức có thể kiểm soát được.
3. Giới hạn số lượng tải lên song song
Các trình duyệt di động có thể mở nhiều kết nối, nhưng trên mạng Wi-Fi bị nghẽn, mỗi luồng bổ sung sẽ cạnh tranh băng thông hạn chế. Hai luồng ổn định sẽ tốt hơn tám luồng đang cạnh tranh nhau. Sử dụng API navigator.connection để phát hiện các điều kiện băng thông thấp và tự động giảm số lượng thực hiện đồng thời.
4. Lưu trữ trạng thái tải lên trong IndexedDB
Lưu trữ ID tải lên, danh sách các phân đoạn đã gửi và bất kỳ vị trí (offset) nào đã được máy chủ xác nhận trong IndexedDB của trình duyệt. Nếu trang được tải lại hoặc người dùng đóng tab, máy khách có thể khôi phục trạng thái trong lần tải tiếp theo. Khi người dùng mở lại trang, hãy yêu cầu họ chọn cùng một tệp; siêu dữ liệu (metadata) đã lưu trữ sẽ cho phép việc tải lên tiếp tục từ phân đoạn cuối cùng đã được xác nhận thay vì bắt đầu lại từ đầu.
5. Phát hiện thay đổi mạng thực tế, không chỉ dựa vào navigator.onLine
Cờ navigator.onLine thường báo "online" ngay cả khi kết nối không thể sử dụng được. Thay vào đó, hãy thiết lập thời gian chờ yêu cầu ngắn (ví dụ: 5 giây) cho mỗi phân đoạn. Nếu xảy ra lỗi timeout, hãy coi như mạng đã bị ngắt. Khi kết nối được khôi phục, hãy truy vấn máy chủ để lấy danh sách các phân đoạn mà nó đã có, sau đó chỉ tiếp tục tải lên các phần còn thiếu. Điều này tránh việc gửi dữ liệu trùng lặp sau một đợt mất kết nối ngắn.
6. Áp dụng cơ chế lùi lại lũy thừa kết hợp với độ trễ ngẫu nhiên (jitter) khi thử lại
Khi thiết bị của nhiều khách mời nhận thấy mạng đã hoạt động trở lại, họ có thể cùng lúc thực hiện các yêu cầu thử lại, gây quá tải cho máy chủ. Cơ chế lùi lại lũy thừa (exponential backoff) khiến mỗi lần thử lại sau sẽ chờ lâu hơn lần trước, trong khi jitter thêm vào một khoảng thời gian ngẫu nhiên nhỏ. Sự kết hợp này giúp phân tán lưu lượng thử lại trong vài giây, ngăn chặn sự gia tăng đột biến.
7. Hiển thị phản hồi phân tầng và dễ tiếp cận
Một thanh trạng thái ba cấp sẽ truyền đạt trạng thái thực tế của tệp:
- Received (Đã nhận) – máy chủ đã lưu trữ mọi phân đoạn và đánh dấu tệp là hoàn tất.
- Preparing (Đang chuẩn bị) – máy chủ đang tạo ảnh thu nhỏ (thumbnails) hoặc chuyển mã (transcoding) video.
- Available (Sẵn sàng) – người tổ chức có thể xem hoặc tải tệp xuống.
Tránh chỉ dựa vào màu sắc; hãy kết hợp các biểu tượng với văn bản ngắn để người dùng trình đọc màn hình cũng có thể hiểu được tiến trình.
Điều gì vẫn có thể xảy ra sai sót?
Ngay cả một cơ chế tải lên có thể tiếp tục (resumable upload) được thiết kế kỹ lưỡng cũng có thể gặp trục trặc ở một vài trường hợp biên (edge cases).
Những điều cần lưu ý tiếp theo
Nền tảng web đang không ngừng phát triển.
Bài học rút ra
Một cơ chế tải lên theo từng phần (chunked upload) có khả năng tiếp tục, có theo dõi từng phần, lưu trữ trạng thái cục bộ và thử lại một cách thông minh sẽ biến một mạng lưới sự kiện chập chờn thành một kênh truyền tải đáng tin cậy cho ảnh của khách mời. Hãy thực hiện danh sách kiểm tra ở trên.
