[Astro] #116 Storage Benchmark — IndexedDB vs OPFS (SyncAccessHandle) 比較検証ツールの実装
概要
Webアプリケーションでローカルストレージに大容量データを保存する際、従来の標準であった IndexedDB と、近年注目されている OPFS(Origin Private File System)のパフォーマンス・レイテンシ特性を比較検証するベンチマークツールを作成・公開した。
[WEB App] Storage Benchmark v1.0 - IndexedDB vs OPFS 比較検証ツール
IndexedDBとOPFS(SyncAccessHandle)の書き込み速度と処理遅延(スパイク)をリアルタイムに可視化・比較するブラウザ完結ツール。
lain-lab.com今回の実装内容
| 機能・モジュール | 概要 |
|---|---|
| Dual-Worker 並列計測 | IndexedDB / OPFS それぞれ専用の Web Worker でメインスレッドをブロックせず計測 |
| OPFS SyncAccessHandle | Web Worker 内専用の同期型ファイルアクセス API での直書き・シーク実装 |
| ミクロレイテンシ可視化 | HTML5 Canvas を用い、書き込み1ブロックごとの所要時間をカラーマップ描画 |
| パラメータ可変テスト | チャンクサイズ(4KB〜256KB)、ブロック数、ループ回数の動的変更機能 |
背景 — なぜ今 OPFS なのか
Web版PhotoshopやVS Code Web、SQLite Wasmなどの高度なWebアプリケーションにおいて、データの永続化先が IndexedDB から OPFS へと移行するケースが増えている。
IndexedDB はオブジェクトストレージとして優秀だが、非同期トランザクションのオーバーヘッドや構造化複製(Structured Clone)の負荷、ガベージコレクションに伴う散発的な遅延(レイテンシスパイキング)が問題になりやすい。
対する OPFS は、Web Worker 内限定で FileSystemSyncAccessHandle という同期型の直書きファイル API を提供する。これにより、C言語の fwrite() や fseek() に近い極小のオーバーヘッドでローカルディスクにアクセスできる。
今回のツールは、単なる「全体の完了時間(MB/s)」の測定にとどまらず、「1回の書き込みで何ミリ秒の遅延(引っかかり)が生じているか」を1ブロック単位で視覚化することを目的として開発した。
スクリーンショット
起動
ベンチマーク実行
動画(GIF)
技術実装の詳細
1. Web Worker 内での計測とメインスレッドとの通信
UIのレイアウト崩れや描画遅延が計測精度に影響しないよう、書き込み処理はすべて Web Worker 上で実行する。Worker 内で各ブロックの処理時間を高精度タイムスタンプ(performance.now())で計測し、プログレスイベントとしてメインスレッドへ送信する。
// Worker 内部の書き込みループ例 (OPFS)
const handle = await fileHandle.createSyncAccessHandle();
const chunk = new Uint8Array(chunkSize);
for (let i = 0; i < totalBlocks; i++) {
const t0 = performance.now();
for (let j = 0; j < writesPerBlock; j++) {
handle.write(chunk, { at: offset });
offset += chunk.length;
}
handle.flush(); // ディスク同期
const duration = performance.now() - t0;
// メインスレッドへ1ブロックの計測結果を通知
self.postMessage({
type: 'PROGRESS',
blockIndex: i,
durationMs: duration
});
}
handle.close();
2. IndexedDB と OPFS の処理比較
IndexedDB 側
非同期トランザクション(readwrite)を作成し、idbStore.put() をループ実行。各トランザクションが完了(oncomplete)するまでの時間をブロック単位で集計する。
OPFS (SyncAccessHandle) 側
navigator.storage.getDirectory() から専用の隠しファイルを作成。createSyncAccessHandle() を取得してバッファを同期的に連続書き込み(write)する。
3. HTML5 Canvas によるレイテンシ可視化
取得した durationMs の値に応じて、キャンバス上の該当ブロック領域(タイル)の色を動的に塗り分ける。
- 緑(< 10ms): 極めてスムーズな処理
- 黄(10ms - 50ms): 軽微な引っかかり
- 赤(> 100ms): 重い遅延スパイク(フレーム落ちの原因)
function getLatencyColor(ms: number): string {
if (ms < 10) return '#00ff66'; // 緑: 正常
if (ms < 30) return '#aaff00'; // 黄緑
if (ms < 50) return '#ffcc00'; // 黄
if (ms < 100) return '#ff6600'; // オレンジ
return '#ff0033'; // 赤: スパイク
}
function renderBlock(ctx: CanvasRenderingContext2D, index: number, duration: number) {
const x = (index % COLUMNS) * BLOCK_SIZE;
const y = Math.floor(index / COLUMNS) * BLOCK_SIZE;
ctx.fillStyle = getLatencyColor(duration);
ctx.fillRect(x, y, BLOCK_SIZE - 1, BLOCK_SIZE - 1);
}
実測データ・パフォーマンス検証結果
標準設定(600ブロック / チャンク16KB / 1ブロック20回書き込み)で実行した結果:
| 指標 | IndexedDB | OPFS (SyncAccessHandle) |
|---|---|---|
| 総処理時間 | 約 5.97 秒 | 約 2.42 秒(2.4倍高速) |
| 平均書き込み速度 | ~3.2 MB/s | ~8.0 MB/s |
| レイテンシ推移 | 赤・オレンジのスパイクが散発(GC・キュー詰まり) | 全ブロックが緑色( 10ms )で均一 |
この結果から、OPFS は単に総スループットが速いだけでなく、遅延のバラツキ(ジッター)が圧倒的に少ないという大きなメリットが実証された。
変更ファイル・構成
| ファイル | 役割 |
|---|---|
src/pages/featured/storage-benchmark-idb-vs-opfs.mdx | ツール紹介・解説ドキュメント |
public/storage-benchmark/index.html | ベンチマークツール UI / DOM 構造 |
public/storage-benchmark/worker-idb.js | IndexedDB 計測用 Web Worker |
public/storage-benchmark/worker-opfs.js | OPFS (SyncAccessHandle) 計測用 Web Worker |
public/storage-benchmark/app.js | メイン UI / Canvas レンダラー / イベント制御 |
まとめ
- IndexedDB: オブジェクト管理・小規模検索には優れるが、連続大容量書き込みではスパイクが発生しやすい
- OPFS (SyncAccessHandle): Web Worker 必須だが、極めてフラットで高速な I/O 特性を発揮する
- タイルマップ状のリアルタイムキャンバス描画により、遅延の発生パターンが可視化できた
今後の展望
- WebAssembly (C/Rust) からの direct OPFS アクセスの検証
- 読み込み(Read)ベンチマークおよびランダムシーク性能測定の追加
- Storage Foundation API や既存ブラウザのストレージ制限(Quota)の挙動調査