💾Storage Quotas và Eviction Criteria
Là lập trình viên website, chúng ta thường xuyên tiếp cận đến việc nhờ sự hỗ trợ từ phía máy tính người dùng để lưu trữ thông tin thay vì liên tục gọi api để giảm tải cho server. Tuy nhiên, việc hiểu rõ cách thức các trình duyệt quản lý Storage Quotas (hạn ngạch lưu trữ) và Eviction Criteria (tiêu chí xóa dữ liệu tự động) là cực kỳ quan trọng khi dữ liệu cần lưu trữ của bạn bắt đầu phình to.
Bài viết này sẽ đi sơ lược bề các công nghệ lưu trữ, cách trình duyệt phân bổ và giới hạn dung lượng, cũng như cơ chế trình duyệt lựa chọn dữ liệu nào để xóa trước khi nó nhận biết được dữ liệu của bạn đang làm cản trở đến tốc độ xử lý dữ liệu của máy người dùng, giúp bạn có cái nhìn toàn diện về tối tuu hóa chiến lược lưu trữ cho ứng dụng của mình.
Các công nghệ lưu trữ phía client
Các công nghệ liệt kê dưới đây được mình ứng dụng vào trong dự án của công ty, bắt đầu cho chuỗi những bài viết về chủ đề 'Client-side Storage'
- Cookies: dùng cho dữ liệu nhỏ và đơn giản; dữ liệu được đính kèm HTTP request, làm tăng payload.
- Web Storage (localStorage & sessionStorage): API key/value đơn giản để lưu chuỗi. localStorage giữ dữ liệu vĩnh viễn (cho đến khi người dùng tự xóa hoặc gỡ ứng dụng), sessionStorage tồn tại trong suốt phiên làm việc (cho đến khi người dùng tắt tab ứng dụng). Hầu hết các trình duyệt giới hạn cho loại này khoảng 5 - 10MB trên mỗi origin.
- IndexedDB: cơ sở dữ liệu NoSQL, cho phép lưu trữ dữ liệu có cấu trúc lớn và truy vấn phức tạp. Đây là lựa chọn lý tưởng cho các ứng dụng yêu cầu khả năng làm việc offline hoặc xử lý lượng dữ liệu cục bộ. Dung lượng sẽ phụ thuộc vào khoảng không gian còn trống trên ổ cứng của người dùng, thường lên tới hàng chục đến hàng trăm GB.
- Cache API: một phần của service worker, cho phép kiểm soát bộ nhớ cache cho các cặp Request/Response. Dùng để quản lý các file trong thư mục assets (JS, CSS, ảnh, ...) và dữ liệu trong API làm tăng tốc độ tải trang và hỗ trợ offline.
Cách trình duyệt phân biệt dữ liệu: Origin-base Storage
Để đảm bảo bảo mật và quyền riêng tư, chống bị ăn cắp dữ liệu, trình duyệt cô lập dữ liệu theo origin. Một origin được định nghĩa bởi: scheme, hostname và port. Ví dụ: https://example.com:443
Điều này có nghĩa là tất cả các đường dẫn có cùng một origin có thể chia sẻ được dữ liệu với nhau. Ví dụ https://example.com/app-1 và https://example.com/app-2 cùng chia sẻ một Storage Bucket và chịu chung giới hạng dung lượng.
Chế độ lưu trữ: Best-Effort vs. Persistent
Dữ liệu được lưu trữ trên máy người dùng có thể được thiết lập ở 2 cơ chế:
Best-effor Storage (mặc định):
- Đây là chế độ được lưu trữ mặc định cho hầu hết các Storage API (IndexedDB, Cache API).
- Dữ liệu được lưu giữ miễn là không vượt quá giới hạng cho phép của origin, thiết bị còn dung lượng trống và người dùng không xóa thủ công.
- Chú ý: trình duyệt có thể xóa dữ liệu best-effort bất kỳ lúc nào để giải phóng dung lượng mà không có thông báo nào cho người dùng. Trường hợp này xảy ra khi hệ thống gặp vấn đề về dung lượng bộ nhớ.
Persistent Storage:
- Một origin có thể yêu cầu được cấp quyền lưu trữ lâu dài thông qua Storage API (navigator.storage.persis())
- Khi được cấp phép, dữ liệu sẽ chỉ bị xóa khi người dùng xóa thủ công qua cài đặt của trình duyệt. Điều này đảm bảo cao hơn về tính bền vững của dữ liệu
Cơ chế cấp quyền:
- Firefox: sẽ hiển thị một hộp thoại hỏi người dùng cho phép
- Chrome, Edge, Safari: tự động duyệt hay từ chối dựa trên lịch sử tương tác của người dùng.
- Dữ liệu persistent hiếm khi bị xóa, đặv biệt với các website có tương tác cao.
Lưu ý về chế độ duyệt web riêng tư (Incognito/Private Browser): trong chế độ này, trình duyệt thường áp dụng dung lượng thấp và tất cả dữ liệu lưu trữ sẽ bị xóa ngay lập tức khi phiên làm việc kết thúc.
Mỗi trình duyệt có chính sách và giới hạn dung lượng riêng cho từng origin
- Cookies: vài KB (thường 4KB) trên mỗi domain, với giới hạn tổng số cookie.
- Web storage: 5MB cho localStorage và 5MB cho sessionStorage trên mỗi origin. Khi vượt quá giới hạn cho phép, trình duyệt sẽ báo lỗi QuotaExceededError
- IndexedDB, Cache API: các api này sẽ dùng chung một pool dung lượng và có giới hạn lớn hơn nhiều (thường hàng chục đến hàng trăm GB), phụ thuộc vào tổng dung lượng ổ cứng của thiết bị.
Ví dụ về giới hạn dung lượng theo trình duyệt:
Firefox:
- Best-effort: Giới hạn là giá trị nhỏ hơn giữa 10% tổng dung lượng ổ cứng và 10 GiB (giới hạn cho nhóm các origin cùng eTLD+1).
- Persistent: Lên đến 50% tổng dung lượng ổ cứng, tối đa 8 TiB, và không bị giới hạn theo eTLD+1.
Ví dụ: Ổ cứng 500 GiB: Best-effort tối đa 10 GiB, Persistent tối đa 250 GiB.
Chromium-based (Chrome, Edge): cả best-effort và persistent đều có thể sử dụng lên đến 60% tổng dung lượng ổ cứng.
Ví dụ: Ổ cứng 1 TiB, một origin có thể sử dụng tới 600 GiB.
Safari (macOS 14+, iOS 17+): giới hạn mặc định khoảng 20% dung lượng ổ cứng cho mỗi origin.
Lưu ý: iframe cross-origin bị giới hạn rất chặt, khoảng 1/10 dung lượng của trang cha để ngăn chặn theo dõi.
Tổng dung lượng của tất cả các origin không vượt quá 80% dung lượng ổ cứng cho trình duyệt và web app, và 15% cho các ứng dụng không phải trình duyệt.
Kiểm tra dung lượng còn trống
Bạn có thể sử dụng navigator.storage.estimate() để ước lượng dung lượng đã dùng và còn trống cho origin hiện tại.
if (navigator.storage && navigator.storage.estimate) { navigator.storage.estimate().then(({ usage, quota }) => { console.log(`Used: ${usage / (1024 * 1024)} MiB`); console.log(`Quota: ${quota / (1024 * 1024)} MiB`); console.log(`Available: ${(quota - usage) / (1024 * 1024)} MiB`); });
}
Lưu ý: Giá trị trả về chỉ là ước lượng. Trình duyệt có thể thêm padding hoặc gộp dữ liệu để chống fingerprinting. Khi lưu vượt quota, các API như IndexedDB, Cache API, OPFS sẽ ném lỗi QuotaExceededError. Luôn sử dụng khối try...catch hoặc .catch() trong Promise để xử lý lỗi này và có chiến lược giảm bớt dữ liệu (ví dụ: xóa dữ liệu cũ nhất).
Tiêu chi xóa dữ liệu (Eviction Criteria)
Dữ liệu lưu trữ best-effort có thể bị xóa bởi trình duyệt trong các trường hợp sau:
Storage Pressure (Thiết bị thiếu dung lượng)
- Đây là trường hợp phổ biến nhất. Khi ổ cứng của thiết bị gần đầy, trình duyệt sẽ chủ động xóa dữ liệu.
- Cơ chế xóa thường tuân theo chính sách Least Recently Used (LRU): Các origin có thời gian tương tác gần nhất xa nhất sẽ bị xóa trước tiên.
- Quá trình xóa sẽ tiếp tục cho đến khi đủ dung lượng hoặc không còn dữ liệu best-effort nào để xóa.
- Cơ chế này không áp dụng cho dữ liệu đã được cấp quyền persistent.
Browser Max Storage Exceeded (Vượt giới hạn tổng bộ nhớ của trình duyệt):
- Một số trình duyệt (như Chrome) có giới hạn tổng dung lượng mà tất cả các origin cộng lại có thể sử dụng (ví dụ: 80% tổng dung lượng ổ cứng).
- Khi tổng dung lượng vượt quá ngưỡng này, trình duyệt sẽ xóa các origin best-effort theo chính sách LRU cho đến khi tổng dung lượng dưới ngưỡng cho phép.
Proactive Eviction (Xóa chủ động - Đặc trưng của Safari):
- Safari, trong nỗ lực tăng cường ngăn chặn theo dõi cross-site, sẽ chủ động xóa dữ liệu của các origin (bao gồm IndexedDB, Cache API, Web Storage) nếu không có tương tác người dùng (click, tap) trong 7 ngày.
- Điều này đặc biệt quan trọng nếu ứng dụng của bạn dựa vào dữ liệu lưu trữ offline mà người dùng không tương tác thường xuyên. Cookies do server set (HTTP Cookies) không bị ảnh hưởng bởi chính sách này.
Cách thức dữ liệu bị xóa:
Khi một origin bị xóa, tất cả dữ liệu của origin đó (bao gồm IndexedDB, Cache API, Web Storage, OPFS) đều bị xóa cùng lúc. Trình duyệt sẽ không xóa từng phần để tránh gây ra trạng thái không nhất quán hoặc lỗi trong ứng dụng.
Chiến Lược Tối Ưu Hóa Lưu Trữ Cho Lập Trình Viên
Việc hiểu rõ cơ chế này giúp chúng ta có thể dễ dàng phân loại dữ liệu nên được lưu vào nơi nào, tận dụng tốt được sức mạnh của máy người dùng
Phân biệt dữ liệu
- Sử dụng Cookies cho xác thực và trạng thái phiên nhỏ.
- Sử dụng Web Storage cho cài đặt người dùng hoặc dữ liệu đơn giản, không cần độ bền cao.
- Sử dụng IndexedDB và Cache API cho dữ liệu lớn hơn, phức tạp hơn, đặc biệt khi cần làm việc offline hoặc cải thiện hiệu suất tải.
Xử lý QuotasExceededError
- Xóa dữ liệu cũ nhất hoặc ít quan trọng nhất.
- Thông báo cho người dùng biết về việc thiếu dung lượng và đề xuất giải pháp.
- Sử dụng try...catch để tránh bị gián đoạn ứng dụng.
- Sử dụng navigator.storage.estimate() để đưa ra cảnh báo sớm cho người dùng.
Hy vọng bài viết này sẽ có ích đối với bạn!! Cùng chia sẻ với mình qua LinkedIn nhé! 👇