Armazenamento para a Web

Há muitas opções diferentes para armazenar dados no navegador. Qual é a melhor para suas necessidades?

As conexões com a Internet podem ser instáveis ou inexistentes durante o deslocamento do usuário. É por isso que o suporte off-line e o desempenho confiável são recursos comuns em apps da Web progressivos. Mesmo em ambientes sem fio perfeitos, o uso criterioso de armazenamento em cache e outras técnicas de armazenamento pode melhorar substancialmente a experiência do usuário. Há várias maneiras de armazenar em cache os recursos estáticos do aplicativo (HTML, JavaScript, CSS, imagens etc.) e dados (dados do usuário, artigos de notícias etc.). Mas qual é a melhor solução? Quanto é possível armazenar? Como evitar a remoção?

Confira uma recomendação geral para armazenar recursos:

IndexedDB, o OPFS e a Cache Storage API são compatíveis com todos os navegadores modernos. Eles são assíncronos e não bloqueiam a linha de execução principal. No entanto, há também uma variante síncrona do OPFS disponível exclusivamente em workers da Web. Eles são acessíveis pelo objeto window, workers da Web e workers de serviço, o que permite usá-los em qualquer lugar do código.

E os outros mecanismos de armazenamento?

Há vários outros mecanismos de armazenamento disponíveis no navegador, mas eles têm uso limitado e podem causar problemas de desempenho significativos.

SessionStorage é específico da guia e tem o escopo da duração da guia. Ele pode ser útil para armazenar pequenas quantidades de informações específicas da sessão, por exemplo, uma chave IndexedDB. Ele precisa ser usado com cuidado, porque é síncrono e bloqueia a linha de execução principal. Ele é limitado a cerca de 5 MB e pode conter apenas strings. Como é específico da guia, ele não é acessível por workers da Web ou workers de serviço.

O LocalStorage deve ser evitado porque é síncrono e bloqueia a linha de execução principal. Ele é limitado a cerca de 5 MB e pode conter apenas strings. O LocalStorage não pode ser acessado por web workers ou service workers.

Os cookies têm usos, mas não devem ser usados para armazenamento. Os cookies são enviados com cada solicitação HTTP. Portanto, armazenar algo além de uma pequena quantidade de dados aumentará significativamente o tamanho de cada solicitação da Web. Elas são síncronas e não podem ser acessadas por web workers. Assim como LocalStorage e SessionStorage, os cookies são limitados a strings.

A API File System Access foi projetada para permitir que os usuários leiam e editem arquivos no sistema de arquivos local. O usuário precisa conceder permissão antes que uma página possa ler ou gravar em qualquer arquivo local. Além disso, as permissões não são mantidas entre sessões, a menos que um identificador de arquivo seja armazenado em cache no IndexedDB. A API File System Access é mais adequada para casos de uso como editores, em que você precisa abrir um arquivo, modificá-lo e, possivelmente, salvar as alterações no arquivo.

A API File System e a API FileWriter oferecem métodos para ler e gravar arquivos em um sistema de arquivos em modo de proteção. Embora seja assíncrono, ele não é recomendado porque está disponível apenas em navegadores baseados no Chromium.

Quanto posso armazenar?

Em resumo, muito, pelo menos algumas centenas de megabytes e, potencialmente, centenas de gigabytes ou mais. As implementações do navegador variam, mas a quantidade de armazenamento disponível geralmente depende do armazenamento disponível no dispositivo.

  • O Chrome permite que o navegador use até 80% do espaço total em disco. Uma origem pode usar até 60% do espaço total em disco. É possível usar a API StorageManager para determinar a cota máxima disponível. Outros navegadores baseados em Chromium podem ser diferentes.
    • No modo de navegação anônima, o Chrome reduz a quantidade de armazenamento que uma origem pode usar para aproximadamente 5% do espaço total em disco.
    • Se o usuário tiver ativado a opção "Limpar cookies e dados de sites ao fechar todas as janelas" no Chrome, a cota de armazenamento será reduzida significativamente para um máximo de aproximadamente 300 MB.
  • O Firefox permite que o navegador use até 50% do espaço livre em disco. Um grupo de eTLD+1 (por exemplo, example.com, www.example.com e foo.bar.example.com) podem usar até 2 GB. Você pode usar a API StorageManager para determinar quanto espaço ainda está disponível.
  • O Safari (para computador e dispositivo móvel) parece permitir cerca de 1 GB. Quando o limite for atingido, o Safari vai pedir permissão ao usuário, aumentando o limite em incrementos de 200 MB. Não consegui encontrar nenhuma documentação oficial sobre isso.
    • Se um PWA for adicionado à tela inicial no Safari para dispositivos móveis, ele criará um novo contêiner de armazenamento, e nada será compartilhado entre o PWA e o Safari para dispositivos móveis. Depois que a cota de um PWA instalado é atingida, não parece haver nenhuma maneira de solicitar mais armazenamento.

Antes, quando um site excedia um determinado limite de dados armazenados, o navegador solicitava que o usuário concedesse permissão para usar mais dados. Por exemplo, se a origem usasse mais de 50 MB, o navegador pediria ao usuário permissão para armazenar até 100 MB e depois pediria novamente em incrementos de 50 MB.

Atualmente, a maioria dos navegadores modernos não solicita autorização ao usuário e permite que um site use até a cota atribuída. A exceção parece ser o Safari, que pede permissão para aumentar a cota alocada quando a cota de armazenamento é excedida. Se uma origem tentar usar mais do que a cota atribuída, as tentativas de gravar dados vão falhar.

Como posso verificar quanto espaço de armazenamento está disponível?

Em muitos navegadores, é possível usar a API StorageManager para determinar a quantidade de armazenamento disponível para a origem e a quantidade de armazenamento que ela está usando. Ela informa o número total de bytes usados pelo IndexedDB e pela API Cache e permite calcular o espaço de armazenamento restante aproximado disponível.

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

É preciso detectar erros de cota excedida (confira abaixo). Em alguns casos, é possível que a cota disponível exceda a quantidade real de armazenamento disponível.

Inspecionar

Durante o desenvolvimento, você pode usar as ferramentas do desenvolvedor do navegador para inspecionar os diferentes tipos de armazenamento e limpar todos os dados armazenados.

Um novo recurso foi adicionado no Chrome 88 que permite substituir a cota de armazenamento do site no painel de armazenamento. Esse recurso permite simular diferentes dispositivos e testar o comportamento dos apps em cenários de baixa disponibilidade de disco. Acesse Application e Storage, ative a caixa de seleção Simulate custom storage quota e insira qualquer número válido para simular a cota de armazenamento.

Enquanto trabalhava neste guia, criei uma ferramenta simples para tentar usar rapidamente o máximo de armazenamento possível. É uma maneira rápida de testar diferentes mecanismos de armazenamento e conferir o que acontece quando você usa toda a cota.

Como lidar com o excesso de cota?

O que você deve fazer quando ultrapassar a cota? O mais importante é sempre detectar e processar erros de gravação, seja um QuotaExceededError ou outra coisa. Depois, dependendo do design do app, decida como lidar com ele. Por exemplo, exclua conteúdo que não foi acessado há muito tempo, remova dados com base no tamanho ou ofereça uma maneira de os usuários escolherem o que querem excluir.

Tanto o IndexedDB quanto a API Cache geram uma DOMError chamada QuotaExceededError quando você excede a cota disponível.

IndexedDB

Se a origem exceder a cota, as tentativas de gravação no IndexedDB falharão. O gerenciador onabort() da transação será chamado, transmitindo um evento. O evento vai incluir um DOMException na propriedade de erro. A verificação do erro name vai retornar 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 Cache

Se a origem tiver excedido a cota, as tentativas de gravação na API Cache serão rejeitadas com um DOMException QuotaExceededError.

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
  }
}

Como funciona a remoção?

O armazenamento da Web é categorizado em dois buckets: "Best Effort" e "Persistent". Melhor esforço significa que o armazenamento pode ser apagado pelo navegador sem interromper o usuário, mas é menos durável para dados de longo prazo ou críticos. O armazenamento persistente não é apagado automaticamente quando a capacidade de armazenamento fica reduzida. O usuário precisa limpar esse armazenamento manualmente (nas configurações do navegador).

Por padrão, os dados de um site (incluindo IndexedDB, API Cache etc.) se enquadram na categoria de melhor esforço, o que significa que, a menos que um site tenha solicitado armazenamento permanente, o navegador pode remover os dados do site a seu critério, por exemplo, quando o armazenamento do dispositivo está baixo.

A política de remoção para o melhor esforço é:

  • Os navegadores baseados em Chromium começam a remover dados quando o espaço deles acaba, limpando todos os dados do site da origem menos usada primeiro, depois da próxima, até que o navegador não exceda mais o limite.
  • O Firefox começará a remover os dados quando o espaço em disco disponível for preenchido, limpando primeiro todos os dados do site da origem menos usada recentemente e, em seguida, a próxima, até que o navegador não ultrapasse o limite.
  • O Safari não removia os dados, mas recentemente implementou um novo limite de sete dias em todo o armazenamento gravável (consulte abaixo).

No iOS e iPadOS 13.4 e Safari 13.1 no macOS, há um limite de sete dias para todo o armazenamento gravável de scripts, incluindo o IndexedDB, o registro de service workers e a API Cache. Isso significa que o Safari vai remover todo o conteúdo do cache após sete dias de uso se o usuário não interagir com o site. Essa política de remoção não se aplica a PWAs instalados que foram adicionados à tela inicial. Consulte Bloqueio completo de cookies de terceiros e muito mais no blog do WebKit para conferir todos os detalhes.

Buckets de armazenamento

A ideia principal da API Storage Buckets é permitir que os sites criem vários buckets de armazenamento, em que o navegador pode excluir cada bucket de forma independente. Isso permite que os desenvolvedores especifiquem a priorização de exclusão para garantir que os dados mais valiosos não sejam excluídos.

Bônus: por que usar um wrapper para IndexedDB

A IndexedDB é uma API de baixo nível que exige uma configuração significativa antes do uso, o que pode ser particularmente difícil para armazenar dados de baixa complexidade. Ao contrário da maioria das APIs modernas baseadas em promessas, ela é baseada em eventos. Os wrappers de promessas, como idb para IndexedDB, ocultam alguns dos recursos avançados, mas, mais importante, ocultam a maquinaria complexa (por exemplo, transações, controle de versões de esquemas) que vem com a biblioteca IndexedDB.

Bônus: SQLite Wasm

Depois que o Web SQL foi descontinuado e removido do Chrome, o Google trabalhou com os mantenedores do famoso banco de dados SQLite para oferecer um substituto do Web SQL baseado no SQLite. Leia SQLite Wasm no navegador com suporte do Origin Private File System para saber como usá-lo.

Conclusão

O armazenamento limitado e a necessidade de armazenar cada vez mais dados são coisas do passado. Os sites podem armazenar de forma eficaz todos os recursos e dados necessários para funcionamento. Com a API StorageManager, é possível determinar quanto está disponível para você e quanto foi usado. E com o armazenamento persistente, a menos que o usuário o remova, você pode proteger contra a remoção.

Outros recursos

Obrigado

Agradecimentos especiais a Jarryd Goodman, Phil Walton, Eiji Kitamura, Daniel Murphy, Darwin Huang, Josh Bell, Marijn Kruisselbrink e Victor Costan pela revisão deste guia. Agradecemos a Eiji Kitamura, Addy Osmani e Marc Cohen, que escreveram os artigos originais que serviram de base para este. Eiji criou uma ferramenta útil chamada Browser Storage Abuser, que foi útil para validar o comportamento atual. Ela permite armazenar o máximo de dados possível e conferir os limites de armazenamento no navegador. Agradecemos a François Beaufort, que fez a pesquisa no Safari para descobrir os limites de armazenamento, e a Thomas Steiner por adicionar informações sobre o sistema de arquivos particular de origem, buckets de armazenamento, SQLite Wasm e uma atualização geral do conteúdo em 2024.

A imagem principal é de Guillaume Bolduc no Unsplash.