Open Bug 2072928 Opened 1 month ago Updated 1 month ago

OPFS: creating, opening, closing and deleting a file performs ~21 fsyncs (7 `metadata.sqlite` transactions in rollback-journal mode); ~75 ms per file on Linux vs ~0.6 ms in Chromium

Categories

(Core :: DOM: File, defect)

Firefox 155
defect

Tracking

()

UNCONFIRMED

People

(Reporter: iam, Unassigned)

Details

Note: AI was used extensively in developing this benchmark and bug report, though I have made some effort to verify its claims, remove glaring issues, and produce something helpful while being totally unfamiliar with Firefox's codebase, C++, and not being in a position to devote much of my time to help or understand further.

Populating the Origin Private File System from a snapshot, either as many small files or as one file written in chunks, takes significantly longer in Firefox than in Chrome on the same machine. The attached benchmark attempts to separate the per-call cost of FileSystemSyncAccessHandle.write() (filed as bug 2072923) from the per-file cost of creating, opening, closing and deleting files. This bug is about the per-file cost.

You can find more information and view the benchmark runs here.

Steps to reproduce

  1. git clone https://github.com/tbrockman/firefox-opfs-simple-bench && cd firefox-opfs-simple-bench && python3 serve.py 8000
  2. Open http://localhost:8000/ in Firefox (normal window) and in Chrome, choose the Full preset, run, and compare the "new-file" rows. Each row is one step of getFileHandle({create: true}) + createSyncAccessHandle(), first write() of 4 KiB, close(), removeEntry(), timed per file, plus the full cycle, for 2000 files, median of 5 runs after a warm-up.
  3. Or unattended: cd automation && npm install && npx puppeteer browsers install firefox && node run.mjs --preset full.

Actual results

Linux (Ryzen 7 7800X3D, Samsung 990 PRO, ext4 on LVM on dm-crypt; one fsync = 5.4 ms on this volume), Firefox 155.0.1 vs Chrome 152, mean per file (Linux run):

Step Firefox 155 Chrome 152 ratio
create + open 34.0 ms 0.38 ms 91×
first write (4 KiB) 0.10 ms 0.09 ms 1.2×
close 22.2 ms 0.08 ms 262×
delete 22.9 ms 0.15 ms 156×
full cycle 79.2 ms 0.69 ms 114×

The same run on macOS (Apple M4 Pro, APFS), Firefox 155.0.1 vs Chrome 153 (macOS run):

Step Firefox 155 Chrome 153 ratio
create + open 0.93 ms 0.26 ms 3.5×
first write (4 KiB) 0.04 ms 0.06 ms 0.7×
close 0.47 ms 0.08 ms 5.7×
delete 0.71 ms 0.11 ms 6.3×
full cycle 2.14 ms 0.51 ms 4.2×

The difference between the two hosts is fsync cost alone: macOS fsync() returns without flushing the drive cache, Linux ext4 does a journal commit plus a cache flush. With the browser profiles on tmpfs (fsync free) the cycle costs 1.34 ms in Firefox vs 0.57 ms in Chrome, which is the CPU/IPC floor (tmpfs control run).

Traced with strace -f -e trace=fsync,fdatasync,syncfs,sync_file_range (the benchmark's --strace-fsync option attributes calls to workloads by wall-clock window; traced run, quick preset, M = 200):

Firefox 155 Chrome 152
fsyncs per create/write/close/delete cycle 21.0 0
fsyncs per flush() call (control) 1.01 1.01

The 21 are 7 SQLite transactions on storage/default/<origin>/fs/metadata.sqlite, each costing three fsyncs in rollback-journal (DELETE) mode at synchronous = NORMAL: the journal, the fs directory (the journal is a new file every time), then the database. Per 800 cycles: 5,750 fsyncs on metadata.sqlite-journal, 5,750 on the fs directory, 5,750 on metadata.sqlite. The data file itself is never synced except by flush().

Where the seven transactions come from (mozilla-central 2026-09-15, 8d76a2ba4c)

Where AI thinks the seven transactions come from (unconfirmed)
  • GetStorageConnection (dom/fs/parent/datamodel/FileSystemDataManager.cpp) opens the database with OpenDatabaseWithFileURL(..., CONNECTION_DEFAULT) and sets no journal_mode; mozStorage's default is synchronous = NORMAL with the comment "Normal is the suggested value for WAL journals" (storage/mozStorageConnection.cpp), but the connection never switches to WAL.
  • create: GetOrCreateFile (FileSystemDatabaseManagerVersion001.cpp), one explicit transaction. (1)
  • open: FileSystemDataManager::LockExclusive calls EnsureFileId (Version002.cpp, one explicit transaction) and then BeginUsageTracking, whose SetUsageTrackingImpl UPDATE autocommits. (2)
  • close: UnlockExclusive calls UpdateUsage (UpdateUsageInDatabase upsert, autocommit) and then EndUsageTracking (UPDATE, autocommit). (2)
  • delete: RemoveFile runs RemoveFileId (DELETE FROM FileIds) and DeleteEntry (DELETE FROM Entries), each autocommitted. (2)

Expected results

Per-file cost within a small factor of other engines.

AI also alleges that

The File System standard ties persistence to flush(); nothing in the spec requires a durable commit per create, open, close or delete. Chrome writes its directory metadata to LevelDB without syncing and reconciles on the next open.

Demonstration of the impact (not proposed as the fix)

A one-line PRAGMA journal_mode = WAL at GetStorageConnection, built on mozilla-central (158.0a1, plain opt build; the unpatched local build reproduces the release numbers), measured with the same benchmark on the same volume:

Build create+open close delete full cycle fsyncs/cycle
unpatched 32.4 ms 21.0 ms 21.7 ms 75.2 ms 21.1
WAL (synchronous stays NORMAL) 0.73 ms 0.52 ms 0.67 ms 1.91 ms 0.12 (checkpoints only)
WAL + synchronous = FULL (every transaction still durable) 16.2 ms 10.7 ms 10.8 ms 37.8 ms 7.1
AI goes on to suggest what a real fix might look like

The write workloads are unchanged by either. A real fix would also need to account the -wal/-shm files in origin usage (IndexedDB has precedent), bound the WAL, and decide the crash-consistency story (RescanTrackedUsages already re-measures files whose tracking was left open). Independently of the journal mode, the seven transactions could become four by committing each operation once (open, close and delete each run two autocommitted statements), and fewer still if the per-open/close usage-tracking flags were kept in memory and flushed lazily; the tmpfs row above is what that would approach.

Severity: -- → S3
You need to log in before you can comment on or make changes to this bug.