Almacenamiento para la Web

Existen muchas opciones diferentes para almacenar datos en el navegador. ¿Cuál se adapta mejor a tus necesidades?

Las conexiones a Internet pueden ser débiles o inexistentes sobre la marcha, por lo que el soporte sin conexión y el rendimiento confiable son características comunes de las apps web progresivas. Incluso en entornos inalámbricos perfectos, el uso sensato del almacenamiento en caché y otras técnicas de almacenamiento pueden mejorar en gran medida la experiencia del usuario. Hay varias maneras de almacenar en caché los recursos estáticos de tu aplicación (HTML, JavaScript, CSS, imágenes, etc.) y datos (datos del usuario, artículos de noticias, etcétera). Pero ¿cuál es la mejor solución? ¿Cuánto puedes almacenar? ¿Cómo se evita que sea expulsada?

¿Qué debo usar?

Esta es una recomendación general para almacenar recursos:

IndexedDB y la API de Cache Storage son compatibles con todos los navegadores modernos. Ambos son asíncronos y no bloquearán el subproceso principal. Se puede acceder a ellos desde el objeto window, los trabajadores web y los service worker, lo que facilita su uso en cualquier parte del código.

¿Qué ocurre con otros mecanismos de almacenamiento?

Hay muchos otros mecanismos de almacenamiento disponibles en el navegador, pero su uso es limitado y pueden causar problemas de rendimiento significativos.

SessionStorage es específico de una 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 IndexedDB. Debe usarse con precaución, ya que es síncrono y bloqueará el subproceso principal. Se limita a aproximadamente 5 MB y solo puede contener cadenas. Debido a que es específica de una pestaña, no se puede acceder desde trabajadores web ni service worker.

Se debe evitar LocalStorage, ya que es síncrono y bloqueará el subproceso principal. Tiene un límite de aproximadamente 5 MB y solo puede contener cadenas. No se puede acceder a LocalStorage desde los trabajadores web ni desde los service worker.

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

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

La API de File System Access se diseñó para que los usuarios puedan leer y editar archivos en su sistema de archivos local con facilidad. El usuario debe otorgar permiso para que una página pueda leer o escribir en cualquier archivo local, y los permisos no se mantienen en todas las sesiones.

No se debe usar WebSQL, y el uso existente debe migrarse a IndexedDB. Se quitó la compatibilidad con casi todos los navegadores principales. El W3C dejó de mantener la especificación de Web SQL en 2010, sin tener planes de realizar más actualizaciones.

No se debe usar la caché de aplicaciones, y el uso existente se debe migrar a los service workers y la API de Cache. Está obsoleta y se quitará la compatibilidad con los navegadores en el futuro.

¿Cuánto puedo almacenar?

En resumen, mucho, al menos un par de cientos de megabytes y, posiblemente, cientos de gigabytes o más. Las implementaciones del navegador varían, pero la cantidad de almacenamiento disponible, por lo general, se basa 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. Otros navegadores basados en Chromium pueden ser diferentes.
    • En el modo Incógnito, Chrome reduce la cantidad de almacenamiento que puede usar un origen a aproximadamente un 5% del espacio total del disco.
    • Si el usuario habilitó la opción "Borrar cookies y datos del sitio al cerrar todas las ventanas" en Chrome, la cuota de almacenamiento se reducirá significativamente a un máximo de aproximadamente 300 MB.
    • Consulta PR #3896 para obtener detalles sobre la implementación de Chrome.
  • Internet Explorer 10 y las versiones posteriores pueden almacenar hasta 250 MB, y se le notificará al usuario cuando se hayan usado más de 10 MB.
  • Firefox permite que el navegador utilice hasta el 50% del espacio libre en disco. Un grupo eTLD+1 (p.ej., example.com, www.example.com y foo.bar.example.com) pueden usar hasta 2 GB. Puedes usar la API de StorageManager para determinar cuánto espacio aún está disponible.
  • Al parecer, Safari (para computadoras y dispositivos móviles) admite 1 GB aproximadamente. Cuando se alcance el límite, Safari le solicitará al usuario que lo aumente en incrementos de 200 MB. No pude encontrar documentación oficial al respecto.
    • Si se agrega una AWP a la pantalla principal en Safari para dispositivos móviles, parecerá crear un contenedor de almacenamiento nuevo y no se compartirá nada entre la AWP y la versión para dispositivos móviles de Safari. Una vez que se alcanza la cuota de una AWP instalada, no parece haber forma de solicitar almacenamiento adicional.

En el pasado, si un sitio superaba cierto umbral de datos almacenados, el navegador le solicitaba al usuario permiso para usar más datos. Por ejemplo, si el origen usó más de 50 MB, el navegador le pedirá al usuario que permita almacenar hasta 100 MB y, luego, volverá a preguntar en incrementos de 50 MB.

Actualmente, la mayoría de los navegadores modernos no solicitan solicitudes al usuario y permiten que un sitio use hasta el límite de su cuota asignada. La excepción parece ser Safari, que solicita permiso para aumentar la cuota asignada cuando se supera la cuota de almacenamiento. Si un origen intenta usar una cuota superior a la asignada, fallarán los intentos de escritura de datos.

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

En muchos navegadores, puedes usar la API de StorageManager para determinar la cantidad de almacenamiento disponible para el origen y cuánto espacio utiliza. 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.`);
}

StorageManager aún no está implementado en todos los navegadores, por lo que debes detectarlo antes de usarlo. Incluso cuando esté disponible, aún debes detectar errores de cuota superada (consulta a continuación). En algunos casos, es posible que la cuota disponible supere la cantidad real de almacenamiento disponible.

Inspección

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

Se agregó una función nueva en Chrome 88 que te permite anular la cuota de almacenamiento del sitio en el panel de 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, luego a Almacenamiento, habilita la casilla de verificación Simular cuota de almacenamiento personalizada y, luego, ingresa un número válido para simular la cuota de almacenamiento.

Panel de almacenamiento de Herramientas para desarrolladores

Mientras trabajaba en este artículo, escribí una herramienta simple para intentar usar la mayor cantidad de almacenamiento posible. Es una forma rápida y fácil de experimentar con diferentes mecanismos de almacenamiento y ver qué sucede cuando usas toda la cuota.

¿Cómo se puede manejar el exceso de cuota?

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

Tanto IndexedDB como la API de Cache arrojan una DOMError llamada QuotaExceededError cuando superas la cuota disponible.

IndexedDB

Si el origen excedió su cuota, los intentos de escritura en IndexedDB fallarán. Se llamará al controlador onabort() de la transacción y pasará un evento. El evento incluirá un DOMException en la propiedad de error. Si verificas el error name, 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 escritura en la API de Cache se rechazarán con una 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 categoriza en dos buckets: “Mejor esfuerzo” y “persistente”. El mejor esfuerzo significa que el navegador puede liberar el almacenamiento sin interrumpir al usuario, pero es menos duradero para datos críticos o a largo plazo. El almacenamiento persistente no se libera automáticamente cuando hay poco espacio. El usuario debe liberar manualmente este almacenamiento (a través de la configuración del navegador).

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

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

  • Los navegadores basados en Chromium comenzarán a expulsar datos cuando el navegador se quede sin espacio, lo que borrará todos los datos del sitio del origen que se usó menos recientemente primero y, luego, del siguiente, hasta que el navegador ya no supere el límite.
  • Internet Explorer 10 y versiones posteriores no expulsarán datos, pero impedirá que el origen escriba más.
  • Firefox comenzará a expulsar datos cuando se llene el espacio en el disco disponible, lo que borrará todos los datos del sitio del origen que se usó menos recientemente primero y, luego, el siguiente, hasta que el navegador ya no supere el límite.
  • Anteriormente, Safari no expulsaba datos, pero hace poco implementó un nuevo límite de siete días en todo el almacenamiento que admite escritura (consulta a continuación).

A partir de iOS y iPadOS 13.4 y Safari 13.1 en macOS, se aplica un límite de siete días a todo el almacenamiento que admita escritura de 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 AWP instaladas que se hayan agregado a la pantalla principal. Consulta Bloqueo completo de cookies de terceros y mucho más en el blog de WebKit para obtener más detalles.

Contenido adicional: 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 muy difícil para almacenar datos simples. A diferencia de la mayoría de las APIs 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 y control de versiones de esquemas) que incluye la biblioteca de IndexedDB.

Conclusión

Atrás quedaron los días de almacenamiento limitado y en el que se le solicitaba al usuario que almacene cada vez 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 la cantidad disponible y la que usaste. Además, con el almacenamiento continuo, puedes protegerlo para su expulsión, a menos que el usuario lo quite.

Recursos adicionales

Gracias

Queremos dar un agradecimiento especial a Jarryd Goodman, Phil Walton, Eiji Kitamura, Daniel Murphy, Darwin Huang, Josh Bell, Marijn Kruisselbrink y Victor Costan por revisar este artículo. Gracias a Eiji Kitamura, Addy Osmani y Marc Cohen, quienes escribieron los artículos originales en los que se basa. Eiji escribió una herramienta útil llamada abuser del almacenamiento del navegador 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 Francois Beaufort, que investigó sobre Safari para conocer sus límites de almacenamiento.

La imagen hero es de Guillaume Bolduc en Unsplash.