Где хранить данные на вебе?
Tags: webdev, database
Published: 2020-05-13 15:44:13

Вольный перевод статьи [Storage for the web](https://web.dev/storage-for-the-web/)

Существует много разных вариантов хранения данных в браузере. Какой из них лучше всего подходит для ваших нужд?

Подключения к интернету могут быть нестабильными или вообще отсутствовать, поэтому автономная поддержка и надежная производительность являются общепринятыми функциями прогрессивных веб-приложений. Даже в идеальных беспроводных средах разумное использование кэширования и других методов хранения может существенно улучшить взаимодействие с пользователем. Существует несколько способов кеширования статических ресурсов приложения (HTML, JavaScript, CSS, изображений и т. Д.) и данных (данные пользователя, новостные статьи и т.д.). Но какое решение лучше? Сколько можно хранить? Как вы предотвращаете их вытеснение?

## Что я должен использовать? 

Вот общая рекомендация для хранения ресурсов:
* Для сетевых ресурсов, необходимых для загрузки вашего приложения и файлового контента, используйте [Cache Storage API](https://web.dev/cache-api-quick-guide/) (часть [Service Wrokers](https://developers.google.com/web/fundamentals/primers/service-workers)).
* Для остальных данных используйте [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) (с [promises](https://www.npmjs.com/package/idb) оберткой).

IndexedDB и Cache Storage API поддерживаются в каждом современном браузере. Они оба асинхронные и не будут блокировать основной поток. Они доступны из `window` объекта, web woker'ов и service wroker'ов, что упрощает их использование в любом месте кода.

## А что насчет других механизмов хранения?

В браузере доступно несколько других механизмов хранения, но они имеют ограниченное использование и могут вызвать значительные проблемы с производительностью.

[SessionStorage](https://developer.mozilla.org/en/docs/Web/API/Window/sessionStorage) зависит от вкладки и ограничен временем жизни вкладки. Он может быть полезнен для хранения небольших объемов информации, специфичной для сеанса, например ключа IndexedDB. Его следует использовать с осторожностью, поскольку он является синхронным и блокирует основной поток. Он ограничен до 5 МБ и может содержать только строки. Поскольку он зависит от вкладки, он недоступен web worker'ам или service worker'ам.

[LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) следует избегать, поскольку он является синхронным и блокирует основной поток. Он ограничен до 5 МБ и может содержать только строки. LocalStorage недоступен web worker'ам или service worker'ам.

Файлы [cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) используются, но не должны использоваться для хранения. Файлы cookie отправляются с каждым HTTP-запросом, поэтому хранение чего-либо большего, чем небольшой объем данных, значительно увеличит размер каждого веб-запроса. Они синхронны и недоступны для web worker'ов. Как и LocalStorage и SessionStorage, файлы cookie ограничиваются только строками.

[File System API](https://developer.mozilla.org/en-US/docs/Web/API/File_and_Directory_Entries_API/Introduction) и FileWriter API предоставляют методы для чтения и записи файлов в изолированную файловую систему. Хотя они асинхроны, они не рекомендуются, потому что они доступны только в Chromium браузерах.

[Native File System API](https://web.dev/native-file-system/) был разработан, чтобы облегчить пользователям чтение и редактирование файлов в их локальной файловой системе. Пользователь должен предоставить разрешение, прежде чем страница сможет читать или записывать в любой локальный файл, и разрешения не сохраняются между сеансами.

WebSQL не должен использоваться, а существующее использование должно быть перенесено в IndexedDB. Поддержка [была удалена](https://caniuse.com/#feat=sql-storage) практически из всех основных браузеров. W3C [прекратил поддержку спецификации Web SQL](https://www.w3.org/TR/webdatabase/) в 2010 году, не планируя дальнейших обновлений.

Application Cache не должен использоваться, а существующее использование должно быть перенесено на service worker'ов и Cache API. Он устарели, и поддержка будет удалена из браузеров в будущем.

## Сколько я могу хранить? 

Короче, **много**, по крайней мере, пара сотен мегабайт, и, возможно, сотни гигабайт или больше. Реализации браузеров различаются, но объем доступного хранилища обычно зависит от объема хранилища, доступного на устройстве.

* Chrome позволяет браузеру использовать до 60% общего дискового пространства. Вы можете использовать [StorageManager API](https://web.dev/storage-for-the-web/#check) для определения максимальной доступной квоты. Другие браузеры на основе Chromium могут позволить браузеру использовать больше памяти.
* Internet Explorer 10 и более поздние версии могут хранить до 250 МБ и уведомит пользователю, если используется более 10 МБ.
* Firefox позволяет [использовать до 2 ГБ](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Browser_storage_limits_and_eviction_criteria#Storage_limits). Вы можете использовать [StorageManager API](https://web.dev/storage-for-the-web/#check), чтобы определить, сколько места еще доступно.
* Safari (как для компьютеров, так и для мобильных устройств) позволяет воспользоваться до 1 ГБ. Когда лимит будет достигнут, Safari предложит пользователю увеличить его с шагом 200 МБ. Я не смог найти никакой официальной документации по этому вопросу.

В прошлом, если сайт превышал определенный порог хранимых данных, браузер предлагал пользователю предоставить разрешение на использование большего количества данных. Например, если сайт использовал более 50 МБ, браузер предложит пользователю разрешить ему сохранить до 100 МБ, а затем запросит снова с шагом 50 МБ.

Сегодня большинство современных браузеров не будут спрашивать пользователя и позволят сайту использовать свою выделенную квоту. Исключением является Safari, который запрашивает 750 МБ и запрашивает разрешение на хранение до 1,1 ГБ. Если источник попытается использовать больше, чем его выделенная квота, дальнейшие попытки записи данных потерпят неудачу.

## Как я могу проверить, сколько места доступно?

Во [многих браузерах](https://caniuse.com/#feat=mdn-api_storagemanager) вы можете использовать [StorageManager API](https://web.dev/storage-for-the-web/#check), чтобы определить объем памяти, доступный для сайта, и объем памяти, который он использует. Он сообщает об общем количестве байтов, используемых IndexedDB и Cache API, и позволяет рассчитать приблизительное оставшееся доступное пространство для хранения.

```javascript
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 [еще не реализован во всех браузерах](https://caniuse.com/#feat=mdn-api_storagemanager), поэтому вы должны проверить его, прежде чем использовать. Даже когда он доступен, вы все равно должны ловить ошибки превышения квоты (см. Ниже). В некоторых случаях доступная квота может превышать фактический объем доступного хранилища.

> Другие браузеры на основе Chromium могут учитывать количество свободного места при сообщении о доступной квоте. Chrome этого не делает и всегда будет давать 60% фактического размера диска. Это помогает уменьшить возможность определения размера cross origin ресурсов.

### Инспектирование

Во время разработки вы можете использовать DevTools вашего браузера для проверки различных типов хранилищ и легко очистить все сохраненные данные.

![](https://webdev.imgix.net/storage-for-the-web/storage-test-tool.png)

Работая над этой статьей, я написал [простой инструмент](), чтобы попытаться максимально быстро использовать как можно больше памяти. Это быстрый и простой способ поэкспериментировать с различными механизмами хранения и посмотреть, что происходит, когда вы используете всю свою квоту.

## Как разобраться с превышением квоты? 

Что делать, когда вы превышаете квоту? Самое главное, вы всегда должны отлавливать и обрабатывать ошибки записи, будь то `QuotaExceededError` или что-то еще. Затем, в зависимости от архитектуры вашего приложения, решите, как с этим справиться. Например, удалите контент, к которому долгое время не обращались, удалите данные в зависимости от размера или предоставьте пользователям возможность выбрать то, что они хотят удалить.

IndexedDB и Cache API выдают `DOMError` с именем `QuotaExceededError`, когда вы превысили доступную квоту.

### IndexedDB

Если сайт превысил свою квоту, попытки записи в IndexedDB будут неудачными. Будет вызван обработчик транзакции `onabort()`, передающий событие. Событие будет включать `DOMException`. Проверка имени ошибки вернет `QuotaExceededError`.

```javascript
const transaction = idb.transaction(['entries'], 'readwrite');
transaction.onabort = function(event) {
  const error = event.target.error; // DOMException
  if (error.name == 'QuotaExceededError') {
    // Fallback code goes here
  }
};
```

### Cache API

Если источник превысил свою квоту, попытки записи в Cache API будут отклонены с исключением `DOMException` `QuotaExceededError`.

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

## Как работает вытеснение? 

Веб-хранилище подразделяется на две группы: «Best effort» и «Persistent Best Effort означает, что хранилище может быть очищено браузером без прерывания работы пользователя, но оно менее долговечно для долговременных или критических данных. Persistent хранилище не очищается автоматически при низком уровне хранилища. Пользователь должен вручную очистить это хранилище (через настройки браузера).

По умолчанию данные сайта (включая IndexedDB, Cache API и т.д.) попадают в категорию Best Effort, что означает, что, если сайт [не запросил постоянное хранилище](https://developers.google.com/web/updates/2016/06/persistent-storage), браузер может исключить данные сайта по своему усмотрению, например, когда память устройства невелика.

Политика вытеснения для Best Effort:
* Браузеры на основе Chromium начнут извлекать данные, когда в браузере не хватит места, сначала удаляя все данные сайта из наименее использованного источника, до тех пор, пока браузер не превысит лимит.
* Internet Explorer 10+ не будет удалять данные и будет препятствовать записи.
* Firefox начнет вытеснять данные, когда будет заполнено доступное дисковое пространство, сначала удаляя все данные сайта из наименее недавно использованного источника, до тех пор, пока браузер не превысит лимит.
* Ранее Safari не высвобождала данные, но недавно внедрила новую семидневный мешок для всего доступного для записи хранилища (см. Ниже).

>Начиная с iOS и iPadOS 13.4 и Safari 13.1 на macOS, существует семь дней для всех хранилищ с возможностью записи сценариев, включая IndexedDB, регистрацию service worker'ов и Cache API. Это означает, что Safari будет удалять весь контент из кэша после семи дней использования Safari, если пользователь не взаимодействует с сайтом. Эта политика вытеснения не распространяется на установленные PWA, которые были добавлены на домашний экран. См. [Полная блокировка сторонних файлов cookie и многое другое](https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/) в блоге WebKit для получения полной информации.

## Бонус: Зачем использовать враппер для IndexedDB? 

IndexedDB - это API низкого уровня, который требует значительных настроек перед использованием, что может быть особенно болезненным для хранения простых данных. В отличие от большинства современных API, основанных на promises, он основан на событиях (events). Promise врапперы, такие как idb для IndexedDB, скрывают некоторые мощные функции, но, что более важно, скрывают сложный механизм (например, транзакции, управление версиями схемы), который поставляется с библиотекой IndexedDB.

## Заключение 

Прошли времена ограниченного хранения и опрашивания пользователя хранить все больше и больше данных. Сайты могут эффективно хранить все ресурсы и данные, необходимые для их работы. Используя [StorageManager API](https://web.dev/storage-for-the-web/#check), вы можете определить, сколько вам доступно и сколько вы использовали. А с помощью Persistent хранилища, если пользователь не удалит его, вы сможете защитить его от вытеснения.

### Дополнительные ресурсы 
* [IndexedDB Best Practices](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/indexeddb-best-practices)
* [Chrome Web Storage and Quota Concepts](https://docs.google.com/document/d/19QemRTdIxYaJ4gkHYf2WWBNPbpuZQDNMpUVf8dQxj4U/preview)