What does fsync really cost on your machine?
Measure your machineNeeds Python
Problem
A write() returns as soon as the bytes are in the operating system's page cache; they reach the device later, and are lost if the machine loses power first. fsync() asks the kernel to push the file's dirty pages to the device and wait. Whether that wait includes the drive's own volatile cache depends on the platform and the drive: a drive with power-loss protection can acknowledge from its protected cache in microseconds, a drive that really flushes takes milliseconds, and on macOS plain fsync stops at the drive, which keeps the data in its cache; only fcntl(F_FULLFSYNC) forces it to stable media. The cost of a durable commit is therefore a property of your machine, and you have to measure it.
Time appends with and without fsync. On macOS, also time F_FULLFSYNC. The ratio is your "durability tax".
The question. How much does one durable append cost on this machine, compared with a plain write, and what does macOS's plain fsync actually buy?
Predict
Expect a gap of 100× or more on a machine with a power-loss-protected drive, and a very large per-call cost on a spinning disk or a drive that really flushes. (On macOS, plain fsync does not force the drive cache. fcntl(F_FULLFSYNC) does, and costs much more.)
Run it
1Install uv once. It fetches Python and the lab's packages for you.
Terminal curl -LsSf https://astral.sh/uv/install.sh | sh2Get the script. It is the whole lab: read it, then download it.
View the codeHide the code
what-does-fsync-really-cost-on.py # /// script # requires-python = ">=3.10" # /// # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT # https://thedataengineering.com/lab-code/LICENSE.txt """What does fsync really cost on your machine? 200-byte appends with no sync, with fsync after each, and (on macOS) with F_FULLFSYNC, which also flushes the drive's own cache. The ratio is your durability tax. """ import argparse, json, os, platform, tempfile, time p = argparse.ArgumentParser(description=__doc__.splitlines()[0]) p.add_argument("--writes", type=int, default=2000, help="Appends per variant.") p.add_argument("--path", default=os.path.join(tempfile.gettempdir(), "fsync-lab.log"), help="Log file (put it on the drive to test).") p.add_argument("--json", help="Also write the results to this JSON file.") args = p.parse_args() def bench(mode): fd = os.open(args.path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC) rec = b"x" * 200 t0 = time.perf_counter() for _ in range(args.writes): os.write(fd, rec) if mode == "fsync": os.fsync(fd) elif mode == "F_FULLFSYNC": import fcntl; fcntl.fcntl(fd, fcntl.F_FULLFSYNC) dt = time.perf_counter() - t0 os.close(fd) return dt / args.writes * 1e6 modes = ["no sync", "fsync"] + (["F_FULLFSYNC"] if platform.system() == "Darwin" else []) rows = [{"mode": m, "us_per_write": bench(m)} for m in modes] base = rows[0]["us_per_write"] for r in rows: r["x_no_sync"] = r["us_per_write"] / base print(f"{r['mode']:<12} {r['us_per_write']:9.1f} µs per write ({r['x_no_sync']:7.0f}x no sync)") if args.json: with open(args.json, "w") as f: json.dump({"tables": {"modes": rows}}, f)3Run it in the folder you saved it to, with the defaults first. Then change a knob from the table below and run it again.
Terminal uv run what-does-fsync-really-cost-on.py uv run what-does-fsync-really-cost-on.py --help # every knob
| Knob · default | What it changes |
|---|---|
| --writes2000 | Appends per variant. |
| --pathos.path.join(tempfile.gettempdir(), 'fsync-lab.log') | Log file (put it on the drive to test). |
Our run
Data table
| Sync mode | µs per write |
|---|---|
| — | 4.27 |
| — | 30.2 |
| — | 2,640 |
Appends, three ways
| Mode | µs per write | vs no sync |
|---|---|---|
| no sync | 4.27 | 1× |
| fsync | 30.2 | 7.08× |
| F_FULLFSYNC | 2,640 | 618× |
no sync 4.3 µs per write ( 1x no sync)
fsync 30.2 µs per write ( 7x no sync)
F_FULLFSYNC 2641.4 µs per write ( 618x no sync)What we found
A plain write cost 4.27 µs. Plain fsync cost 30.2 µs, only 7.08× a plain write, far less than the expected 100×: on macOS it hands the data to the drive and returns without waiting for the drive's cache. F_FULLFSYNC, which does wait, cost 2,640 µs, 618× a plain write: that is the real durability tax of this laptop's SSD, and it caps a writer that flushes every commit at one commit per flush.
Read it
The small fsync number is the trap. A database that calls plain fsync on macOS looks fast and is not durable against power loss; PostgreSQL, SQLite and others offer a setting to use F_FULLFSYNC for this reason. On Linux the same call does flush the drive cache (unless the drive lies), so the same code has a different guarantee on each platform.
The cost of the real flush sets the ceiling of everything above it: a writer that syncs every commit cannot exceed one commit per flush time. Group commit, the next lab, is how systems get past that ceiling without giving up the guarantee.
Go further
Then run the same test on a USB stick or a network drive and compare.