[Astro] #116 Storage Benchmark — IndexedDB vs OPFS (SyncAccessHandle) 比較検証ツールの実装

[Astro] #116 Storage Benchmark — IndexedDB vs OPFS (SyncAccessHandle) 比較検証ツールの実装

概要

Webアプリケーションでローカルストレージに大容量データを保存する際、従来の標準であった IndexedDB と、近年注目されている OPFS(Origin Private File System)のパフォーマンス・レイテンシ特性を比較検証するベンチマークツールを作成・公開した。

今回の実装内容

機能・モジュール概要
Dual-Worker 並列計測IndexedDB / OPFS それぞれ専用の Web Worker でメインスレッドをブロックせず計測
OPFS SyncAccessHandleWeb 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ブロック単位で視覚化することを目的として開発した。

スクリーンショット

起動

Storage Benchmark v1.0 - 起動

ベンチマーク実行

Storage Benchmark v1.0 - ベンチマーク実行

動画(GIF)

Storage Benchmark v1.0 - 動画(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回書き込み)で実行した結果:

指標IndexedDBOPFS (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.jsIndexedDB 計測用 Web Worker
public/storage-benchmark/worker-opfs.jsOPFS (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)の挙動調査