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 para a Web. Em especial, 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 na Web. Para realmente aproveitar esse potencial, os desenvolvedores precisam ter o mesmo poder e flexibilidade quando se trata de armazenamento.

É aqui que entra a API Storage Foundation. A API Storage Foundation é uma nova API de armazenamento rápida e discreta que desbloqueia casos de uso novos e muito solicitados para a Web, como a implementação de bancos de dados de alto desempenho e o gerenciamento prático 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 plataforma e a Web.

A API Storage Foundation foi projetada para se parecer com um sistema de arquivos muito básico. Portanto, ela oferece flexibilidade aos desenvolvedores com primitivos genéricos, simples e de alto desempenho para a criação de componentes de nível superior. Os aplicativos podem aproveitar a melhor ferramenta para as próprias 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, cada uma criada com casos de uso específicos em mente.

  • Algumas dessas opções não se sobrepõem a essa proposta, já que elas permitem o armazenamento de apenas quantidades muito pequenas de dados, 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 plataforma de API semelhante, mas o uso dela é interagir com o sistema de arquivos do cliente e fornecer acesso a dados que podem estar fora da origem ou até mesmo do navegador. Esse foco diferente vem com considerações de segurança mais estritas e custos de desempenho mais altos.
  • A API IndexedDB pode ser usada como back-end para alguns dos casos de uso da API Storage Foundation. Por exemplo, o Emscripten inclui o IDBFS, um sistema de arquivos persistente baseado em IndexedDB. No entanto, como o IndexedDB é basicamente um armazenamento de chave-valor, ele tem limitações significativas de desempenho. Além disso, o acesso direto às subseções de um arquivo é ainda mais difícil e mais lento no IndexedDB.
  • Por fim, a interface CacheStorage é amplamente compatível 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 eficiente 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 criatividade ou criatividade que operam em grandes quantidades de dados de vídeo, áudio ou imagem. Esses apps podem descarregar segmentos para o 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 oferecem recursos básicos para interagir com arquivos e caminhos de arquivos.
  • Identificadores de arquivo, que fornecem acesso de leitura e gravação a um arquivo existente.

Chamadas do sistema de arquivos

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

  • storageFoundation.open(name): abre o arquivo com o nome indicado (se houver) e, caso contrário, cria um novo arquivo. Retorna uma promessa que é resolvida com o arquivo aberto.
  • storageFoundation.delete(name): remove o arquivo com o nome informado. Retorna uma promessa que é resolvida quando o arquivo é excluído.
  • storageFoundation.rename(oldName, newName): renomeia o arquivo do nome antigo para o novo atomicamente. 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 existentes.
  • storageFoundation.requestCapacity(requestedCapacity): solicita 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 arquivo

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 de um arquivo na memória com o dispositivo de armazenamento e retorna uma promessa que será resolvida quando a operação for 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 comprimento for menor que o atual, os bytes serão removidos a partir do final do arquivo. Caso contrário, o arquivo será estendido com bytes com 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 sucesso.

    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 que o buffer de origem.
    • readBytes: o número de bytes que foram lidos em buffer. Esse valor poderá ser menor que o tamanho do buffer se ocorrer um erro ou se o intervalo de leitura se estender além do fim do arquivo. Ele será definido como zero se o intervalo de leitura estiver além do fim do arquivo.
  • NativeIOFile.write(buffer, offset): grava o conteúdo do buffer especificado no arquivo no deslocamento especificado. O buffer é transferido antes de qualquer dado ser gravado e, portanto, é removido. 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 dele.

    Um NativeIOWriteResult é um objeto que consiste em duas entradas:

    • buffer: um ArrayBufferView, que é o resultado da transferência do buffer transmitido para write(). Ele é do mesmo tipo e comprimento que o buffer de origem.
    • writtenBytes: o número de bytes que foram gravados no buffer. Ele poderá ser menor que o tamanho do buffer se ocorrer um erro.

Exemplos completos

Para deixar os conceitos introduzidos acima mais claros, confira estes dois exemplos completos que orientam você nos diferentes estágios no ciclo de vida dos arquivos do Storage Foundation.

Abertura, escrita, leitura, 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, 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

Teste a demonstração da API Storage Foundation na incorporação abaixo. Criar, renomear, gravar e ler os arquivos e conferir a capacidade disponível que você solicitou como atualização à medida que faz as mudanças. Confira 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 fundamentais definidos em Como controlar o acesso a recursos avançados da plataforma Web, incluindo controle do usuário, transparência e ergonomia.

Seguindo o mesmo padrão de outras APIs modernas de armazenamento na Web, o acesso à API Storage Foundation é limitado à origem, o que significa que uma origem só pode acessar dados criados automaticamente. Ela também se limita 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 liberar 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 via Markus Spiske no Unsplash (links em inglês).