ウェブ向けのストレージ

ブラウザにデータを保存する方法はたくさんあります。どの方法がニーズに最適でしょうか?

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

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

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

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

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

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

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

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

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 ベースの他のブラウザでは異なる場合があります。
    • シークレット モードでは、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 のようです。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.`);
}

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

検証

開発中は、ブラウザのデベロッパー ツールを使用して、さまざまなストレージ タイプを検査し、保存されているすべてのデータをクリアできます。

Chrome 88 で、[ストレージ] パネルでサイトのストレージ割り当てをオーバーライドできる新機能が追加されました。この機能を使用すると、さまざまなデバイスをシミュレートし、ディスク容量が少ないシナリオでのアプリの動作をテストできます。[アプリケーション]、[ストレージ] に移動し、 [カスタムの保存容量をシミュレート] チェックボックスをオンにして、任意の有効な数値を入力して 保存容量をシミュレートします。

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

割り当てを超えた場合の処理方法

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

IndexedDB と Cache API はどちらも、使用可能な割り当てを超えると、QuotaExceededError という名前の DOMError をスローします。

IndexedDB

オリジンが割り当てを超えている場合、IndexedDB への書き込みは失敗します。トランザクションの onabort() ハンドラが呼び出され、イベントが渡されます。 イベントには、エラー プロパティに 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 は Safari の使用から 7 日後にキャッシュからすべてのコンテンツを削除します。この削除ポリシーは、ホーム画面に追加されたインストール済みの PWA には適用されません 。詳しくは、WebKit ブログの Full Third-Party Cookie Blocking and Moreをご覧ください。

ストレージ バケット

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

ボーナス: IndexedDB にラッパーを使用する理由

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

ボーナス: SQLite Wasm

Web SQL が非推奨となり Chrome から削除された後、Google は人気の SQLite データベースのメンテナーと協力して、SQLite に基づく Web SQL の代替機能を提供しました。 使用方法について詳しくは、Origin Private File System を基盤とするブラウザの 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 年にオリジン プライベート ファイル システム、ストレージ バケット、SQLite Wasm に関する情報を追加し、コンテンツ全体を更新してくれた Thomas Steiner に感謝します。