Almacenamiento para la Web

Existen muchas opciones diferentes para almacenar datos en el navegador. ¿Cuál es la mejor para tus necesidades?

Las conexiones a Internet pueden ser inestables o inexistentes cuando estás en movimiento, por lo que la compatibilidad sin conexión y el rendimiento confiable son funciones comunes en las apps web progresivas. Incluso en entornos inalámbricos perfectos, el uso prudente del almacenamiento en caché y otras técnicas de almacenamiento pueden mejorar considerablemente la experiencia del usuario. Existen varias formas de almacenar en caché los recursos estáticos de la aplicación (HTML, JavaScript, CSS, imágenes, etc.) y los datos (datos del usuario, artículos de noticias, etc.). Pero, ¿cuál es la mejor solución? ¿Cuánto puedes almacenar? ¿Cómo evitas que se quite?

¿Qué debo usar?

Esta es una recomendación general para almacenar recursos:

IndexedDB, el OPFS y la API de Cache Storage son compatibles con todos los navegadores modernos. Son asíncronos y no bloquearán el subproceso principal (pero también hay una variante síncrona del OPFS que está disponible exclusivamente en los web workers). Se puede acceder a ellos desde el objeto window, los web workers y los service workers, lo que permite usarlos en cualquier parte del código.

¿Qué sucede con otros mecanismos de almacenamiento?

Existen varios otros mecanismos de almacenamiento disponibles en el navegador, pero tienen un uso limitado y pueden causar problemas de rendimiento significativos.

SessionStorage es específico de la pestaña y se limita a la vida útil de la pestaña. Puede ser útil para almacenar pequeñas cantidades de información específica de la sesión, por ejemplo, una clave de IndexedDB. Se debe usar con precaución porque es síncrono y bloqueará el subproceso principal. Se limita a aproximadamente 5 MB y solo puede contener cadenas. Debido a que es específico de la pestaña, no se puede acceder a él desde los web workers ni los service workers.

Se debe evitar LocalStorage porque es síncrono y bloqueará el subproceso principal. Se limita a aproximadamente 5 MB y solo puede contener cadenas. No se puede acceder a LocalStorage desde los web workers ni los service workers.

Las cookies tienen sus usos, pero no se deben usar para el almacenamiento. Las cookies se envían con cada solicitud HTTP, por lo que almacenar cualquier cantidad de datos que no sea pequeña aumentará significativamente el tamaño de cada solicitud web. Son síncronas y no se puede acceder a ellas desde los web workers. Al igual que LocalStorage y SessionStorage, las cookies se limitan solo a cadenas.

La API de File System Access se diseñó para permitir que los usuarios lean y editen archivos en su sistema de archivos local. El usuario debe otorgar permiso antes de que una página pueda leer o escribir en cualquier archivo local, y los permisos no persisten en las sesiones, a menos que se almacene en caché un controlador de archivos en IndexedDB. La API de File System Access es más adecuada para casos de uso como los editores, en los que necesitas abrir un archivo, modificarlo y, luego, guardar los cambios en el archivo.

Las APIs de File System y FileWriter proporcionan métodos para leer y escribir archivos en un sistema de archivos en zona de pruebas. Si bien es asíncrono, no se recomienda porque solo está disponible en navegadores basados en Chromium.

¿Cuánto puedo almacenar?

En resumen, mucho, al menos un par de cientos de megabytes y, potencialmente, cientos de gigabytes o más. Las implementaciones del navegador varían, pero la cantidad de almacenamiento disponible suele basarse en la cantidad de almacenamiento disponible en el dispositivo.

  • Chrome permite que el navegador use hasta el 80% del espacio total del disco. Un origen puede usar hasta el 60% del espacio total del disco. Puedes usar la API de StorageManager para determinar la cuota máxima disponible. Es posible que otros navegadores basados en Chromium sean diferentes.
    • En el modo Incógnito, Chrome reduce la cantidad de almacenamiento que puede usar un origen a aproximadamente el 5% del espacio total del disco.
    • Si el usuario habilitó "Borrar las cookies y los datos de sitios cuando cierras todas las ventanas" en Chrome, la cuota de almacenamiento se reduce significativamente a un máximo de aproximadamente 300 MB.
  • Firefox permite que el navegador use hasta el 50% del espacio libre del disco. Un grupo eTLD+1 (p.ej., example.com, www.example.com y foo.bar.example.com) puede usar hasta 2 GB. Puedes usar la API de StorageManager para determinar cuánto espacio aún está disponible.
  • Safari (tanto para computadoras como para dispositivos móviles) parece permitir aproximadamente 1 GB. Cuando se alcance el límite, Safari le solicitará al usuario que aumente el límite en incrementos de 200 MB. No pude encontrar ninguna documentación oficial sobre esto.
    • Si se agrega una PWA a la pantalla principal en Safari para dispositivos móviles, se crea un nuevo contenedor de almacenamiento y no se comparte nada entre la PWA y Safari para dispositivos móviles. Una vez que se alcanza la cuota para una PWA instalada, no parece haber ninguna forma de solicitar almacenamiento adicional.

En el pasado, si un sitio excedía un cierto umbral de datos almacenados, el navegador le solicitaba al usuario que otorgara permiso para usar más datos. Por ejemplo, si el origen usaba más de 50 MB, el navegador le solicitaba al usuario que le permitiera almacenar hasta 100 MB y, luego, volvía a preguntar en incrementos de 50 MB.

Hoy en día, la mayoría de los navegadores modernos no le solicitan al usuario que otorgue permiso y permiten que un sitio use hasta su cuota asignada. La excepción parece ser Safari, que solicita permiso cuando se excede la cuota de almacenamiento para aumentar la cuota asignada. Si un origen intenta usar más de su cuota asignada, fallarán los intentos posteriores de escribir datos.

¿Cómo puedo verificar cuánto almacenamiento está disponible?

En muchos navegadores, puedes usar la API de StorageManager para determinar la cantidad de almacenamiento disponible para el origen y cuánto almacenamiento está usando. Informa la cantidad total de bytes que usan IndexedDB y la API de Cache, y permite calcular el espacio de almacenamiento restante aproximado disponible.

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.`);
}

Debes detectar los errores de exceso de cuota (consulta a continuación). En algunos casos, es posible que la cuota disponible exceda la cantidad real de almacenamiento disponible.

Inspeccionar

Durante el desarrollo, puedes usar las Herramientas para desarrolladores de tu navegador para inspeccionar los diferentes tipos de almacenamiento y borrar todos los datos almacenados.

Se agregó una nueva función en Chrome 88 que te permite anular la cuota de almacenamiento del sitio en el panel Almacenamiento. Esta función te permite simular diferentes dispositivos y probar el comportamiento de tus apps en situaciones de baja disponibilidad de disco. Ve a Aplicación y, luego, a Almacenamiento, habilita la casilla de verificación Simular la cuota de almacenamiento personalizada y, luego, ingresa cualquier número válido para simular la cuota de almacenamiento.

Mientras trabajaba en esta guía, escribí una herramienta simple para intentar usar la mayor cantidad de almacenamiento posible rápidamente. Es una forma rápida de experimentar con diferentes mecanismos de almacenamiento y ver qué sucede cuando usas toda tu cuota.

¿Cómo se maneja el exceso de cuota?

¿Qué debes hacer cuando superas la cuota? Lo más importante es que siempre debes detectar y controlar los errores de escritura, ya sea un QuotaExceededError o algo más. Luego, según el diseño de tu app, decide cómo controlarlo. Por ejemplo, borra el contenido al que no se accedió en mucho tiempo, quita los datos según el tamaño o proporciona una forma para que los usuarios elijan lo que quieren borrar.

Tanto IndexedDB como la API de Cache arrojan un DOMError llamado QuotaExceededError cuando excediste la cuota disponible.

IndexedDB

Si el origen excedió su cuota, fallarán los intentos de escribir en IndexedDB. Se llamará al controlador onabort() de la transacción y se pasará un evento. El evento incluirá un DOMException en la propiedad de error. Si verificas el name del error, se mostrará 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 de Cache

Si el origen excedió su cuota, los intentos de escribir en la API de Cache se rechazarán con un 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ómo funciona la expulsión?

El almacenamiento web se clasifica en dos buckets: "Best Effort" y "Persistent". El mejor esfuerzo significa que el navegador puede borrar el almacenamiento sin interrumpir al usuario, pero es menos duradero para datos críticos o a largo plazo. El almacenamiento persistente no se borra automáticamente cuando el almacenamiento es bajo. El usuario debe borrar este almacenamiento de forma manual (a través de la configuración del navegador).

De forma predeterminada, los datos de un sitio (incluidos IndexedDB, la API de Cache, etc.) entran en la categoría de mejor esfuerzo, lo que significa que, a menos que un sitio haya solicitado almacenamiento persistente, el navegador puede expulsar los datos del sitio a su discreción, por ejemplo, cuando el almacenamiento del dispositivo es bajo.

La política de expulsión para el mejor esfuerzo es la siguiente:

  • Los navegadores basados en Chromium comenzarán a expulsar datos cuando el navegador se quede sin espacio, borrando primero todos los datos del sitio del origen menos usado recientemente y, luego, el siguiente, hasta que el navegador ya no supere el límite.
  • Firefox comenzará a expulsar datos cuando se llene el espacio disponible en el disco, borrando primero todos los datos del sitio del origen menos usado recientemente y, luego, el siguiente, hasta que el navegador ya no supere el límite.
  • Anteriormente, Safari no expulsaba datos, pero recientemente implementó un nuevo límite de siete días en todo el almacenamiento que se puede escribir (consulta a continuación).

A partir de iOS y iPadOS 13.4 y Safari 13.1 en macOS, hay un límite de siete días en todo el almacenamiento que se puede escribir con secuencias de comandos, incluidos IndexedDB, el registro de service worker y la API de Cache. Esto significa que Safari expulsará todo el contenido de la caché después de siete días de uso de Safari si el usuario no interactúa con el sitio. Esta política de expulsión no se aplica a las PWAs instaladas que se agregaron a la pantalla principal. Consulta Full Third-Party Cookie Blocking and More en el blog de WebKit para obtener detalles completos.

Buckets de almacenamiento

La idea principal de la API de Storage Buckets es otorgar a los sitios la capacidad de crear varios buckets de almacenamiento, en los que el navegador puede elegir borrar cada bucket de forma independiente de otros buckets. Esto permite a los desarrolladores especificar la prioridad de expulsión para asegurarse de que no se borren los datos más valiosos.

Bonificación: Por qué usar un wrapper para IndexedDB

IndexedDB es una API de bajo nivel que requiere una configuración significativa antes de su uso, lo que puede ser particularmente doloroso para almacenar datos de baja complejidad. A diferencia de la mayoría de las APIs modernas basadas en promesas, se basa en eventos. Los wrappers de promesas como idb para IndexedDB ocultan algunas de las funciones potentes, pero, lo que es más importante, ocultan la maquinaria compleja (p.ej., transacciones, control de versiones de esquema) que viene con la biblioteca de IndexedDB.

Bonificación: SQLite Wasm

Después de que Web SQL se diera de baja y se quitara de Chrome, Google trabajó con los mantenedores de la popular base de datos SQLite para ofrecer un reemplazo para Web SQL basado en SQLite. Lee SQLite Wasm en el navegador respaldado por el sistema de archivos privado de origen para obtener detalles sobre cómo usarlo.

Conclusión

Ya no existen los días de almacenamiento limitado y de solicitarle al usuario que almacene más y más datos. Los sitios pueden almacenar de manera eficaz todos los recursos y datos que necesitan para ejecutarse. Con la API de StorageManager, puedes determinar cuánto está disponible para ti y cuánto usaste. Además, con el almacenamiento persistente, a menos que el usuario lo quite, puedes protegerlo de la expulsión.

Recursos adicionales

Gracias

Agradecemos especialmente a Jarryd Goodman, Phil Walton, Eiji Kitamura, Daniel Murphy, Darwin Huang, Josh Bell, Marijn Kruisselbrink y Victor Costan por revisar esta guía. Gracias a Eiji Kitamura, Addy Osmani y Marc Cohen, quienes escribieron los artículos originales en los que se basa este. Eiji escribió una herramienta útil llamada Browser Storage Abuser que fue útil para validar el comportamiento actual. Te permite almacenar la mayor cantidad de datos posible y ver los límites de almacenamiento en tu navegador. Gracias a François Beaufort, quien investigó Safari para determinar sus límites de almacenamiento, y a Thomas Steiner por agregar información sobre el sistema de archivos privado de origen, los buckets de almacenamiento, SQLite Wasm y una actualización general de contenido en 2024.