Should os.WriteFile call Sync()?

238 views
Skip to first unread message

Karel Bílek

unread,
Apr 16, 2025, 6:10:08 PMApr 16
to golang-nuts
I was quite surprised recently that, at least on linux, file.Close() does not guarantee file.Sync(); in some edge-cases, files can be not put properly to filesystem.

If the data integrity is critical, it seems better to call file.Sync() before file.Close().

(Linux has this in close / fsync syscall manpages.)

I am surprised that the docs don't mention this; also, that os.WriteFile does not call file sync, so even when os.WriteFile() returns no error, it doesn't mean the file is actually written on the disk!

I have 2 questions

1) should this be documented in godoc somewhere?
2) should os.WriteFile explicitly call fsync?


Robert Engels

unread,
Apr 17, 2025, 9:18:44 AMApr 17
to Karel Bílek, golang-nuts
Even if the OS writes it to disk there is the possibility of failure due to lazy writes by the drive itself. Which is why you need full fsync- which is coordinated all the way down. It is also many orders of magnitude slower. See this for performance difference details https://github.com/robaho/cpp_leveldb?tab=readme-ov-file#performance

On Apr 16, 2025, at 6:09 PM, Karel Bílek <[email protected]> wrote:

I was quite surprised recently that, at least on linux, file.Close() does not guarantee file.Sync(); in some edge-cases, files can be not put properly to filesystem.
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/dd574b31-8146-4710-9937-a56e8ecbe428n%40googlegroups.com.

Robert Engels

unread,
Apr 17, 2025, 9:37:25 AMApr 17
to Karel Bílek, golang-nuts
Also, the real world edge case you are referring to is only (almost always) a hard system kernel crash - anything else and it will be written to disk. Also, even if it it written to disk you might still have a failure - which is can be mitigated with with redundant/raid storage. 

Anyway, the option is there and anyone doing database or critical system design knows when and why to use it. 

On Apr 17, 2025, at 9:18 AM, Robert Engels <[email protected]> wrote:



Karel Bílek

unread,
Apr 17, 2025, 2:02:21 PMApr 17
to Robert Engels, golang-nuts
> Which is why you need full fsync- which is coordinated all the way down

I don't understand this reply; file.Sync() does fsync syscall on POSIX
(and with F_FULLFSYNC on macOS).

Yes it's slower, but it's not really documented that it might be
necessary. (It did cause us trouble in reality when the whole OS
crashed for an unrelated reason.)

Bushnell, Thomas

unread,
Apr 17, 2025, 2:16:37 PMApr 17
to Karel Bílek, Robert Engels, golang-nuts
I'm sorry you experienced data loss here, but I don't think Go is remiss. The only thing in the entire API that refers to stable storage is the Sync call; on a correctly-behaving system you do not ever need to call Sync. It is only needed if you are protecting against an incorrectly-behaving system. (The kernel will always be careful to sync before shutting down, for example.)

Fault tolerance is not just about calling sync now and then; sync is a useful tool but it is neither necessary nor sufficient to protect against hardware failures, unexpected shutdowns, and the like.

-----Original Message-----
From: [email protected] <[email protected]> On Behalf Of Karel Bílek
Sent: Thursday, April 17, 2025 3:01 PM
To: Robert Engels <[email protected]>
Cc: golang-nuts <[email protected]>
Subject: Re: [go-nuts] Should os.WriteFile call Sync()?

This message was sent by an external party.
To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/CAGUkT8bqhzjCqP3i2GTV74F2EYbFcvvmpqZCqh8rYucdG-2Bcw%40mail.gmail.com.

robert engels

unread,
Apr 17, 2025, 3:04:15 PMApr 17
to Karel Bílek, golang-nuts
If you review the chart I showed, you see that sync on Go O_SYNC does not do a full fsync (based on the timings).


and how a “full sync” is performed in C++


and thus the many orders of magnitude slower performance - which is why you would never want O_SYNC to be a “full sync” by default.


Brian Candler

unread,
Apr 18, 2025, 6:31:52 AMApr 18
to golang-nuts
On Thursday, 17 April 2025 at 00:10:08 UTC+1 Karel Bílek wrote:
I was quite surprised recently that, at least on linux, file.Close() does not guarantee file.Sync(); in some edge-cases, files can be not put properly to filesystem.

If the data integrity is critical, it seems better to call file.Sync() before file.Close().

It depends what you mean by "data integrity". If you want to be reasonably sure[^1] that the data has been persisted to disk *before you continue with anything else*, e.g. in case the power is pulled out later, then yes, you need to fsync the file [^2].

However, of course, someone could pull the power plug *before* your program gets to the point of calling fsync() and/or close().  Therefore, it's really a question of how your application recovers from errors when it next starts, and/or how it communicates with other applications. For example, say that its next step is to send a reply saying "yes I got your request, you don't need to worry about it any more", then maybe the contract says that the other party is entitled to assume that the message has been persisted safely when it receives that message. Therefore, you should persist to disk before sending the reply.

Theodore Ts'o explains this very nicely:
In particular, people were assuming that if you write and close a file (without syncing), followed by an atomic rename, the filesystem would guarantee that *either* the old file *or* the new file would be persisted to disk. Because of delayed allocation this was a false assumption - but it is so ingrained in many pieces of code that a workaround was put into ext4 so that people get the behaviour they "expect".

Aside: sometimes all that you need for data integrity is sequencing, which can be enforced by write barriers, without having to wait for things to complete (since the write barrier passes down the stack even through deferred writes).

For example, suppose your application did the following:
- write chunk A
- barrier
- write chunk B

The power could be pulled out *at any point*, even in the middle of a write. On restart you will have to deal with these situations:
- corrupt or incomplete A only
- complete A, corrupt or incomplete B
- complete A, complete B

But with a write barrier, you will never see:
- corrupt or incomplete A, corrupt or incomplete B
- corrupt or incomplete A, complete B

[^1] if the device lies, e.g. it says the data has been put in persistent storage but it's only in non-battery-backed RAM, then all bets are off.

[^2] it's also essential to check the return code from fsync():
unread,
Apr 20, 2025, 5:10:42 PMApr 20
to golang-nuts
Reply all
Reply to author
Forward
0 new messages