ブラウザストレージの制約を理解する：LocalStorageとIndexedDBのユースケース別選択ガイド

## はじめに

Webアプリケーションを開発していると、クライアントサイドでのデータ保存は避けて通れない課題となります。特に以下の制約は多くの開発者を悩ませます：

- LocalStorageはWebWorkerで使用できない
- IndexedDBは単独ではウィンドウをまたいでデータを共有できない

この記事では、これらの制約の詳細と実践的な解決策を解説します。

## 想定する読者

- WebWorkerを使用したアプリケーションを開発している方
- 複数タブでのデータ同期が必要なアプリケーションを開発している方
- ブラウザストレージの選択に悩んでいる方

## 前提知識

- JavaScriptの基本的な知識
- WebWorkerの基本的な理解
- ブラウザストレージ（LocalStorage, IndexedDB）の基本的な使用経験

## LocalStorageの制約を理解する

### WebWorkerでLocalStorageが使えない理由

LocalStorageがWebWorkerで使用できない理由は、以下の2つに集約されます：

1. 設計上の制約
   - WebWorkerは独立したグローバルコンテキストで動作
   - メインスレッドのウィンドウオブジェクトにアクセスできない

2. セキュリティ上の制約
   - メインスレッドに影響を与える可能性のあるAPIへのアクセスが制限されている
   - DOM操作やLocalStorageなどのウィンドウ関連APIは使用不可

## IndexedDBのデータ共有における課題

### タブ間でのデータ共有

IndexedDBは強力なクライアントサイドストレージですが、以下の制限があります：

- 組み込みのタブ間通信機能がない
- 自動的なデータ同期の仕組みがない

## 実践的な解決策

### 1. WebWorkerでのデータ処理が必要な場合

```javascript
// IndexedDBを使用したWebWorkerの実装例
// worker.js
self.onmessage = async function(e) {
  const db = await openDatabase();
  switch(e.data.type) {
    case 'save':
      await saveData(db, e.data.payload);
      self.postMessage({ type: 'saved' });
      break;
    case 'load':
      const data = await loadData(db);
      self.postMessage({ type: 'loaded', data });
      break;
  }
};

async function openDatabase() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('MyDatabase', 1);
    request.onerror = () => reject(request.error);
    request.onsuccess = () => resolve(request.result);
    request.onupgradeneeded = (e) => {
      const db = e.target.result;
      db.createObjectStore('myStore', { keyPath: 'id' });
    };
  });
}
```

### 2. タブ間でのデータ同期が必要な場合

以下の2つの方法から選択できます：

#### A. BroadcastChannel APIの使用

```javascript
// メインスクリプト内での実装例
const channel = new BroadcastChannel('storage_channel');

// データ更新時
async function updateData(data) {
  await saveToIndexedDB(data);
  channel.postMessage({
    type: 'update',
    data: data
  });
}

// 他のタブからの更新を受信
channel.onmessage = async (event) => {
  if (event.data.type === 'update') {
    // UIの更新など
    updateUI(event.data.data);
  }
};
```

#### B. SharedWorkerの使用

```javascript
// shared-worker.js
let connections = [];
let database = null;

self.onconnect = function(e) {
  const port = e.ports[0];
  connections.push(port);
  
  port.onmessage = async function(e) {
    switch(e.data.type) {
      case 'write':
        await writeData(e.data.payload);
        // 他の接続に変更を通知
        broadcast({
          type: 'updated',
          data: e.data.payload
        });
        break;
    }
  };
};

function broadcast(message) {
  connections.forEach(port => port.postMessage(message));
}
```

## ユースケース別の選択ガイド

1. シンプルなデータ保存
   - WebWorker不要
   - タブ間同期必要
   → LocalStorage + BroadcastChannel API

2. 複雑なデータ処理
   - WebWorker必要
   - タブ間同期必要
   → IndexedDB + SharedWorker
   または
   → IndexedDB + BroadcastChannel API

3. 大量のデータ処理
   - WebWorker必要
   - 高パフォーマンス要求
   → OPFS in WebWorker
   または
   → IndexedDB in WebWorker

## パフォーマンスの考慮点

1. LocalStorage
   - 読み取り: 約0.0052ミリ秒
   - 書き込み: 約0.017ミリ秒
   - メインスレッドをブロックする点に注意

2. IndexedDB
   - 読み取り: 約0.1ミリ秒
   - 書き込み: 約0.17ミリ秒
   - 非同期処理でメインスレッドをブロックしない

## まとめ

ブラウザストレージの選択は、アプリケーションの要件によって大きく変わります：

- シンプルなデータ保存ならLocalStorage
- 複雑なデータ処理や大量データならIndexedDB
- タブ間同期が必要な場合はBroadcastChannelかSharedWorker

これらの制約を理解し、適切なストレージと同期方法を選択することで、効率的なWebアプリケーションの開発が可能になります。

## 参考資料

- [MDN Web Docs - Web Storage API](https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API)
- [MDN Web Docs - IndexedDB API](https://developer.mozilla.org/ja/docs/Web/API/IndexedDB_API)
- [MDN Web Docs - BroadcastChannel](https://developer.mozilla.org/ja/docs/Web/API/BroadcastChannel)
- [MDN Web Docs - SharedWorker](https://developer.mozilla.org/ja/docs/Web/API/SharedWorker)