Plate 92
TemporaryFile vs NamedTemporaryFile Delete Modes Lab
Hands-on tempfile modes lab: TemporaryFile vs NamedTemporaryFile delete True/False vs mkstemp unlink patterns with real measured ops/s on localhost box.
Aditya Challa6 min read
On this page
- Intro — what this post promises
- APIs compared
- Lab topology
- Lead table (p50)
- How to read these numbers
- Pitfalls we hit (or avoided)
- Practical checklist
- Methodology footnote
- Versions / environment
- delete=True vs delete=False
- Relationship to atomic replace
- Ratio cheatsheet (vs TemporaryFile)
- SpooledTemporaryFile (not timed, but related)
- Security / predictability notes
- Capacity planning example
- Verdict
Intro — what this post promises
Need a scratch file? TemporaryFile, NamedTemporaryFile(delete=...), and mkstemp are not the same syscall story. One is often already anonymous; another keeps a visible path; fsync turns a cheap unlink into a barrier.
This is a hands-on lab with measured numbers:
- Create → write (~400 B) → read/flush → close for several tempfile APIs.
NamedTemporaryFile(delete=True)vsdelete=False+ manual unlink.mkstempunlink-while-open vs close-then-unlink (± fsync).
Related links:
- atomic rename vs overwrite localhost lab
- fsync vs fdatasync localhost lab
- pipe vs tmpfile IPC localhost lab
- posix fadvise sequential vs random localhost lab
- Why your average latency graph is lying (p50 / p95 / p99)
Lab honesty (1 Oct 2026 IST): Python 3.13.5; dir under /tmp overlay. Affiliates: 0.
Verdict up front: TemporaryFile ~41100 ops/s; NamedTemporaryFile ~19600 (~0.48×). mkstemp+fsync+unlink ~3600; without fsync ~32k.
APIs compared
| API | Path visible? | Cleanup |
|---|---|---|
TemporaryFile | usually no (POSIX unlinked) | on close |
NamedTemporaryFile(delete=True) | yes while open | unlink on close |
NamedTemporaryFile(delete=False) | yes | you unlink |
mkstemp + unlink while open | no after unlink | anonymous fd |
mkstemp + close + unlink | briefly yes | manual |
same + fsync | — | durable tax |
Related links:
Lab topology
Script: lab-evidence/45-tempfile-modes/results/run_lab.py.
Lead table (p50)
| Arm | ops/s | µs/op | vs TemporaryFile |
|---|---|---|---|
| TemporaryFile | 41103 | 24.3 | 1.00× |
| mkstemp leave file (no unlink timed) | 41174 | 24.3 | 1.00× |
| mkstemp close+unlink no fsync | 32502 | 30.8 | 0.79× |
| mkstemp unlink-while-open | 31755 | 31.5 | 0.77× |
| NamedTemporaryFile delete=False+unlink | 20174 | 49.6 | 0.49× |
| NamedTemporaryFile delete=True | 19563 | 51.1 | 0.48× |
| mkstemp close+unlink + fsync | ~3600 | ~278 | ~0.09× |
Payload spot checks (NamedTemporaryFile delete=True): 64 B ~20835/s; 64 KiB ~14979/s.
Related links:
How to read these numbers
- No path needed →
TemporaryFile(fastest convenient API here). - Need a name for a subprocess →
NamedTemporaryFile(~2× slower) ormkstemp. - delete=True vs False+unlink ≈ same cost.
- fsync on temps is a durability product decision — huge cliff.
Pitfalls we hit (or avoided)
- Putting fsync in the “default mkstemp” arm — rebenchmarked without it (~32k vs ~3.6k).
- Assuming NamedTemporaryFile ≈ TemporaryFile — ~0.48×.
- Leaking delete=False files — always unlink or use a context that does.
- Cross-device temps for atomic replace — see atomic-rename lab.
- Measuring only create without write/close — we included a small write.
Practical checklist
- Scratch buffers:
TemporaryFileor SpooledTemporaryFile. - External tools needing a path:
NamedTemporaryFile(delete=False)+ try/finally unlink, ormkstemp. - Don’t fsync temps unless the durability contract says so.
- Prefer same filesystem as the final
os.replacetarget. - Clean up crash leftovers for
delete=False.
Related links:
- pipe vs tmpfile IPC localhost lab
- posix fadvise sequential vs random localhost lab
- SQLite WAL vs DELETE journal localhost lab
- json vs orjson vs msgpack localhost lab
Methodology footnote
Each op writes a fixed payload, flushes, and closes. NamedTemporaryFile(delete=False) unlinks after close inside the timed op. The fsync mkstemp arm calls os.fsync(fd) before close. Leftover files from the “leave file” arm are unlinked outside the timed window.
Versions / environment
- Python 3.13.5
tempfile/os - Workdir under
/tmpon overlay lab VM
Anonymous temps are the speed default; named temps buy a path for about half the throughput here.
delete=True vs delete=False
Throughput nearly matched (~19.6k vs ~20.2k). The product difference is failure mode: delete=False survives across process crashes until something unlinks the path — useful for debugging postmortems, dangerous for /tmp fill-ups. Prefer delete=True unless a child process must reopen by name after you close (then pass the name carefully and unlink in a finally).
Relationship to atomic replace
Named temps often feed os.replace into a config path (atomic-rename lab). Create the named temp in the destination directory so replace stays same-FS atomic. Using TemporaryFile cannot supply that path; that is the feature trade, not a bug.
Ratio cheatsheet (vs TemporaryFile)
NamedTemporaryFile delete=True 0.48× · delete=False+unlink 0.49× · mkstemp unlink-while-open 0.77× · mkstemp close+unlink no fsync 0.79× · mkstemp+fsync ~0.09×
If a profile shows temp creation hot, switching Named→Temporary (when safe) is a plausible 2×; adding fsync is how you accidentally get a 10× regression.
On busy shared /tmp, also watch inode limits and sticky-bit permissions — throughput numbers assume a quiet lab workdir.
SpooledTemporaryFile (not timed, but related)
For small blobs that might grow, SpooledTemporaryFile keeps bytes in RAM until a size threshold, then rolls to disk. That path can beat any of today’s disk arms for sub-threshold sizes — and looks nothing like NamedTemporaryFile in profiles. If your “temp file” is usually under a few KiB, consider spooling before paying named-path costs.
Security / predictability notes
Named temps use predictable directory listings if permissions are wrong. Keep dir= on a private directory when secrets touch disk. TemporaryFile’s anonymous POSIX pattern reduces the rename/race window because there is often no lasting directory entry — another reason it is the default scratch tool.
Capacity planning example
At ~20k NamedTemporaryFile ops/s, one core could theoretically open ~20 million named temps per second in a microbench — reality will be lock contention on the directory and page cache. Still, if a job creates 100k temps, budget ~5 s of create/close overhead from this lab’s µs/op before counting useful work. TemporaryFile would be ~2.4 s for the same count on these numbers.
Verdict
TemporaryFile (~41k ops/s) beat NamedTemporaryFile (~20k, ~0.48×) for small write/close cycles. mkstemp without fsync sat ~32k; with fsync ~3.6k. Pick anonymous when you can; pay for names deliberately; fsync only with a durability story.
Evidence: lab-evidence/45-tempfile-modes/results/. Affiliates: 0.
Lab evidence
What I found running this
Lab 1 Oct 2026 IST. Python 3.13.5; /tmp; ~400 B payload. TemporaryFile ~41103 ops/s; NamedTemporaryFile(delete=True) ~19563 (~0.48x); delete=False+unlink ~20174; mkstemp unlink-while-open ~31755; mkstemp close+unlink (no fsync) ~32502; mkstemp+fsync+unlink ~3600. Affiliates: 0. Evidence: lab-evidence/45-tempfile-modes/.
Related links
Plate 18
fnmatch vs re Name Filter: Localhost Lab
Hands-on fnmatch.filter vs re.compile name-list filtering measured on Linux localhost.
Observability & SRE · 30 Sept 2026
Plate 84
tarfile vs zipfile Create+Extract: Localhost Lab
Hands-on tarfile vs zipfile create+extract lab: real MB/s on a mixed small-file fixture (uncompressed tar vs zip), measured on Linux localhost for SREs.
Observability & SRE · 30 Sept 2026
Plate 85
scandir vs listdir vs iterdir: Localhost Lab
Hands-on os.scandir vs listdir vs Path.iterdir lab: real entries/s for names and is_file on a synthetic tree, measured on Linux localhost (lab) for SREs.
Observability & SRE · 30 Sept 2026