1. 概要
ブラウザで同期的なファイルI/Oを扱うには、保存場所・実行コンテキスト・I/O方式という3つの問題を同時に解く必要があります。OPFS はブラウザ内部専用のストレージを提供し、Web Worker はブロッキング処理をメインスレッドの外で実行できるようにし、FileSystemSyncAccessHandle はOPFSのファイルを同期的に読み書きできるようにします。PyodideのブラウザファイルI/Oのissueでも、この3つの概念が一緒に登場します。
確認すべきポイントは次のとおりです。
OPFSがなぜ必要なのか、従来の方式とどう違うのかWeb WorkerがファイルI/Oとどんな関係にあるのかFileSystemSyncAccessHandleがどんな問題を解決するのか- 3つの概念がなぜいつも一緒に登場するのか
2. なぜOPFSが必要なのか
ブラウザのファイル処理は長い間、<input type="file"> や FileReader ベースの限定的なAPIに依存してきました。File APIは、ユーザーがfile inputやdrag and dropで提供したファイルを読み込んで処理する用途に近いものです。
その後 File System Access API が登場し、ユーザーの同意に基づいてローカルファイルを読み書きできるようになりましたが、ユーザーに見えるファイルシステムを扱うこととorigin内部専用ストレージは目的が異なります。OPFSはoriginごとのprivate storage endpointであり、ブラウザが管理するアプリ専用ストレージに近い存在です。
OPFSはこの問題を解決するために生まれました。 OPFS(Origin Private File System)は、origin(ウェブサイトのアドレス)単位で分離されたブラウザ内部専用のファイルシステムです。ユーザーに直接公開されないストレージなので、ユーザーに見えるファイルシステムより権限・セキュリティのフローがシンプルで、Worker内の同期アクセスハンドルと組み合わせれば、WASMベースのアプリでファイルI/Oモデルを合わせるのに適しています。
- 容量制限があります — IndexedDBと同様にブラウザのストレージクォータに従い、
navigator.storage.estimate()で使用量を確認できます。 - サイトデータを消去すると削除されます。
- ユーザーはファイルのパスを見つけられません。
- 同期的なAPIを提供します — 他のメソッドはすべてasyncですが、OPFSだけは
FileSystemSyncAccessHandleによる同期アクセスをサポートします。ただし、Dedicated Web Worker内でのみ可能です。
NOTE
OPFSはorigin単位で分離されたブラウザ内部専用のファイルシステムです。セキュリティチェックや権限リクエストがないためasync overheadなしでアクセスでき、Dedicated Web Worker環境では同期的なファイルアクセスも可能です。
3. 3つの概念
OPFS、Web Worker、FileSystemSyncAccessHandle は、それぞれ異なる観点の概念です。
3.1. OPFS — どこに保存するか
OPFSはブラウザが管理するサンドボックス化されたファイルシステムです。originごとに独立したストレージを持ち、ファイルエクスプローラーには表示されません。アクセス方法は2つ — メインスレッド・Workerの両方で使えるasync APIと、Worker内でのみ使えるFileSystemSyncAccessHandleです。
// async API로 OPFS 접근 (메인 스레드에서도 가능)
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('data.bin', { create: true });
// 쓰기
const writable = await fileHandle.createWritable();
await writable.write('hello');
await writable.close();
// 읽기
const file = await fileHandle.getFile();
const text = await file.text();TIP
OPFSは「どこに保存するか」の問題です。origin単位で分離されたブラウザ内部のファイルシステムであり、アクセス方法はasync API(メイン/Worker)とFileSystemSyncAccessHandle(Worker専用)の2つです。
3.2. Web Worker — 誰が実行するか
JavaScriptは基本的にシングルスレッドです。すべてのコードがメインスレッドで実行され、このスレッドがUIレンダリング・イベント処理も担当するため、重い計算やブロッキングI/Oをメインスレッドで走らせると画面が固まります。Web Worker はメインスレッドから分離された別スレッドを作り、DOMに直接アクセスしない代わりにpostMessage() ベースのメッセージで通信します。
// 메인 스레드
const worker = new Worker('worker.js');
worker.postMessage({ type: 'start' });
worker.onmessage = (e) => console.log('결과:', e.data);
// worker.js
self.onmessage = async (e) => {
// 여기서 블로킹 작업 수행
self.postMessage({ result: '완료' });
};TIP
Web Workerは「誰が実行するか」の問題です。メインスレッドのブロッキングを防ぐため、同期I/OはWorker内でのみ許可されています。
3.3. FileSystemSyncAccessHandle — どう読み書きするか
FileSystemSyncAccessHandle はOPFSファイルへのアクセス方法の1つで、名前のとおり同期的(Sync)に読み書きします。read、write、getSize、flush、truncate、close などのメソッドは同期的に動作します。ただし、このハンドルはOPFSファイルに対してのみ作成でき、Dedicated Web Worker内でのみアクセスできます。
// Worker 안에서
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('fast.bin', { create: true });
// 핸들 생성은 async (한 번만)
const accessHandle = await fileHandle.createSyncAccessHandle();
// 이 아래는 전부 동기 — await 없음
const encoder = new TextEncoder();
const buf = encoder.encode('Some data');
accessHandle.write(buf, { at: 0 }); // 동기 쓰기
accessHandle.flush(); // 디스크에 반영
const size = accessHandle.getSize(); // 동기 크기 조회
const view = new DataView(new ArrayBuffer(size));
accessHandle.read(view, { at: 0 }); // 동기 읽기
accessHandle.close();read()/write() はブロッキングであり、メインスレッドを止めてしまう可能性があるため、仕様上Dedicated Web Worker内でのみ使用できます。createSyncAccessHandle() メソッド自体はPromiseを返す非同期呼び出しですが、返されたハンドルのファイル操作メソッドは同期的です。
WARNING
FileSystemSyncAccessHandle は仕様上、Dedicated Web Worker内でのみ使えます。メインスレッドで createSyncAccessHandle() を呼び出すとエラーになります。
4. 3つの概念が噛み合う構造
それぞれ異なる問題を解決しますが、「ブラウザで同期的なファイルI/Oを実装する」というゴールには3つが同時に必要です。
| 解決すべき問題 | 担当する概念 |
|---|---|
| 速く、権限なしでファイルを永続化 | OPFS |
| 同期的なread / writeの実装 | FileSystemSyncAccessHandle |
| ブロッキング呼び出しがUIを止めないように | Web Worker |
どれか1つでも欠けてはいけません。
- OPFSの代わりにFile System Access API → ユーザーのローカルファイルへのアクセスはできるものの、セキュリティチェックで遅く、毎回権限ポップアップが出ます。(目的が異なる: File System Access API=ユーザーのファイル、OPFS=ブラウザ内部専用)
FileSystemSyncAccessHandleなしでasyncのみ → 同期的なf.read()パターンは不可能- Workerなしでメインスレッド → ブロッキング呼び出しがUIを停止
Sync I/O実行フローのダイアグラム
TIP
OPFSは保存場所、Web Workerは実行コンテキスト、FileSystemSyncAccessHandleはI/O方式です。互いに異なる観点であり、それぞれ別の問題を解決します。
5. OPFSアクセスAPIの比較
| API | 同期かどうか | OPFS専用 | 読み/書き | 推奨コンテキスト |
|---|---|---|---|---|
getFile() | async | ❌ | 読みのみ | メインスレッド / Worker |
createWritable() | async | ❌ | 書きのみ | メインスレッド / Worker |
createSyncAccessHandle() | sync | ✅ OPFSのみ | 読み + 書き | Worker推奨 |
6. まとめ
最初にPyodideのブラウザファイルI/Oの文脈を読んだときは3つの概念がひとまとまりに感じられましたが、観点ごとに分離すると明確になります。
- OPFS — どこに保存するか(保存場所)
- Web Worker — 誰が実行するか(実行コンテキスト)
- FileSystemSyncAccessHandle — どう読み書きするか(I/O方式)
TIP
3つの概念はそれぞれ別の問題を解決しながら、「ブラウザで同期的なファイルI/O」という1つのゴールのために噛み合っています。
7. 参考文献
- MDN — File APIdeveloper.mozilla.org
- MDN — Origin private file systemdeveloper.mozilla.org
- MDN — FileSystemSyncAccessHandledeveloper.mozilla.org
- MDN — FileSystemFileHandle: createSyncAccessHandle()developer.mozilla.org
- MDN — Web Workers APIdeveloper.mozilla.org
- MDN — Using Web Workersdeveloper.mozilla.org
- MDN — Introducing workersdeveloper.mozilla.org
- Pyodide Issue #5862 — Sync native FS via FileSystemSyncAccessHandle & OPFSgithub.com