Almacenamiento de alto rendimiento para tu app: la API de Storage Foundation

Cada vez más, la plataforma web ofrece a los desarrolladores las herramientas que necesitan para crear aplicaciones web de alto rendimiento y ajustadas. En particular, WebAssembly (Wasm) abrió las puertas a aplicaciones web rápidas y potentes, mientras que tecnologías como Emscripten ahora permiten a los desarrolladores reutilizar código probado en la Web. Para aprovechar todo este potencial, los desarrolladores deben tener la misma potencia y flexibilidad en lo que respecta al almacenamiento.

Aquí es donde entra en juego la API de Storage Foundation. La API de Storage Foundation es una nueva API de almacenamiento rápida y sin tendencias que desbloquea casos de uso nuevos y muy solicitados para la Web, como la implementación de bases de datos de buen rendimiento y la administración elegante de archivos temporales grandes. Con esta nueva interfaz, los desarrolladores pueden "traer su propio almacenamiento" a la Web, lo que reduce la brecha de funciones entre el código web y el específico de la plataforma.

La API de Storage Foundation está diseñada para parecerse a un sistema de archivos muy básico, por lo que brinda flexibilidad a los desarrolladores, ya que proporciona primitivas genéricas, simples y de alto rendimiento en las que pueden compilar componentes de nivel superior. Las aplicaciones pueden aprovechar la mejor herramienta para sus necesidades, ya que les permite encontrar el equilibrio adecuado entre usabilidad, rendimiento y confiabilidad.

¿Por qué la Web necesita otra API de almacenamiento?

La plataforma web ofrece varias opciones de almacenamiento para desarrolladores, cada una de las cuales se crea teniendo en cuenta casos de uso específicos.

  • Algunas de estas opciones claramente no se superponen con esta propuesta, ya que solo permiten almacenar cantidades muy pequeñas de datos, como las cookies o la API de Web Storage, que consiste en los mecanismos sessionStorage y localStorage.
  • Otras opciones ya dejaron de estar disponibles por varios motivos, como la API de File and Directory Entries o WebSQL.
  • La API de File System Access tiene una plataforma de API similar, pero se usa para interactuar con el sistema de archivos del cliente y proporcionar acceso a datos que pueden estar fuera del origen o incluso de la propiedad del navegador. Este enfoque diferente conlleva consideraciones de seguridad más estrictas y costos de rendimiento más altos.
  • La API de IndexedDB se puede usar como backend para algunos de los casos de uso de la API de Storage Foundation. Por ejemplo, Emscripten incluye IDBFS, un sistema de archivos persistentes basado en IndexedDB. Sin embargo, dado que IndexedDB es, en esencia, un almacén de pares clave-valor, tiene limitaciones de rendimiento significativas. Además, el acceso directo a las subsecciones de un archivo es aún más difícil y lento en IndexedDB.
  • Por último, la interfaz de CacheStorage es muy compatible y está ajustada para almacenar datos de gran tamaño, como recursos de aplicaciones web, pero los valores son inmutables.

La API de Storage Foundation es un intento de cerrar todos los vacíos de las opciones de almacenamiento anteriores, ya que permite el almacenamiento con buen rendimiento de archivos grandes mutables definidos dentro del origen de la aplicación.

Casos de uso sugeridos para la API de Storage Foundation

Estos son algunos ejemplos de sitios que pueden usar esta API:

  • Son apps de productividad o creatividad que funcionan con grandes cantidades de datos de video, audio o imágenes. Estas apps pueden descargar segmentos al disco en lugar de retenerlos en la memoria.
  • Apps que dependen de un sistema de archivos persistentes al que se puede acceder desde Wasm y que necesitan más rendimiento de lo que IDBFS puede garantizar

¿Qué es la API de Storage Foundation?

La API consta de dos partes principales:

  • Llamadas al sistema de archivos, que proporcionan una funcionalidad básica para interactuar con archivos y rutas de acceso a archivos.
  • Identificadores de archivo, que proporcionan acceso de lectura y escritura a un archivo existente.

Llamadas al sistema de archivos

La API de Storage Foundation presenta un objeto nuevo, storageFoundation, que se encuentra en el objeto window y que incluye varias funciones:

  • storageFoundation.open(name): Abre el archivo con el nombre determinado (si existe) y, de lo contrario, crea un archivo nuevo. Muestra una promesa que se resuelve con el archivo abierto.
  • storageFoundation.delete(name): Quita el archivo con el nombre determinado. Muestra una promesa que se resuelve cuando se borra el archivo.
  • storageFoundation.rename(oldName, newName): Cambia el nombre del archivo del nombre anterior al nuevo de forma atómica. Muestra una promesa que se resuelve cuando se cambia el nombre del archivo.
  • storageFoundation.getAll(): Muestra una promesa que se resuelve con un array de todos los nombres de archivos existentes.
  • storageFoundation.requestCapacity(requestedCapacity): Solicita una nueva capacidad (en bytes) para el uso en el contexto de ejecución actual. Muestra una promesa que se resolvió con la cantidad restante de capacidad disponible.
  • storageFoundation.releaseCapacity(toBeReleasedCapacity): libera la cantidad especificada de bytes del contexto de ejecución actual y muestra una promesa que se resuelve con la capacidad restante.
  • storageFoundation.getRemainingCapacity(): Muestra una promesa que se resuelve con la capacidad disponible para el contexto de ejecución actual.

Identificadores de archivo

El trabajo con archivos se realiza mediante las siguientes funciones:

  • NativeIOFile.close(): Cierra un archivo y muestra una promesa que se resuelve cuando finaliza la operación.
  • NativeIOFile.flush(): Sincroniza (es decir, limpia) el estado en la memoria de un archivo con el dispositivo de almacenamiento y muestra una promesa que se resuelve cuando se completa la operación.
  • NativeIOFile.getLength(): Muestra una promesa que se resuelve con la longitud del archivo en bytes.
  • NativeIOFile.setLength(length): Establece la longitud del archivo en bytes y muestra una promesa que se resuelve cuando se completa la operación. Si la longitud nueva es menor que la actual, se quitan los bytes a partir del final del archivo. De lo contrario, el archivo se extiende con bytes con valor cero.
  • NativeIOFile.read(buffer, offset): Lee el contenido del archivo en el desplazamiento determinado a través de un búfer que es el resultado de la transferencia del búfer determinado, que luego se separa. Muestra un NativeIOReadResult con el búfer transferido y la cantidad de bytes que se leyeron de forma correcta.

    Un NativeIOReadResult es un objeto que consta de dos entradas:

    • buffer: Es un ArrayBufferView, que es el resultado de transferir el búfer que se pasa a read(). Es del mismo tipo y longitud que el búfer de origen.
    • readBytes: Es la cantidad de bytes que se leyeron correctamente en buffer. Puede ser menor que el tamaño del búfer si se produce un error o si el rango de lectura se extiende más allá del final del archivo. Se establece en cero si el rango de lectura supera el final del archivo.
  • NativeIOFile.write(buffer, offset): escribe el contenido del búfer determinado en el archivo en el desplazamiento determinado. El búfer se transfiere antes de que se escriban los datos y, por lo tanto, se deja separado. Muestra un NativeIOWriteResult con el búfer transferido y la cantidad de bytes que se escribieron de forma correcta. Se extenderá el archivo si el rango de escritura excede su longitud.

    Un NativeIOWriteResult es un objeto que consta de dos entradas:

    • buffer: Es un ArrayBufferView que es el resultado de transferir el búfer que se pasa a write(). Es del mismo tipo y longitud que el búfer de origen.
    • writtenBytes: Es la cantidad de bytes que se escribieron correctamente en buffer. Puede ser menor que el tamaño del búfer si se produce un error.

Ejemplos completos

Para que los conceptos presentados anteriormente sean más claros, aquí hay dos ejemplos completos que te guían por las diferentes etapas del ciclo de vida de los archivos de Storage Foundation.

Apertura, escritura, lectura, cierre

// Open a file (creating it if needed).
const file = await storageFoundation.open('test_file');
try {
  // Request 100 bytes of capacity for this context.
  await storageFoundation.requestCapacity(100);

  const writeBuffer = new Uint8Array([64, 65, 66]);
  // Write the buffer at offset 0. After this operation, `result.buffer`
  // contains the transferred buffer and `result.writtenBytes` is 3,
  // the number of bytes written. `writeBuffer` is left detached.
  let result = await file.write(writeBuffer, 0);

  const readBuffer = new Uint8Array(3);
  // Read at offset 1. `result.buffer` contains the transferred buffer,
  // `result.readBytes` is 2, the number of bytes read. `readBuffer` is left
  // detached.
  result = await file.read(readBuffer, 1);
  // `Uint8Array(3) [65, 66, 0]`
  console.log(result.buffer);
} finally {
  file.close();
}

Apertura, enumeración, eliminación

// Open three different files (creating them if needed).
await storageFoundation.open('sunrise');
await storageFoundation.open('noon');
await storageFoundation.open('sunset');
// List all existing files.
// `["sunset", "sunrise", "noon"]`
await storageFoundation.getAll();
// Delete one of the three files.
await storageFoundation.delete('noon');
// List all remaining existing files.
// `["sunrise", "noon"]`
await storageFoundation.getAll();

Demostración

Puedes jugar con la demostración de la API de Storage Foundation en la incorporación que aparece a continuación. Crea archivos, cámbiales el nombre, escribe en ellos y lee desde ellos, y observa la capacidad disponible que solicitaste actualización a medida que realizas cambios. Puedes encontrar el código fuente de la demostración en Glitch.

Seguridad y permisos

El equipo de Chromium diseñó e implementó la API de Storage Foundation según los principios básicos definidos en Cómo controlar el acceso a las funciones potentes de la plataforma web, incluidos el control de usuario, la transparencia y la ergonomía.

Siguiendo el mismo patrón que otras APIs de almacenamiento modernas en la Web, el acceso a la API de Storage Foundation está vinculado al origen, lo que significa que un origen solo puede acceder a datos autocreados. También se limita a contextos seguros.

Control de usuarios

La cuota de almacenamiento se usará para distribuir el acceso al espacio en el disco y evitar abusos. Primero, se debe solicitar la memoria que quieres ocupar. Al igual que otras APIs de almacenamiento, los usuarios pueden liberar el espacio que ocupa la API de Storage Foundation a través de su navegador.

Vínculos útiles

Agradecimientos

Emanuel Krivoy y Richard Stotz especificaron la API de Storage Foundation. Pete LePage y Joe Medley revisaron este artículo.

Imagen hero de Markus Spiske en Unsplash.