Origin Private File System (OPFS)
ブラウザ内でネイティブDB並みのI/Oが出せる理由が分かる。OPFSの隔離ツリー構造と同期アクセスハンドル、WASMアプリの永続化基盤としての設計を正確に整理します。
- 1.OPFSはオリジンごとに隔離された不可視の仮想ファイルシステムで、ユーザーのディスクとは別空間にブラウザが実体を管理する。
- 2.createSyncAccessHandleはWeb Worker上でのみ取得でき、read/write/flushが同期で完結するため、非同期APIのタスク往復コストを避けられる。
- 3.この同期I/O経路がSQLiteやDuckDBなどWASM移植DBの永続化層として採用され、ブラウザ内で実用的なランダムアクセス性能を出す土台になっている。
OPFSとは何か:オリジン専有の仮想ファイルシステム
Origin Private File System(OPFS)は、各オリジンに割り当てられた不可視の階層ファイルツリーです。名前のとおり「オリジンに私有(private)」されており、ユーザーのOSファイルシステム上には対応するフォルダが見えず、ファイルマネージャからも辿れません。実体はブラウザが管理する内部ストレージ領域にあり、アプリケーションからはディレクトリハンドルとファイルハンドルを通じてのみアクセスします。
入口はnavigator.storage.getDirectory()一本です。
// OPFSのルート(FileSystemDirectoryHandle)を取得
const root = await navigator.storage.getDirectory();
const dir = await root.getDirectoryHandle("cache", { create: true });
const file = await dir.getFileHandle("data.bin", { create: true });
この隔離は同一オリジンポリシーの延長線上にあり、別オリジンのOPFSツリーには一切到達できません。IndexedDBが構造化複製されたレコードの出し入れであるのに対し、OPFSは「ファイルの任意オフセットへ任意バイト列を書く」という低レベル操作を提供する点が本質的な違いです。この違いが、後述するWASM DBの永続化層としての採用理由に直結します。
OPFSはユーザーの実ファイルに触れません。オリジンに割り当てられたサンドボックス内で完結するため、漏れ出す先がなく、許可ダイアログなしで即座に使えます。ユーザー可視のファイルシステムを扱うshowOpenFilePicker系とは権限モデルが根本的に異なる点は、File System Access APIの内部で詳しく扱います。
同期アクセスハンドル:ワーカー限定の高速I/O
OPFSの性能面での中核がcreateSyncAccessHandle()です。これはWeb Worker上でのみ呼び出せ、メインスレッドで実行するとInvalidStateErrorになります。取得できるFileSystemSyncAccessHandleは、名前どおり同期メソッド群を持ちます。
// DedicatedWorker等、ワーカーのコンテキスト内で実行する
const root = await navigator.storage.getDirectory();
const fh = await root.getFileHandle("db.sqlite", { create: true });
const access = await fh.createSyncAccessHandle();
const buf = new Uint8Array([1, 2, 3]);
access.write(buf, { at: 0 }); // 同期。書き込んだバイト数を返す
const out = new Uint8Array(3);
access.read(out, { at: 0 }); // 同期。読み込んだバイト数を返す
access.flush(); // ディスクへの反映を要求
access.close(); // ハンドル解放。排他ロックを解く
同期であることが速度に効く理由は明確です。read/writeはイベントループのタスクキューを経由せず、呼び出したその場で結果を返します。通常の非同期ストレージAPIは1操作ごとにマイクロタスク/タスクの往復コストを払いますが、SyncAccessHandleはその往復を消し、かつ渡したArrayBufferのバイト列をほぼそのままI/Oに流すため構造化複製のコピーも発生しません。atによるオフセット指定で、ファイル全体を読み直さずに任意位置を部分更新できる点も、ランダムアクセスが多いワークロードで効いてきます。
同期APIがワーカー限定なのは設計上の必然です。同期呼び出しはその間スレッドを止めるため、UIスレッドで許可すると画面がフリーズします。ワーカーは専用スレッドなので、ブロックしてもメインスレッドの描画やイベント処理には波及しません。
createSyncAccessHandle()は対象ファイルに排他ロックを取ります。同じファイルへ2つ目のハンドルを開こうとすると失敗します。処理後は必ずclose()を呼んでロックを解放してください。ワーカーをクラッシュさせたまま放置すると、次回オープンが失敗し続ける原因になります。
なぜWASMアプリの永続化基盤になるのか
SQLiteやDuckDBをWebAssemblyへ移植したビルドは、もともとPOSIX風のブロックI/O(open/read/write/fsync相当)を前提に書かれています。WebAssemblyの実行モデルでは、この種のネイティブコードはリニアメモリ上で動きますが、永続化先としてブラウザが提供する同期かつランダムアクセス可能なバイト列I/Oが必要になります。IndexedDBの非同期APIをVFS(Virtual File System)層で同期に見せかける実装は、コールバックの折り返しやスレッド間メッセージングのオーバーヘッドが大きく、ページキャッシュのフラッシュ頻度が高いDBエンジンでは性能上のボトルネックになりがちでした。
OPFSのSyncAccessHandleは、このギャップを埋めます。ワーカースレッド上でWASMモジュールを動かし、そのファイルI/O呼び出しをSyncAccessHandleへ直接マッピングすれば、ネイティブのファイルI/Oに近い形でVFSを実装できます。これが「ブラウザ内で実用速度のSQLite」を成立させている中核技術です。
| 観点 | IndexedDB経由のVFS | OPFS SyncAccessHandle経由のVFS |
|---|---|---|
| I/Oの同期性 | 非同期。同期APIへのラップが必要 | ネイティブに同期 |
| データ形式 | 構造化複製されたレコード | 生バイト列(オフセット直接指定) |
| 実行コンテキスト | メイン/ワーカーどちらでも可 | ワーカーのみ |
| ランダムアクセス性能 | ページ相当の抽象化コストがかかる | ファイルオフセットへ直接read/write |
「ブラウザの中でネイティブDBエンジンをそのまま動かすには、非同期I/Oでは遅すぎる」という問題に対する解がOPFSのSyncAccessHandleです。ワーカー限定・同期・生バイトI/O、という3つの制約はいずれも性能とスレッド安全性のためのトレードオフであり、恣意的な制限ではありません。
クォータと退避の扱い
OPFSに保存されたデータは、他のオリジンストレージと同じくブラウザの容量管理下にあります。既定はbest-effort(容量逼迫時に退避され得る)で、退避から守りたい場合はnavigator.storage.persist()で永続化を要求します。この容量管理とLRUベースの退避方針そのものはIndexedDBのストレージ管理と共通の仕組みであり、OPFS固有の特別な例外はありません。WASM DBをOPFSに置く設計では、失われて困るデータの永続化要求と、必要に応じたサーバー側バックアップの併用を前提にすべきです。
まとめ
OPFSは、オリジンに隔離された不可視の仮想ファイルシステムであり、その価値の核心はWorker上のcreateSyncAccessHandleが提供する同期・低コピーのバイトI/Oにあります。非同期APIのタスク往復を避け、ファイルオフセットへ直接read/writeできることで、ブラウザ内で動くWASM版DBエンジンにネイティブに近い永続化層を与えます。設計時の要点は、(1) 同期I/Oは必ずワーカーで完結させる、(2) ハンドルは使い終わったら確実にcloseする、(3) 容量退避に備えてpersist要求と必要ならサーバー複製を併用する、の3つです。ユーザー可視ファイルとの権限モデルの違いはFile System Access APIの内部、マルチスレッド設計全般はWeb Workerのスレッドモデル、共有メモリを使う高度な並行処理はSharedArrayBufferとAtomicsを合わせて参照してください。
Web/フロントエンド Article
Origin Private File System (OPFS)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
ブラウザ
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 6
導入後に効く点
createSyncAccessHandleはWeb Worker上でのみ取得でき、read/write/flushが同期で完結するため、非同期APIのタスク往復コストを避けられる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 6
判断チェックリスト
- 自社の用途が「ブラウザ / OPFS」に近いか確認する。
- 強みである「OPFSはオリジンごとに隔離された不可視の仮想ファイルシステムで、ユーザーのディスクとは別空間にブラウザが実体を管理する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。