Armazenamento de alto desempenho para seu app: a API Storage Foundation

A plataforma da Web oferece cada vez mais aos desenvolvedores as ferramentas necessárias para criar aplicativos de alto desempenho ajustados para a Web. Mais especificamente, o WebAssembly (Wasm) abriu as portas para aplicativos da Web rápidos e avançados, enquanto tecnologias como o Emscripten agora permitem que os desenvolvedores reutilizem códigos testados e testados na Web. Para realmente aproveitar esse potencial, os desenvolvedores precisam ter a mesma capacidade e flexibilidade de armazenamento.

É aí que entra a API Storage Foundation. A API Storage Foundation é uma nova API de armazenamento rápida e simples que abre casos de uso novos e muito solicitados para a Web, como a implementação de bancos de dados de alto desempenho e o gerenciamento otimizado de grandes arquivos temporários. Com essa nova interface, os desenvolvedores podem "trazer o próprio armazenamento" para a Web, reduzindo a lacuna de recursos entre o código específico da Web e da plataforma.

A API Storage Foundation foi projetada para se assemelhar a um sistema de arquivos muito básico para que os desenvolvedores tenham flexibilidade, fornecendo primitivos genéricos, simples e de alta performance para que eles possam criar componentes de nível superior. Os aplicativos podem aproveitar a melhor ferramenta para suas necessidades, encontrando o equilíbrio certo entre usabilidade, desempenho e confiabilidade.

Por que a Web precisa de outra API de armazenamento?

A plataforma da Web oferece várias opções de armazenamento para desenvolvedores, e cada uma delas é criada com casos de uso específicos em mente.

  • Algumas dessas opções claramente não se sobrepõem a essa proposta, já que elas permitem que apenas quantidades muito pequenas de dados sejam armazenadas, como cookies ou a API Web Storage que consiste nos mecanismos sessionStorage e localStorage.
  • Outras opções já foram descontinuadas por vários motivos, como a API File and Directory Entries ou o WebSQL.
  • A API File System Access tem uma superfície de API semelhante, mas ela serve para interagir com o sistema de arquivos do cliente e fornecer acesso a dados que podem estar fora da origem ou até mesmo de propriedade do navegador. Esse foco diferente vem com considerações de segurança mais rigorosas e custos de desempenho mais altos.
  • A API IndexedDB pode ser usada como um back-end para alguns dos casos de uso da API Storage Foundation. Por exemplo, o Emscripten inclui o IDBFS, um sistema de arquivos permanente baseado no IndexedDB. No entanto, como o IndexedDB é fundamentalmente um armazenamento de chave-valor, ele tem limitações significativas de desempenho. Além disso, acessar diretamente as subseções de um arquivo é ainda mais difícil e mais lento no IndexedDB.
  • Por fim, a interface CacheStorage tem ampla compatibilidade e é ajustada para armazenar dados de grande porte, como recursos de aplicativos da Web, mas os valores são imutáveis.

A API Storage Foundation é uma tentativa de preencher todas as lacunas das opções de armazenamento anteriores, permitindo o armazenamento com bom desempenho de arquivos grandes mutáveis definidos na origem do aplicativo.

Casos de uso sugeridos para a API Storage Foundation

Exemplos de sites que podem usar essa API incluem:

  • Apps de produtividade ou criatividade que operam em grandes quantidades de dados de vídeo, áudio ou imagem. Esses apps podem descarregar segmentos no disco em vez de mantê-los na memória.
  • Apps que dependem de um sistema de arquivos permanente acessível pelo Wasm e que precisam de mais desempenho do que o IDBFS pode garantir.

O que é a API Storage Foundation?

A API tem duas partes principais:

  • Chamadas do sistema de arquivos, que fornecem a funcionalidade básica para interagir com arquivos e caminhos
  • Identificadores de arquivos, que fornecem acesso de leitura e gravação a um arquivo existente.

Chamadas do sistema de arquivos

A API Storage Foundation apresenta um novo objeto, storageFoundation, que reside no objeto window e que inclui várias funções:

  • storageFoundation.open(name): abre o arquivo com o nome informado, se existir, e cria um novo arquivo. Retorna uma promessa que é resolvida com o arquivo aberto.
  • storageFoundation.delete(name): remove o arquivo com o nome fornecido. Retorna uma promessa que se resolve quando o arquivo é excluído.
  • storageFoundation.rename(oldName, newName): renomeia atomicamente o arquivo do nome antigo para o novo. Retorna uma promessa que é resolvida quando o arquivo é renomeado.
  • storageFoundation.getAll(): retorna uma promessa que é resolvida com uma matriz de todos os nomes de arquivos atuais.
  • storageFoundation.requestCapacity(requestedCapacity): solicita a nova capacidade (em bytes) para uso pelo contexto de execução atual. Retorna uma promessa que foi resolvida com a quantidade restante de capacidade disponível.
  • storageFoundation.releaseCapacity(toBeReleasedCapacity): libera o número especificado de bytes do contexto de execução atual e retorna uma promessa que é resolvida com a capacidade restante.
  • storageFoundation.getRemainingCapacity(): retorna uma promessa que é resolvida com a capacidade disponível para o contexto de execução atual.

Identificadores de arquivos

O trabalho com arquivos acontece por meio das seguintes funções:

  • NativeIOFile.close(): fecha um arquivo e retorna uma promessa que é resolvida quando a operação é concluída.
  • NativeIOFile.flush(): sincroniza (ou seja, limpa) o estado na memória de um arquivo com o dispositivo de armazenamento e retorna uma promessa que é resolvida quando a operação é concluída.
  • NativeIOFile.getLength(): retorna uma promessa que é resolvida com o tamanho do arquivo em bytes.
  • NativeIOFile.setLength(length): define o tamanho do arquivo em bytes e retorna uma promessa que é resolvida quando a operação é concluída. Se o novo tamanho for menor que o atual, os bytes serão removidos a partir do fim do arquivo. Caso contrário, o arquivo será estendido com bytes de valor zero.
  • NativeIOFile.read(buffer, offset): lê o conteúdo do arquivo no deslocamento especificado por meio de um buffer que é o resultado da transferência do buffer especificado, que é então removido. Retorna um NativeIOReadResult com o buffer transferido e o número de bytes que foram lidos com êxito.

    Um NativeIOReadResult é um objeto que consiste em duas entradas:

    • buffer: um ArrayBufferView, que é o resultado da transferência do buffer transmitido para read(). Ele é do mesmo tipo e comprimento do buffer de origem.
    • readBytes: o número de bytes que foram lidos com sucesso em buffer. Esse valor pode ser menor que o tamanho do buffer, se ocorrer um erro ou se o intervalo de leitura se estender além do final do arquivo. Ele é definido como zero se o intervalo de leitura for além do fim do arquivo.
  • NativeIOFile.write(buffer, offset): grava o conteúdo do buffer indicado no arquivo no deslocamento especificado. O buffer é transferido antes da gravação dos dados e, portanto, fica separado. Retorna um NativeIOWriteResult com o buffer transferido e o número de bytes gravados. O arquivo será estendido se o intervalo de gravação exceder o comprimento.

    Um NativeIOWriteResult é um objeto que consiste em duas entradas:

    • buffer: um ArrayBufferView que é o resultado da transferência do buffer transmitido para write(). É do mesmo tipo e comprimento do buffer de origem.
    • writtenBytes: o número de bytes gravados em buffer. Esse valor pode ser menor que o tamanho do buffer se ocorrer um erro.

Exemplos completos

Para esclarecer os conceitos introduzidos acima, veja dois exemplos completos que orientam você pelos diferentes estágios no ciclo de vida dos arquivos do Storage Foundation.

Abertura, escrita, leitura e fechamento

// 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();
}

Abertura, listagem e exclusão

// 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();

Demonstração

Você pode testar a demonstração da API Storage Foundation na incorporação abaixo. Crie, renomeie, grave e leia arquivos e veja a capacidade disponível que você solicitou atualização à medida que faz as alterações. Veja o código-fonte da demonstração no Glitch.

Segurança e permissões

A equipe do Chromium projetou e implementou a API Storage Foundation usando os princípios básicos definidos em Como controlar o acesso a recursos avançados da plataforma da Web, incluindo controle do usuário, transparência e ergonomia.

Seguindo o mesmo padrão de outras APIs de armazenamento modernas na Web, o acesso à API Storage Foundation é vinculado à origem, o que significa que uma origem só pode acessar dados criados por você. Isso também é limitado a contextos seguros.

Controle do usuário

A cota de armazenamento será usada para distribuir o acesso ao espaço em disco e para evitar abusos. A memória que você quer ocupar precisa ser solicitada primeiro. Assim como outras APIs de armazenamento, os usuários podem limpar o espaço ocupado pela API Storage Foundation no navegador.

Links úteis

Agradecimentos

A API Storage Foundation foi especificada e implementada por Emanuel Krivoy e Richard Stotz. Este artigo foi revisado por Pete LePage e Joe Medley.

Imagem principal de Markus Spiske no Unsplash (links em inglês).