Hands-on atomic rename vs overwrite lab: tempfile+os.replace vs truncate-write with/without fsync. Real ops/s on localhost; crash-safety limits labeled.
7 min read
Writing a temp file in the same directory and calling os.replace costs little over overwriting in place: for 4 KiB files it ran at about 13,580 ops/s against 16,566 (about 18% slower), and for 64 KiB files replace was actually about 1.44x faster. Durability is the expensive part, because adding fsync dropped replace to about 3,376 ops/s and fsync of both file and directory to about 2,240, so use os.replace by default for config files and add fsync only when you need data to survive power loss.
This is a hands-on lab with measured numbers:
os.replace for 64 B / 4 KiB / 64 KiB payloads.fsync after write.Related links:
Lab honesty (1 Oct 2026 IST): Shared Linux lab box; Python 3.13.5; files under /tmp on overlay. Affiliates: 0. Throughput ≠ proven crash recovery.
Verdict up front: for 4 KiB, replace costs 18%~~ vs overwrite (13580 vs 16566 ops/s). fsync is the cliff (~~3–5× slower). File+dir fsync rename landed ~2240 ops/s.
| Pattern | What it does | Readers see |
|---|---|---|
| overwrite | open(wb) truncate + write | may see partial on crash |
| replace | write temp → os.replace | old or new complete (same FS) |
| + fsync file | durability of data bytes | still may miss dir entry without dir fsync |
| + fsync dir | durable rename cookbook | still not a formal crash proof here |
Related links:
Script: lab-evidence/42-atomic-rename/results/run_lab.py.
| Arm | ops/s | µs/op | vs overwrite |
|---|---|---|---|
| overwrite | 16566 | 60.4 | 1.00× |
| replace | 13580 | 73.6 | 0.82× |
| overwrite + fsync | 5029 | 199 | 0.30× |
| replace + fsync | 3376 | 296 | 0.20× |
| replace + fsync file+dir | 2240 | 446 | 0.14× |
Atomicity without fsync is cheap here (~18%). Durability is expensive — same story family as the fsync lab.
Related links:
64 B: overwrite 16377 ops/s; replace 13698 (~0.84×); overwrite+fsync 4929; replace+fsync 3377.
64 KiB: overwrite 5234; replace 7558 (~1.44× faster replace!). Writing a new temp + rename beat truncating and rewriting the large file on this run — worth re-checking on your FS, but it shows replace is not always the slower path for big blobs.
We did not pull power, kill -9 mid-write, or run dm-flakey. The durable rename pattern is the industry cookbook (fsync data file, rename/replace, fsync directory). This lab times that cookbook; it does not prove your disk honors flush.
Related links:
os.replace is required for atomicity; cross-device becomes copy+unlink.os.replace.Related links:
Each op fully writes the payload. Replace arms use unique temp names then os.replace. Directory fsync uses os.open(dir, O_RDONLY) + os.fsync(dir_fd). Ops/s = n / p50_batch_seconds.
/tmp on overlay/virtio lab VMWritable config files are where torn writes hurt humans. Pay the replace tax; add fsync only when the durability contract requires it.
os.replace is atomic only on the same filesystem. Writing to /tmp/foo and replacing onto /var/lib/app/config.json can silently become “copy + unlink” semantics across mounts — or fail. This lab kept temps beside the target under /tmp. Production configs usually live on the data volume; create config.json.tmp next to config.json, not on an unrelated tmpfs, unless you intentionally accept non-atomic publish.
After a successful replace, a reader opening the path sees either the previous complete file or the new complete file — not a mix of both contents from a single truncated write. Readers that already hold an open FD keep the old inode (classic Linux behavior). That is often what you want for long-lived workers; document whether they must reopen to pick up config.
Batch exporters that rewrite large snapshots may find replace faster than overwrite (our 64 KiB case). Profile your size; do not assume the 4 KiB ranking.
| Arm | Ratio |
|---|---|
| overwrite | 1.00 |
| replace | 0.82 |
| overwrite+fsync | 0.30 |
| replace+fsync | 0.20 |
| replace+fsync file+dir | 0.14 |
If you currently overwrite without fsync, switching to replace is the cheap correctness upgrade. If you already fsync every overwrite, moving to replace+fsync (or +dir) is mostly about atomic visibility, not a new performance class — you already paid the barrier.
Compare absolute µs/op to the fsync lab’s per-record sync costs: both say “barriers dominate metadata tricks.”
Ship replace by default for user-visible state files; measure before adding fsync-on-every-write in a tight loop.
On this box, 4 KiB atomic replace ran at ~13580 ops/s versus overwrite ~16566 (~0.82×). Adding fsync dropped replace to ~3376 ops/s; fsync file+dir to ~2240. Atomicity is affordable; durability dominates — and we still did not simulate a crash.
Evidence: lab-evidence/42-atomic-rename/results/. Affiliates: 0.
Overwriting truncates first, so a crash mid-write can leave a torn file. With a temp file plus os.replace on the same filesystem, readers see either the old complete file or the new one.
Yes, or at least on the same filesystem. os.replace is atomic only within one filesystem. Across mounts it can turn into copy + unlink or fail, so create config.json.tmp next to config.json.
For durability across power loss, the cookbook is fsync the file, replace, then fsync the directory. In this lab that cost 446 µs per op vs 74 µs for replace alone. The lab timed it but did not simulate a crash.
Lab evidence
Lab 1 Oct 2026 IST. Python 3.13.5 on /tmp overlay. med 4KiB: overwrite 16566 ops/s (60 us); replace 13580 (~0.82x); overwrite+fsync 5029 (~0.30x); replace+fsync 3376 (~0.20x); replace+fsync file+dir 2240 (~0.14x). small 64B similar. large 64KiB replace 7558 vs overwrite 5234 without fsync. No crash simulation. Affiliates: 0. Evidence: lab-evidence/42-atomic-rename/.
Explore the product
Untested by us. Check what's included, the license and refund terms before you buy.
Hands-on platform.platform vs os.uname host inventory lab: real ops/s plus cache notes, measured on Linux localhost today in this hands-on lab for SREs.
Summary1 Oct 20264 min read
Hands-on uuid.uuid4 vs uuid.uuid1 ID generation lab: real ops/s plus version/node checks, measured on Linux localhost today in this hands-on lab for SREs.
Summary1 Oct 20263 min read
Hands-on signal SIGUSR1 vs threading.Event wakeup lab: real p50 latency in microseconds, measured on Linux localhost today in this hands-on lab for SREs.
Summary1 Oct 20264 min read