ウェブ向けのストレージ

ブラウザにデータを保存する方法は、さまざまなものがあります。ニーズに最適なものはどれですか。

外出先ではインターネット接続が不安定になったり、接続できなくなったりすることがあります。そのため、プログレッシブ ウェブアプリでは、オフライン サポートと信頼性の高いパフォーマンスが一般的な機能となっています。完璧なワイヤレス環境であっても、キャッシュ保存などのストレージ手法を適切に使用することで、ユーザー エクスペリエンスを大幅に改善できます。静的なアプリケーション リソース(HTML、JavaScript、CSS、画像など)とデータ(ユーザーデータ、ニュース記事など)をキャッシュに保存する方法はいくつかあります。しかし、最適なソリューションはどれでしょうか。保存できる容量はどれくらいですか?強制排除されないようにするにはどうすればよいですか?

何を使用すればよいですか?

リソースを保存する際の一般的な推奨事項は次のとおりです。

IndexedDB、OPFS、Cache Storage API は、すべての最新のブラウザでサポートされています。これらは非同期であり、メインスレッドをブロックしません(ただし、ウェブワーカーでのみ使用できる OPFS の同期バリアントもあります)。window オブジェクト、ウェブワーカー、サービス ワーカーからアクセスできるため、コード内のどこでも使用できます。

他のストレージ メカニズムはどうですか?

ブラウザで使用できるストレージ メカニズムは他にもいくつかありますが、用途が限られており、パフォーマンスに重大な問題を引き起こす可能性があります。

SessionStorage はタブ固有であり、タブの有効期間にスコープされます。IndexedDB キーなど、セッション固有の少量の情報を保存するのに役立ちます。同期的であり、メインスレッドをブロックするため、慎重に使用する必要があります。約 5 MB に制限されており、文字列のみを含めることができます。タブ固有であるため、ウェブワーカーやサービス ワーカーからはアクセスできません。

LocalStorage は同期的であり、メインスレッドをブロックするため、使用を避ける必要があります。約 5 MB に制限されており、文字列のみを含めることができます。LocalStorage には、ウェブワーカーやサービス ワーカーからアクセスできません。

Cookie には用途がありますが、ストレージには使用しないでください。Cookie はすべての HTTP リクエストで送信されるため、少量のデータ以外を保存すると、すべてのウェブリクエストのサイズが大幅に増加します。これらは同期的であり、ウェブワーカーからアクセスできません。LocalStorage や SessionStorage と同様に、Cookie は文字列のみに制限されます。

File System Access API は、ユーザーがローカル ファイル システム上のファイルを読み取り、編集できるように設計されています。ページがローカル ファイルの読み取りまたは書き込みを行う前に、ユーザーが権限を付与する必要があります。ファイル ハンドルが IndexedDB にキャッシュされていない限り、権限はセッション間で保持されません。File System Access API は、ファイルを開いて変更し、変更をファイルに保存する可能性があるエディタなどのユースケースに最適です。

File System API と FileWriter API には、サンドボックス化されたファイル システムに対してファイルの読み取りと書き込みを行うメソッドが用意されています。非同期ではありますが、Chromium ベースのブラウザでのみ使用可能であるため、おすすめしません。

保存できる容量はどれくらいですか?

簡単に言うと、大量です。少なくとも数百メガバイト、場合によっては数百ギガバイト以上になります。ブラウザの実装はさまざまですが、利用可能なストレージ容量は通常、デバイスで利用可能なストレージ容量に基づきます。

  • Chrome では、ブラウザがディスク容量全体の最大 80% を使用できます。オリジンは、合計ディスク容量の最大 60% を使用できます。StorageManager API を使用すると、利用可能な最大割り当てを特定できます。他の Chromium ベースのブラウザでは異なる場合があります。
    • シークレット モードでは、オリジンが使用できるストレージ容量が、ディスク容量全体の約 5% に制限されます。
    • ユーザーが Chrome で [すべてのウィンドウを閉じるときに Cookie とサイトデータを削除する] を有効にしている場合、保存容量の上限は大幅に削減され、最大で約 300 MB になります。
  • Firefox では、ブラウザがディスク空き容量の最大 50% を使用できます。eTLD+1 グループ(example.com、www.example.com、foo.bar.example.com など)は、最大 2 GB を使用できます。StorageManager API を使用すると、使用可能な容量を確認できます。
  • Safari(パソコンとモバイルの両方)では、約 1 GB が許可されているようです。上限に達すると、Safari はユーザーにプロンプトを表示し、上限を 200 MB ずつ増やします。これに関する公式ドキュメントは見つかりませんでした。
    • モバイル Safari で PWA をホーム画面に追加すると、新しいデータ保存用のコンテナが作成され、PWA とモバイル Safari の間で何も共有されません。インストールされた PWA の割り当てに達すると、追加のストレージをリクエストする方法はないようです。

以前は、サイトが保存されたデータの特定のしきい値を超えると、ブラウザはユーザーに、より多くのデータを使用する権限を付与するよう求めていました。たとえば、オリジンが 50 MB を超えて使用した場合、ブラウザはユーザーに最大 100 MB の保存を許可するよう求め、その後 50 MB 単位で再度確認します。

現在、ほとんどの最新のブラウザでは、ユーザーにプロンプトを表示せず、サイトに割り当てられた割り当て量まで使用できます。例外は Safari のようで、保存容量の上限を超えると、数量に基づく割り当てを増やす許可を求めるプロンプトが表示されます。オリジンが割り当てられた割り当てを超えて使用しようとすると、それ以降のデータの書き込みは失敗します。

利用可能な保存容量を確認するにはどうすればよいですか?

多くのブラウザでは、StorageManager API を使用して、オリジンで使用可能なストレージ容量と、使用しているストレージ容量を特定できます。IndexedDB と Cache API で使用されている合計バイト数を報告し、使用可能な残りのストレージ容量を概算できるようにします。

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

割り当て超過エラーをキャッチする必要があります(下記参照)。場合によっては、使用可能な割り当てが、実際に使用可能なストレージ容量を超えることがあります。

検証

開発中は、ブラウザの DevTools を使用してさまざまなストレージ タイプを検査し、保存されているすべてのデータを消去できます。

Chrome 88 に、[Storage] ペインでサイトの保存容量の上限をオーバーライドできる新機能が追加されました。この機能を使用すると、さまざまなデバイスをシミュレートし、ディスク容量が少ないシナリオでアプリの動作をテストできます。[Application]、[Storage] の順に移動し、[Simulate custom storage quota] チェックボックスをオンにして、保存容量をシミュレートする有効な数値を入力します。

割り当て超過への対処方法

割り当てを超過した場合はどうすればよいですか?最も重要なのは、QuotaExceededError かどうかに関係なく、書き込みエラーを常にキャッチして処理することです。その後、アプリの設計に応じて、処理方法を決定します。たとえば、長期間アクセスされていないコンテンツを削除する、サイズに基づいてデータを削除する、削除するコンテンツをユーザーが選択できるようにする、などの方法があります。

IndexedDB と Cache API の両方で、利用可能な割り当てを超えると、QuotaExceededError という名前の DOMError がスローされます。

IndexedDB

オリジンが割り当てを超過している場合、IndexedDB への書き込みは失敗します。トランザクションの onabort() ハンドラが呼び出され、イベントが渡されます。このイベントの error プロパティには DOMException が含まれます。エラー name を確認すると、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
  }
};

Cache API

オリジンが割り当てを超過している場合、Cache API への書き込みは QuotaExceededError DOMException で拒否されます。

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

エビクションの仕組み

ウェブ ストレージは、「ベスト エフォート」と「永続」の 2 つのバケットに分類されます。ベスト エフォートとは、ユーザーを中断することなくブラウザでストレージをクリアできることを意味しますが、長期的なデータや重要なデータに対する耐久性は低くなります。空き容量が少なくなっても、永続ストレージは自動的に消去されません。ユーザーは、このストレージを手動で(ブラウザ設定から)消去する必要があります。

デフォルトでは、サイトのデータ(IndexedDB、Cache API などを含む)はベスト エフォート カテゴリに分類されます。つまり、サイトが永続ストレージをリクエストしていない限り、ブラウザは独自の判断でサイトデータを削除する可能性があります(デバイスのストレージが不足している場合など)。

ベスト エフォートの強制排除ポリシーは次のとおりです。

  • Chromium ベースのブラウザでは、ブラウザの空き容量がなくなると、最も長い間使われていないオリジンからすべてのサイトデータが削除され、ブラウザが制限を超えなくなるまで、次のオリジンが削除されます。
  • Firefox は、利用可能なディスク容量がいっぱいになると、データの削除を開始します。最も長い間使用されていないオリジンからすべてのサイトデータを消去し、ブラウザが制限を超えなくなるまで、次のオリジンを消去します。
  • Safari は以前はデータを削除しませんでしたが、最近、すべての書き込み可能なストレージに 7 日間の上限を新たに実装しました(下記参照)。

iOS と iPadOS 13.4、および macOS の Safari 13.1 以降では、IndexedDB、サービス ワーカー登録、Cache API など、すべてのスクリプト書き込み可能ストレージに 7 日間の上限が設定されています。つまり、ユーザーがサイトを操作しない場合、Safari は Safari の使用から 7 日後にキャッシュからすべてのコンテンツを削除します。この強制排除ポリシーは、ホーム画面に追加されたインストール済みの PWA には適用されません。詳しくは、WebKit ブログの フル サードパーティ Cookie のブロックなど をご覧ください。

ストレージ バケット

Storage Buckets API の中心的な考え方は、サイトに複数のストレージ バケットを作成する機能を付与することです。ブラウザは、各バケットを他のバケットとは独立して削除することを選択できます。これにより、デベロッパーは強制排除の優先順位を指定して、最も価値のあるデータが削除されないようにすることができます。

おまけ: IndexedDB のラッパーを使用する理由

IndexedDB は、使用前に多くの設定が必要な低レベルの API であり、複雑さの低いデータを保存する場合には特に面倒です。最新の Promise ベースの API とは異なり、イベントベースです。IndexedDB 用の idb などの Promise ラッパーは、強力な機能の一部を隠しますが、さらに重要なのは、IndexedDB ライブラリに付属する複雑なメカニズム(トランザクション、スキーマ バージョニングなど)を隠すことです。

おまけ: SQLite Wasm

Web SQL が非推奨となり、Chrome から削除された後、Google は、人気の SQLite データベースのメンテナーと協力して、SQLite に基づく Web SQL の代替手段を提供しました。使用方法の詳細については、オリジン プライベート ファイル システムを基盤とするブラウザの SQLite Wasm をご覧ください。

まとめ

ストレージが限られていた時代は終わり、ユーザーに保存するデータを増やすよう促す必要はなくなりました。サイトは、実行に必要なすべてのリソースとデータを効果的に保存できます。StorageManager API を使用すると、利用可能な容量と使用済みの容量を確認できます。永続ストレージを使用すると、ユーザーが削除しない限り、強制排除から保護できます。

参考情報

ありがとうございました

このガイドのレビューにご協力いただいた Jarryd Goodman、Phil Walton、Eiji Kitamura、Daniel Murphy、Darwin Huang、Josh Bell、Marijn Kruisselbrink、Victor Costan の各氏に感謝いたします。この記事のベースとなった元の記事を執筆した Eiji Kitamura、Addy Osmani、Marc Cohen に感謝します。Eiji は、現在の動作を検証するのに役立つ Browser Storage Abuser という便利なツールを作成しました。これにより、可能な限り多くのデータを保存し、ブラウザのストレージ上限を確認できます。Safari のストレージ上限を調査してくれた François Beaufort 氏、オリジン プライベート ファイル システム、ストレージ バケット、SQLite Wasm に関する情報を追加し、2024 年にコンテンツ全体を更新してくれた Thomas Steiner 氏に感謝いたします。