Go File Handling in Production: Ownership, Atomic Writes, and Durability
TL;DR
Opening a file is easy.
Designing file operations that remain correct during crashes, concurrent access, and production failures is harder.
In production Go systems:
*os.Fileis an owned operating-system resource.- File lifetime should be explicit.
O_CREATEdoes not mean exclusive creation.O_TRUNCcan destroy existing data before replacement succeeds.RenameandSyncsolve different problems.- Atomic publication and durability are separate design concerns.
The hard part of file handling is not calling an API.
It is understanding what the operating system guarantees.
1. os.File Is a Resource, Not Just a Filename
Many developers initially think about files as paths:
But after opening a file:
the program no longer owns only a string.
It owns an operating-system resource.
Conceptually:
The pathname and the opened resource are related, but they are not the same thing.
This distinction explains many production bugs.
2. Resource Ownership Should Be Obvious
The code that acquires a resource should usually own its lifetime.
Good:
The ownership is clear:
Avoid helpers that return open files without clear ownership rules.
This creates ambiguity:
Who closes f?
The caller?
The helper?
Both?
Neither?
In production systems, unclear ownership eventually becomes a resource leak.
3. Beware Deferred Close Inside Large Loops
This pattern looks clean:
But defer executes when the surrounding function returns.
If there are 100,000 files:
The program may exhaust file descriptors before cleanup happens.
Prefer:
The resource lifetime now matches one operation.
4. Close Can Be Part of the Error Path
For reading:
is normally enough.
Writing is different.
Example:
Why?
Because some failures may only become visible during close.
A successful write does not always mean:
The complete operation succeeded.
The right question is:
What guarantee does this function promise when it returns nil?
5. Open, Create, and OpenFile Express Different Intent
Reading an existing file
Meaning:
Open an existing resource for reading.
Creating or truncating
Equivalent idea:
Be careful.
Truncation is destructive.
Explicit semantics
The flags define concurrency behavior.
They are not just implementation details.
6. O_CREATE Does Not Mean Exclusive Creation
A common mistake:
Many developers read this as:
Create this file and fail if it already exists.
That is wrong.
It means:
Create the file if it does not exist.
If exclusive creation is required:
Now the filesystem performs:
7. Avoid Check-Then-Act Filesystem Logic
This is dangerous:
Because:
The assumption made by the first operation may no longer be true.
This is called a TOCTOU race:
Whenever possible:
Prefer:
over:
8. Why O_TRUNC Can Destroy Data
Consider:
The sequence:
Possible result:
or:
For temporary output this may be fine.
For:
- configuration files
- metadata
- checkpoints
- manifests
- user data
it is usually unsafe.
9. Safer Pattern: Generate Then Publish
A safer update model:
Example:
Write the complete new version first.
Only after success:
Readers see either:
or:
not:
10. Keep Temporary Files Near the Destination
Avoid:
because they may exist on different filesystems.
Prefer:
Then:
This makes the publication operation explicit.
11. Rename and Sync Solve Different Problems
These two operations are often confused.
Rename
Answers:
Can readers observe a partially published file?
It provides atomic namespace replacement.
Sync
Answers:
Has data been pushed toward stable storage?
Example:
The difference:
These are different guarantees.
12. Production Checklist
Before shipping file-writing code:
Ownership
- Who closes the file?
- Can an error path leak resources?
Creation
- Should existing files be overwritten?
- Should creation be exclusive?
Updates
- Can a crash leave partial data?
- Should you write a temporary file first?
Durability
- Is "write succeeded" enough?
- Do you need
Sync?
Concurrency
- Are you doing check-then-act logic?
- Can another process modify the file?
Conclusion
Production file handling in Go is not about remembering:
The important skill is understanding the relationship between:
Reliable Go programs make these boundaries explicit.
Use the operating system deliberately.
Do not rely on assumptions.