アプリ向けの高パフォーマンス ストレージ: Storage Foundation API

ウェブ プラットフォームには、ウェブ向けの高性能アプリケーションを微調整して構築するために必要なツールがますます増えています。特に、WebAssembly(Wasm)により、高速で強力なウェブ アプリケーションの開発が可能になりました。また、Emscripten などのテクノロジーにより、デベロッパーはウェブ上で実証済みのコードを再利用できるようになりました。この可能性を最大限に活用するには、ストレージに関しても同じパワーと柔軟性が開発者に必要です。

ここで Storage Foundation API が役立ちます。Storage Foundation API は、高速で非依存の新しいストレージ API です。パフォーマンスの高いデータベースの実装や、大規模な一時ファイルの適切な管理など、ウェブで求められている新しいユースケースを実現します。この新しいインターフェースにより、デベロッパーは「独自のストレージ」をウェブに持ち込むことができ、ウェブとプラットフォーム固有のコード間の機能のギャップを縮小できます。

Storage Foundation API は非常に基本的なファイル システムに似せて設計されているため、デベロッパーは、より高いレベルのコンポーネントを構築できる汎用性の高いシンプルで高性能なプリミティブを利用でき、柔軟性が増します。アプリケーションは、ニーズに最適なツールを活用して、ユーザビリティ、パフォーマンス、信頼性のバランスを適切に取ることができます。

ウェブに別のストレージ API が必要な理由

ウェブ プラットフォームには、デベロッパー向けのさまざまなストレージ オプションが用意されています。これらのオプションは、それぞれ特定のユースケースを念頭に置いて構築されています。

  • これらのオプションの一部は、CookiesessionStorage メカニズムと localStorage メカニズムからなる Web Storage API など、非常に少量のデータしか保存できないため、この提案と明らかに重複していません。
  • 他のオプションは、File and Directory Entries APIWebSQL など、さまざまな理由ですでに非推奨になっています。
  • File System Access API は API サーフェスは似ていますが、クライアントのファイル システムとインターフェースを形成し、オリジンの外部、またはブラウザの所有権外にある可能性のあるデータへのアクセスを提供します。この異なる重点により、セキュリティの考慮事項が厳しくなり、パフォーマンス費用が高くなります。
  • IndexedDB API は、Storage Foundation API の一部ユースケースのバックエンドとして使用できます。たとえば、Emscripten には IndexedDB ベースの永続ファイル システムである IDBFS が含まれています。ただし、IndexedDB は基本的にキーバリュー ストアであるため、パフォーマンスに大きな制限があります。さらに、IndexedDB では、ファイルのサブセクションに直接アクセスするのはさらに困難で時間がかかります。
  • 最後に、CacheStorage インターフェースは広くサポートされており、ウェブ アプリケーション リソースなどの大規模なデータを保存するように調整されていますが、値は不変です。

Storage Foundation API は、アプリケーションのオリジン内で定義された変更可能な大規模なファイルの高速ストレージを可能にすることで、以前のストレージ オプションのギャップをすべて解消しようとするものです。

Storage Foundation API の推奨ユースケース

この API を使用するサイトの例を次に示します。

  • 大量の動画、音声、画像データを処理する生産性向上アプリや創造性向上アプリ。このようなアプリでは、セグメントをメモリに保持するのではなく、ディスクにオフロードできます。
  • Wasm からアクセスできる永続ファイル システムに依存し、IDBFS で保証できる以上のパフォーマンスを必要とするアプリ。

Storage Foundation API とは何ですか?

この API は主に 2 つの部分で構成されています。

  • ファイル システム呼び出し: ファイルとファイルパスを操作するための基本的な機能を提供します。
  • ファイルハンドル。既存のファイルへの読み取りと書き込みのアクセス権を提供します。

ファイル システム呼び出し

Storage Foundation API には、window オブジェクトに存在し、次の関数を含む新しいオブジェクト storageFoundation が導入されています。

  • storageFoundation.open(name): 指定された名前のファイルが存在する場合は開き、存在しない場合は新しいファイルを作成します。開いたファイルで解決される Promise を返します。
  • storageFoundation.delete(name): 指定された名前のファイルを削除します。ファイルが削除されたときに解決される Promise を返します。
  • storageFoundation.rename(oldName, newName): ファイルの名前を古い名前から新しい名前にアトミックに変更します。ファイル名が変更されたときに解決される Promise を返します。
  • storageFoundation.getAll(): 既存のすべてのファイル名の配列で解決されるプロミスを返します。
  • storageFoundation.requestCapacity(requestedCapacity): 現在の実行コンテキストで使用するための新しい容量(バイト単位)をリクエストします。使用可能な残りの容量で解決される Promise を返します。
  • storageFoundation.releaseCapacity(toBeReleasedCapacity): 現在の実行コンテキストから指定されたバイト数を解放し、残りの容量で解決する Promise を返します。
  • storageFoundation.getRemainingCapacity(): 現在の実行コンテキストで使用可能な容量で解決される Promise を返します。

ファイル ハンドル

ファイルの操作は、次の関数を使用して行います。

  • NativeIOFile.close(): ファイルを閉じて、オペレーションの完了時に解決する Promise を返します。
  • NativeIOFile.flush(): ファイルのメモリ内状態をストレージ デバイスと同期(フラッシュ)し、オペレーションの完了時に解決する Promise を返します。
  • NativeIOFile.getLength(): ファイルの長さ(バイト単位)で解決する Promise を返します。
  • NativeIOFile.setLength(length): ファイルの長さをバイト単位で設定し、オペレーションの完了時に解決する Promise を返します。新しい長さが現在の長さより短い場合、バイトはファイルの末尾から削除されます。それ以外の場合は、ファイルはゼロ値のバイト数で拡張されます。
  • NativeIOFile.read(buffer, offset): 指定されたバッファの転送結果であるバッファを介して、指定されたオフセットのファイルの内容を読み取ります。このバッファは切断されたままになります。転送されたバッファと、正常に読み取られたバイト数を含む NativeIOReadResult を返します。

    NativeIOReadResult は、次の 2 つのエントリで構成されるオブジェクトです。

    • buffer: ArrayBufferViewread() に渡されたバッファの転送結果です。ソースバッファと同じ型と長さです。
    • readBytes: buffer に正常に読み込まれたバイト数。エラーが発生した場合や、読み取り範囲がファイルの末尾を超える場合、この値はバッファサイズよりも小さくなることがあります。読み取り範囲がファイルの末尾を超えている場合はゼロに設定されます。
  • NativeIOFile.write(buffer, offset): 指定されたバッファの内容を、指定されたオフセットのファイルに書き込みます。バッファはデータが書き込まれる前に転送されるため、切断されたままになります。転送されたバッファと、正常に書き込まれたバイト数を含む NativeIOWriteResult を返します。書き込み範囲が長さを超えると、ファイルが拡張されます。

    NativeIOWriteResult は、次の 2 つのエントリで構成されるオブジェクトです。

    • buffer: write() に渡されたバッファの転送結果である ArrayBufferView。ソースバッファと同じ型と長さです。
    • writtenBytes: buffer に正常に書き込まれたバイト数。エラーが発生した場合、この値はバッファサイズよりも小さくなることがあります。

完全な例

上記のコンセプトを明確にするために、Storage Foundation ファイルのライフサイクルのさまざまなステージを説明する 2 つの完全な例を示します。

開く、書き込む、読み取る、閉じる

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

デモ

以下の埋め込みで Storage Foundation API のデモをお試しいただけます。ファイルの作成、名前変更、書き込み、読み取りを行い、変更時に更新をリクエストした利用可能な容量を確認できます。このデモのソースコードは Glitch で確認できます。

セキュリティと権限

Chromium チームは、強力なウェブ プラットフォーム機能へのアクセスを制御するで定義されているコア プリンシプル(ユーザー制御、透明性、人間工学など)を使用して、Storage Foundation API を設計して実装しました。

ウェブ上の他の最新のストレージ API と同じパターンに従い、Storage Foundation API へのアクセスはオリジンに限定されます。つまり、オリジンは自己作成データにのみアクセスできます。また、安全なコンテキストに限定されています。

ユーザー コントロール

ストレージ割り当ては、ディスク容量へのアクセスを分散し、不正使用を防ぐために使用されます。占有するメモリは、まずリクエストする必要があります。他のストレージ API と同様に、ユーザーはブラウザから Storage Foundation API によって使用されているスペースを消去できます。

関連情報

謝辞

Storage Foundation API は、Emanuel KrivoyRichard Stotz によって仕様化および実装されました。この記事は、Pete LePageJoe Medley が確認しました。

ヒーロー画像: Markus SpiskeUnsplash より。