Skip to content
About
Main content

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 ; they reach the device later, and are lost if the machine loses power first. fsync() asks the kernel to push the file's 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 actually buy?


Predict

On macOS, how many times slower than a plain write is a plain fsync?

Run it

  1. 1Install uv once. It fetches Python and the lab's packages for you.

    Terminal
    curl -LsSf https://astral.sh/uv/install.sh | sh
  2. 2Get the script. It is the whole lab: read it, then download it.

    what-does-fsync-really-cost-on.py43 linesDownload
    View 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)
    
  3. 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
Knobs
Knob · defaultWhat 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

Cost per 200-byte appendApple M2 · 8 cores · 8 GB
Data table
Cost per 200-byte append
Sync modeµs per write
—4.27
—30.2
—2,640
macOS 26.6.2 · Python 3.13.3 · run 2026-10-20

Appends, three ways

Appends, three ways
Modeµs per writevs no sync
no sync4.271×
fsync30.27.08×
F_FULLFSYNC2,640618×
Output · uv run what-does-fsync-really-cost-on.py
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. , 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.