Высокопроизводительное хранилище для вашего приложения: API Storage Foundation

Веб-платформа всё чаще предлагает разработчикам инструменты, необходимые для создания оптимизированных высокопроизводительных веб-приложений. В частности, WebAssembly (Wasm) открыл путь к быстрым и мощным веб-приложениям, а такие технологии, как Emscripten, теперь позволяют разработчикам повторно использовать проверенный и проверенный код в вебе. Чтобы в полной мере раскрыть этот потенциал, разработчикам необходимо обладать такими же возможностями и гибкостью в области хранения данных.

Именно здесь на помощь приходит Storage Foundation API. Storage Foundation API — это новый быстрый и нестандартный API для хранения данных, который открывает новые и востребованные возможности использования в вебе, такие как реализация производительных баз данных и эффективное управление большими временными файлами. С помощью этого нового интерфейса разработчики могут «привнести собственное хранилище» в веб, сокращая разрыв в функциональности между веб- и платформенно-специфичным кодом.

API Storage Foundation разработан таким образом, чтобы напоминать очень простую файловую систему, что обеспечивает разработчикам гибкость, предоставляя универсальные, простые и производительные примитивы, на основе которых они могут создавать компоненты более высокого уровня. Приложения могут использовать наиболее подходящий инструмент для своих задач, находя оптимальный баланс между удобством использования, производительностью и надёжностью.

Зачем Интернету нужен еще один API для хранения данных?

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

  • Некоторые из этих вариантов явно не пересекаются с этим предложением, поскольку они позволяют хранить только очень небольшие объемы данных, например файлы cookie или API веб-хранилища, состоящий из механизмов sessionStorage и localStorage .
  • Другие варианты уже устарели по разным причинам, например API файлов и каталогов или WebSQL .
  • API доступа к файловой системе имеет схожую структуру, но предназначен для взаимодействия с файловой системой клиента и предоставления доступа к данным, которые могут находиться за пределами владения источника или даже браузера. Эта иная направленность влечет за собой более строгие требования к безопасности и более высокие затраты на производительность.
  • API IndexedDB можно использовать в качестве бэкенда для некоторых сценариев использования Storage Foundation API. Например, Emscripten включает IDBFS — персистентную файловую систему на основе IndexedDB. Однако, поскольку IndexedDB по сути является хранилищем типа «ключ-значение», его производительность существенно ограничена. Более того, прямой доступ к подразделам файла в IndexedDB ещё сложнее и медленнее.
  • Наконец, интерфейс CacheStorage широко поддерживается и настроен для хранения больших объемов данных, таких как ресурсы веб-приложений, но значения неизменяемы.

Storage Foundation API — это попытка закрыть все пробелы предыдущих вариантов хранения, обеспечивая производительное хранение изменяемых больших файлов, определенных в источнике приложения.

Предлагаемые варианты использования API Storage Foundation

Примеры сайтов, которые могут использовать этот API:

  • Приложения для продуктивной работы или творчества, работающие с большими объёмами видео-, аудио- или графических данных. Такие приложения могут выгружать сегменты на диск, а не хранить их в памяти.
  • Приложения, которые используют постоянную файловую систему, доступную из Wasm, и которым требуется большая производительность, чем может гарантировать IDBFS.

Что такое API Storage Foundation?

API состоит из двух основных частей:

  • Вызовы файловой системы , которые обеспечивают базовую функциональность для взаимодействия с файлами и путями к файлам.
  • Дескрипторы файлов , которые обеспечивают доступ для чтения и записи к существующему файлу.

Вызовы файловой системы

API Storage Foundation представляет новый объект storageFoundation , который находится на объекте window и включает в себя ряд функций:

  • storageFoundation.open(name) : открывает файл с заданным именем, если он существует, или создаёт новый файл в противном случае. Возвращает обещание, которое разрешается с открытым файлом.
  • storageFoundation.delete(name) : удаляет файл с указанным именем. Возвращает обещание, которое выполняется после удаления файла.
  • storageFoundation.rename(oldName, newName) : атомарно переименовывает файл из старого имени в новое. Возвращает обещание, которое разрешается при переименовании файла.
  • storageFoundation.getAll() : возвращает обещание, которое разрешается с помощью массива всех существующих имен файлов.
  • storageFoundation.requestCapacity(requestedCapacity) : запрашивает новую ёмкость (в байтах) для использования текущим контекстом выполнения. Возвращает обещание, которое было выполнено с оставшимся доступным объёмом ёмкости.
  • storageFoundation.releaseCapacity(toBeReleasedCapacity) : освобождает указанное количество байтов из текущего контекста выполнения и возвращает обещание, которое разрешается с оставшейся емкостью.
  • storageFoundation.getRemainingCapacity() : возвращает обещание, которое разрешается с доступной емкостью для текущего контекста выполнения.

Дескрипторы файлов

Работа с файлами происходит посредством следующих функций:

  • NativeIOFile.close() : закрывает файл и возвращает обещание, которое разрешается после завершения операции.
  • NativeIOFile.flush() : синхронизирует (т.е. очищает) состояние файла в памяти с устройством хранения и возвращает обещание, которое разрешается после завершения операции.
  • NativeIOFile.getLength() : возвращает обещание, которое разрешается с длиной файла в байтах.
  • NativeIOFile.setLength(length) : устанавливает длину файла в байтах и возвращает обещание, которое выполняется после завершения операции. Если новая длина меньше текущей, байты удаляются, начиная с конца файла. В противном случае файл расширяется нулевыми байтами.
  • NativeIOFile.read(buffer, offset) : считывает содержимое файла по указанному смещению через буфер, полученный в результате передачи указанного буфера, который затем остаётся отсоединённым. Возвращает NativeIOReadResult с переданным буфером и количеством успешно прочитанных байтов.

    NativeIOReadResult — это объект, состоящий из двух записей:

    • buffer : ArrayBufferView , который является результатом передачи буфера, переданного в read() . Он имеет тот же тип и длину, что и исходный буфер.
    • readBytes : Количество байтов, успешно считанных в buffer . Может быть меньше размера буфера в случае ошибки или если диапазон чтения выходит за пределы конца файла. Значение равно нулю, если диапазон чтения выходит за пределы конца файла.
  • NativeIOFile.write(buffer, offset) : записывает содержимое указанного буфера в файл по указанному смещению. Буфер передаётся до записи любых данных и поэтому остаётся отсоединённым. Возвращает NativeIOWriteResult с переданным буфером и количеством успешно записанных байтов. Файл будет расширен, если диапазон записи превысит его длину.

    NativeIOWriteResult — это объект, состоящий из двух записей:

    • buffer : ArrayBufferView , являющийся результатом передачи буфера, переданного в write() . Он имеет тот же тип и длину, что и исходный буфер.
    • writtenBytes : Количество байтов, успешно записанных в buffer . В случае ошибки это количество может быть меньше размера буфера.

Полные примеры

Чтобы сделать изложенные выше концепции более понятными, приведем два полных примера, которые проведут вас по различным этапам жизненного цикла файлов Storage Foundation.

Открытие, написание, чтение, закрытие

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

Открытие, листинг, удаление

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

Безопасность и разрешения

Команда Chromium разработала и реализовала API Storage Foundation, используя основные принципы, определенные в документе «Управление доступом к мощным функциям веб-платформы» , включая пользовательский контроль, прозрачность и эргономичность.

Следуя той же схеме, что и другие современные API хранилищ в интернете, доступ к API Storage Foundation привязан к источнику, то есть источник может получать доступ только к самостоятельно созданным данным. Доступ к нему также ограничен безопасными контекстами.

Пользовательский контроль

Квота хранилища будет использоваться для распределения доступа к дисковому пространству и предотвращения злоупотреблений. Память, которую вы хотите занять, необходимо предварительно запросить. Как и другие API хранилищ, пользователи могут очистить пространство, занимаемое Storage Foundation API, через браузер.

Полезные ссылки

Благодарности

API Storage Foundation был разработан и реализован Эмануэлем Кривоем и Ричардом Стотцем . Рецензентами статьи выступили Пит ЛеПейдж и Джо Медли .

Изображение героя предоставлено Маркусом Списке на Unsplash .