Có nhiều lựa chọn để lưu trữ dữ liệu trong trình duyệt. Lựa chọn nào phù hợp nhất với nhu cầu của bạn?
Kết nối Internet có thể không ổn định hoặc không có khi đang di chuyển, đó là lý do tại sao tính năng hỗ trợ ngoại tuyến và hiệu suất đáng tin cậy là các tính năng phổ biến trong ứng dụng web tiến bộ. Ngay cả trong môi trường không dây hoàn hảo, việc sử dụng hợp lý bộ nhớ đệm và các kỹ thuật lưu trữ khác có thể cải thiện đáng kể trải nghiệm người dùng. Có một số cách để lưu tài nguyên ứng dụng tĩnh (HTML, JavaScript, CSS, hình ảnh, v.v.) và dữ liệu (dữ liệu người dùng, tin bài, v.v.) vào bộ nhớ đệm. Nhưng đâu là giải pháp tốt nhất? Bạn có thể lưu trữ bao nhiêu? Làm cách nào để ngăn chặn việc này?
Tôi nên sử dụng công cụ nào?
Sau đây là đề xuất chung về việc lưu trữ tài nguyên:
- Đối với các tài nguyên mạng cần thiết để tải ứng dụng, hãy sử dụng Cache Storage API (một phần của trình chạy dịch vụ).
- Đối với nội dung dựa trên tệp, hãy sử dụng Hệ thống tệp riêng tư gốc (OPFS).
- Đối với dữ liệu khác, hãy sử dụng IndexedDB (với một trình bao bọc lời hứa).
IndexedDB, OPFS và Cache Storage API được hỗ trợ trong mọi trình duyệt hiện đại.
Các API này không đồng bộ và sẽ không chặn luồng chính (nhưng cũng có một biến thể đồng bộ của OPFS chỉ có trong trình chạy web). Bạn có thể truy cập vào các API này từ đối tượng window, trình chạy web và trình chạy dịch vụ, nhờ đó có thể sử dụng chúng ở bất cứ đâu trong mã của bạn.
Còn các cơ chế lưu trữ khác thì sao?
Có một số cơ chế lưu trữ khác trong trình duyệt, nhưng chúng có mức sử dụng hạn chế và có thể gây ra các vấn đề nghiêm trọng về hiệu suất.
SessionStorage dành riêng cho thẻ và có phạm vi trong suốt thời gian hoạt động của thẻ. Cách này có thể hữu ích khi lưu trữ một lượng nhỏ thông tin cụ thể về phiên, chẳng hạn như khoá IndexedDB. Bạn nên thận trọng khi sử dụng vì phương thức này có tính đồng bộ và sẽ chặn luồng chính. Dung lượng lưu trữ này bị giới hạn ở mức khoảng 5 MB và chỉ có thể chứa chuỗi. Vì dành riêng cho thẻ, nên trình chạy web hoặc trình chạy dịch vụ không thể truy cập vào API này.
Bạn nên tránh sử dụng LocalStorage vì API này đồng bộ và sẽ chặn luồng chính. Dung lượng lưu trữ bị giới hạn ở mức khoảng 5 MB và chỉ có thể chứa chuỗi. Không thể truy cập LocalStorage từ trình chạy web hoặc trình chạy dịch vụ.
Cookie có công dụng riêng nhưng không nên dùng để lưu trữ. Cookie được gửi cùng với mọi yêu cầu HTTP, vì vậy, việc lưu trữ bất kỳ dữ liệu nào nhiều hơn một lượng nhỏ dữ liệu sẽ làm tăng đáng kể kích thước của mọi yêu cầu web. Các API này đồng bộ và không thể truy cập được từ trình chạy web. Giống như LocalStorage và SessionStorage, cookie chỉ được giới hạn ở các chuỗi.
File System Access API được thiết kế để giúp người dùng có thể đọc và chỉnh sửa tệp trên hệ thống tệp cục bộ của họ. Người dùng phải cấp quyền trước khi một trang có thể đọc hoặc ghi vào bất kỳ tệp cục bộ nào và quyền không được duy trì giữa các phiên, trừ phi một tay điều khiển tệp được lưu vào bộ nhớ đệm trong IndexedDB. API Truy cập hệ thống tệp phù hợp nhất với các trường hợp sử dụng như trình chỉnh sửa, trong đó bạn cần mở một tệp, sửa đổi tệp đó rồi có thể lưu lại các thay đổi vào tệp.
File System API và FileWriter API cung cấp các phương thức để đọc và ghi tệp vào hệ thống tệp hộp cát. Mặc dù không đồng bộ, nhưng bạn không nên sử dụng vì chỉ có trong các trình duyệt dựa trên Chromium.
Tôi có thể lưu trữ bao nhiêu dữ liệu?
Nói ngắn gọn là rất nhiều, ít nhất là vài trăm megabyte và có thể lên đến hàng trăm gigabyte hoặc hơn. Cách triển khai của trình duyệt sẽ khác nhau, nhưng dung lượng lưu trữ hiện có thường dựa trên dung lượng lưu trữ hiện có trên thiết bị.
- Chrome cho phép trình duyệt sử dụng tối đa 80% tổng dung lượng ổ đĩa. Một nguồn gốc có thể sử dụng tối đa 60% tổng dung lượng ổ đĩa. Bạn có thể sử dụng StorageManager API để xác định hạn mức tối đa hiện có. Các trình duyệt khác dựa trên Chromium có thể khác.
- Ở chế độ ẩn danh, Chrome giảm dung lượng lưu trữ mà một nguồn gốc có thể sử dụng xuống còn khoảng 5% tổng dung lượng ổ đĩa.
- Nếu người dùng đã bật chế độ "Xoá cookie và dữ liệu trang web khi bạn đóng tất cả cửa sổ" trong Chrome, thì hạn mức bộ nhớ sẽ giảm đáng kể xuống mức tối đa là khoảng 300 MB.
- Firefox cho phép trình duyệt sử dụng tối đa 50% dung lượng ổ đĩa trống. Một nhóm eTLD+1 (ví dụ:
example.com,www.example.comvàfoo.bar.example.com) có thể sử dụng tối đa 2 GB. Bạn có thể sử dụng StorageManager API để xác định dung lượng còn trống. - Safari (cả máy tính và thiết bị di động) dường như cho phép khoảng 1 GB. Khi đạt đến giới hạn, Safari sẽ nhắc người dùng, tăng giới hạn theo mức tăng 200 MB. Tôi không tìm thấy tài liệu chính thức nào về vấn đề này.
- Nếu một PWA được thêm vào màn hình chính trên Safari dành cho thiết bị di động, thì PWA đó sẽ tạo một vùng chứa lưu trữ mới và không có nội dung nào được chia sẻ giữa PWA và Safari dành cho thiết bị di động. Sau khi đạt đến hạn mức cho một PWA đã cài đặt, có vẻ như không có cách nào để yêu cầu thêm bộ nhớ.
Trước đây, nếu một trang web vượt quá ngưỡng dữ liệu được lưu trữ nhất định, thì trình duyệt sẽ nhắc người dùng cấp quyền sử dụng thêm dữ liệu. Ví dụ: nếu nguồn gốc sử dụng hơn 50 MB, trình duyệt sẽ nhắc người dùng cho phép lưu trữ tối đa 100 MB, sau đó hỏi lại khi tăng thêm 50 MB.
Ngày nay, hầu hết các trình duyệt hiện đại sẽ không nhắc người dùng và sẽ cho phép một trang web sử dụng tối đa hạn mức được phân bổ. Ngoại lệ dường như là Safari, nhắc khi vượt quá hạn mức bộ nhớ, yêu cầu quyền tăng hạn mức được phân bổ. Nếu một nguồn gốc cố gắng sử dụng nhiều hơn hạn mức được phân bổ, thì các lần ghi dữ liệu tiếp theo sẽ không thành công.
Làm cách nào để kiểm tra dung lượng lưu trữ còn trống?
Trong nhiều trình duyệt, bạn có thể sử dụng StorageManager API để xác định dung lượng lưu trữ có sẵn cho nguồn gốc và dung lượng lưu trữ mà nguồn gốc đó đang sử dụng. API này báo cáo tổng số byte mà IndexedDB và Cache API sử dụng, đồng thời cho phép tính toán dung lượng lưu trữ còn lại gần đúng.
if (navigator.storage && navigator.storage.estimate) {
const quota = await navigator.storage.estimate();
// quota.usage -> Number of bytes used.
// quota.quota -> Maximum number of bytes available.
const percentageUsed = (quota.usage / quota.quota) * 100;
console.log(`You've used ${percentageUsed}% of the available storage.`);
const remaining = quota.quota - quota.usage;
console.log(`You can write up to ${remaining} more bytes.`);
}
Bạn phải phát hiện lỗi vượt quá hạn mức (xem bên dưới). Trong một số trường hợp, hạn mức có thể vượt quá dung lượng lưu trữ thực tế.
Kiểm tra
Trong quá trình phát triển, bạn có thể sử dụng Công cụ cho nhà phát triển của trình duyệt để kiểm tra các loại bộ nhớ khác nhau và xoá tất cả dữ liệu đã lưu trữ.
Một tính năng mới đã được thêm vào Chrome 88 cho phép bạn ghi đè hạn mức bộ nhớ của trang web trong Ngăn bộ nhớ. Tính năng này cho phép bạn mô phỏng nhiều thiết bị và kiểm thử hành vi của ứng dụng trong các trường hợp dung lượng ổ đĩa thấp. Chuyển đến Application (Ứng dụng) rồi đến Storage (Bộ nhớ), bật hộp đánh dấu Simulate custom storage quota (Mô phỏng hạn mức bộ nhớ tuỳ chỉnh) rồi nhập một số hợp lệ bất kỳ để mô phỏng hạn mức bộ nhớ.
Làm cách nào để xử lý khi vượt quá hạn mức?
Bạn nên làm gì khi vượt quá hạn mức? Quan trọng nhất là bạn phải luôn phát hiện và xử lý lỗi ghi, cho dù đó là QuotaExceededError hay lỗi khác. Sau đó, tuỳ thuộc vào thiết kế ứng dụng, hãy quyết định cách xử lý.
Ví dụ: xoá nội dung đã lâu không được truy cập, xoá dữ liệu dựa trên kích thước hoặc cung cấp cách để người dùng chọn nội dung họ muốn xoá.
Cả IndexedDB và API Bộ nhớ đệm đều gửi một DOMError có tên là QuotaExceededError khi bạn vượt quá hạn mức hiện có.
IndexedDB
Nếu nguồn gốc đã vượt quá hạn mức, thì các nỗ lực ghi vào IndexedDB sẽ không thành công. Trình xử lý onabort() của giao dịch sẽ được gọi, truyền một sự kiện.
Sự kiện sẽ bao gồm một DOMException trong thuộc tính lỗi. Việc kiểm tra lỗi name sẽ trả về QuotaExceededError.
const transaction = idb.transaction(['entries'], 'readwrite');
transaction.onabort = function(event) {
const error = event.target.error; // DOMException
if (error.name == 'QuotaExceededError') {
// Fallback code goes here
}
};
API bộ nhớ đệm
Nếu nguồn gốc đã vượt quá hạn mức, thì các nỗ lực ghi vào Cache API sẽ bị từ chối kèm theo QuotaExceededError DOMException.
try {
const cache = await caches.open('my-cache');
await cache.add(new Request('/sample1.jpg'));
} catch (err) {
if (error.name === 'QuotaExceededError') {
// Fallback code goes here
}
}
Cơ chế loại bỏ hoạt động như thế nào?
Bộ nhớ web được phân loại thành hai bộ chứa: "Tốt nhất có thể" và "Ổn định". Tốt nhất có nghĩa là trình duyệt có thể xoá bộ nhớ mà không làm gián đoạn người dùng, nhưng ít bền hơn đối với dữ liệu dài hạn hoặc dữ liệu quan trọng. Bộ nhớ ổn định không tự động bị xoá khi sắp hết bộ nhớ. Người dùng cần xoá bộ nhớ này theo cách thủ công (thông qua các chế độ cài đặt của trình duyệt).
Theo mặc định, dữ liệu của một trang web (bao gồm IndexedDB, Cache API, v.v.) thuộc danh mục nỗ lực tối đa, nghĩa là trừ phi một trang web đã yêu cầu bộ nhớ ổn định, trình duyệt có thể loại bỏ dữ liệu trang web theo quyết định của riêng mình, chẳng hạn như khi bộ nhớ trên thiết bị sắp hết.
Chính sách loại bỏ cho nỗ lực tối đa là:
- Trình duyệt dựa trên Chromium sẽ bắt đầu loại bỏ dữ liệu khi trình duyệt hết dung lượng, xoá tất cả dữ liệu trang web khỏi nguồn gốc ít được sử dụng gần đây nhất trước, sau đó là nguồn gốc tiếp theo, cho đến khi trình duyệt không còn vượt quá giới hạn.
- Firefox sẽ bắt đầu loại bỏ dữ liệu khi dung lượng ổ đĩa còn trống đã đầy, xoá tất cả dữ liệu trang web khỏi nguồn gốc ít được sử dụng gần đây nhất trước, sau đó là nguồn gốc tiếp theo, cho đến khi trình duyệt không còn vượt quá giới hạn.
- Trước đây, Safari không loại bỏ dữ liệu, nhưng gần đây đã triển khai giới hạn mới là 7 ngày đối với tất cả bộ nhớ có thể ghi (xem bên dưới).
Kể từ iOS và iPadOS 13.4 và Safari 13.1 trên macOS, có một giới hạn 7 ngày đối với tất cả bộ nhớ có thể ghi tập lệnh, bao gồm cả IndexedDB, dịch vụ đăng ký worker và API bộ nhớ đệm. Điều này có nghĩa là Safari sẽ loại bỏ tất cả nội dung khỏi bộ nhớ đệm sau 7 ngày sử dụng Safari nếu người dùng không tương tác với trang web. Chính sách loại bỏ này không áp dụng cho các PWA đã cài đặt và được thêm vào màn hình chính. Hãy xem bài viết Full Third-Party Cookie Blocking and More (Chặn hoàn toàn cookie của bên thứ ba và nhiều tính năng khác) trên blog WebKit để biết toàn bộ thông tin chi tiết.
Bộ chứa lưu trữ
Ý tưởng cốt lõi của Storage Buckets API là cấp cho các trang web khả năng tạo nhiều bộ chứa lưu trữ, trong đó trình duyệt có thể chọn xoá từng bộ chứa độc lập với các bộ chứa khác. Điều này cho phép nhà phát triển chỉ định mức độ ưu tiên loại bỏ để đảm bảo dữ liệu có giá trị nhất không bị xoá.
Phần bổ sung: Lý do nên sử dụng trình bao bọc cho IndexedDB
IndexedDB là một API cấp thấp, đòi hỏi phải thiết lập nhiều trước khi sử dụng. Điều này có thể gây khó khăn cho việc lưu trữ dữ liệu có độ phức tạp thấp. Không giống như hầu hết các API dựa trên lời hứa hiện đại, API này dựa trên sự kiện. Các trình bao bọc Promise như idb cho IndexedDB ẩn một số tính năng mạnh mẽ nhưng quan trọng hơn là ẩn cơ chế phức tạp (ví dụ: giao dịch, tạo phiên bản giản đồ) đi kèm với thư viện IndexedDB.
Phần thưởng: SQLite Wasm
Sau khi Web SQL ngừng hoạt động và bị xoá khỏi Chrome, Google đã hợp tác với những người duy trì cơ sở dữ liệu SQLite phổ biến để cung cấp một giải pháp thay thế cho Web SQL dựa trên SQLite. Đọc bài viết SQLite Wasm trong trình duyệt được hỗ trợ bởi Hệ thống tệp riêng tư gốc để biết thông tin chi tiết về cách sử dụng.
Kết luận
Đã qua rồi thời kỳ bộ nhớ bị giới hạn và người dùng phải lưu trữ ngày càng nhiều dữ liệu. Các trang web có thể lưu trữ hiệu quả tất cả tài nguyên và dữ liệu mà chúng cần để chạy. Khi sử dụng StorageManager API, bạn có thể xác định dung lượng còn trống và dung lượng đã sử dụng. Và với bộ nhớ ổn định, bạn có thể bảo vệ dữ liệu khỏi bị loại bỏ, trừ phi người dùng xoá dữ liệu đó.
Tài nguyên khác
Cảm ơn bạn!
Xin chân thành cảm ơn Jarryd Goodman, Phil Walton, Eiji Kitamura, Daniel Murphy, Darwin Huang, Josh Bell, Marijn Kruisselbrink và Victor Costan đã xem xét hướng dẫn này. Cảm ơn Eiji Kitamura, Addy Osmani và Marc Cohen đã viết các bài viết gốc làm cơ sở cho bài viết này. Eiji đã viết một công cụ hữu ích có tên là Browser Storage Abuser (Trình lạm dụng bộ nhớ của trình duyệt) để xác thực hành vi hiện tại. API này cho phép bạn lưu trữ nhiều dữ liệu nhất có thể và xem giới hạn bộ nhớ trên trình duyệt. Cảm ơn François Beaufort đã tìm hiểu về Safari để tìm ra giới hạn bộ nhớ của trình duyệt này, và cảm ơn Thomas Steiner đã bổ sung thông tin về hệ thống tệp riêng tư gốc, bộ chứa lưu trữ, SQLite Wasm và nội dung cập nhật tổng thể vào năm 2024.