ウェブ向けのストレージ

ブラウザにデータを保存する方法は数多くあります。どちらがニーズに合っていますか?

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

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

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

その他のストレージ メカニズムについて

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

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

LocalStorage は同期的であり、メインスレッドをブロックするため、使用しないことをおすすめします。サイズの上限は約 5 MB で、文字列のみを含めることができます。Web Worker や Service Worker から LocalStorage にアクセスすることはできません。

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

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

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

保存できる量

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

  • Chrome では、ブラウザが合計ディスク容量の最大 80% を使用できます。オリジンは、合計ディスク容量の最大 60% を使用できます。StorageManager API を使用して、使用可能な最大割り当てを特定できます。他の Chromium ベースのブラウザでは異なる場合があります。
    • シークレット モードでは、Chrome はオリジンが使用できるストレージの量を合計ディスク容量の約 5% に減らします。
    • Chrome で [すべてのウィンドウを閉じると Cookie とサイトデータを削除する] を有効にしている場合、保存容量の割り当ては最大で約 300 MB に大幅に減らされます。
  • Firefox では、ブラウザが空きディスク容量の最大 50% を使用できます。eTLD+1 グループ(例: example.comwww.example.comfoo.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 では、[ストレージ] ペインでサイトのストレージ クォータをオーバーライドできる新機能が追加されました。この機能を使用すると、さまざまなデバイスをシミュレートし、ディスク可用性が低いシナリオでアプリの動作をテストできます。[アプリケーション]、[ストレージ] の順に移動し、[カスタムの保存容量をシミュレート] チェックボックスをオンにして、有効な数値を入力して保存容量をシミュレートします。

このガイドの作成中に、できるだけ多くのストレージをすばやく使用しようとするシンプルなツールを作成しました。さまざまなストレージ メカニズムを試し、割り当てをすべて使用した場合にどうなるかをすばやく確認できます。

割り当て超過を処理する方法

割り当てを超過した場合、どうすればよいですか?最も重要なことは、書き込みエラーが 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、Service Worker 登録、Cache API など、すべてのスクリプト書き込み可能なストレージに 7 日間の上限があります。つまり、ユーザーがサイトを操作しなかった場合、Safari の使用から 7 日後に Safari はキャッシュからすべてのコンテンツを削除します。このエビクション ポリシーは、ホーム画面に追加されているインストール済みの 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 の代替手段を提供しました。使用方法の詳細については、Origin プライベート ファイル システムを基盤とするブラウザでの 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 氏と、2024 年のコンテンツ全体の更新に関する情報を追加して、Thomas Steiner に感謝します。

ヒーロー画像は Unsplash の Guillaume Bolduc によるものです。