fsync is a boolean, it defaults to on, and its context is sighup, so it can be changed with a configuration reload rather than a restart. It is also the most dangerous setting in postgresql.conf. Most of the parameters in this series, set wrong, cost you a bad plan or some wasted memory. This one, set wrong, costs you the cluster.

What off actually removes

PostgreSQL’s durability rests on a single rule: a change must be recorded durably in the WAL before the modified data page is permitted to reach disk. That is what write-ahead means, and everything else about crash recovery is built on top of it.

The trouble is that write() does not put anything on disk. It copies bytes into the kernel’s page cache and returns, and the kernel flushes them later, on its own schedule, in whatever order suits it. The fsync() system call is the only instrument PostgreSQL has for imposing order on that process, for saying: not past this point until these bytes are actually down.

Setting fsync = off tells PostgreSQL to stop making those calls. The writes still happen and the data still gets to disk eventually, but nothing constrains when, and nothing constrains the order. The write-ahead rule becomes a hope.

The sharpest consequence is what happens to checkpoints. A checkpoint is an assertion: everything before this point is durably on disk, therefore the WAL describing it can be recycled. With fsync off, that assertion is never verified, and PostgreSQL goes ahead and discards WAL it may still need. Now add a power failure. The data files can contain changes that no surviving WAL record describes, the WAL can end well before the changes that reached the platters, and individual pages can be torn halfway through a write. Recovery has nothing coherent to replay against.

What you get back is not a database missing its last few seconds of work. It is a directory of files that is no longer a valid database, and no amount of replay will make it one.

This is not synchronous_commit

Here is the confusion worth killing, because both settings get filed under durability and both make writes faster.

synchronous_commit = off tells PostgreSQL to report a commit as successful before that commit’s WAL record has been flushed. Crash inside that window and you lose the most recent transactions, bounded by three times wal_writer_delay. What you do not lose is consistency. The documentation is explicit that unlike fsync, setting this off creates no risk of database inconsistency: the cluster that comes back up is a correct database, just a slightly older one. Every transaction it retained is intact, and the ones it dropped were dropped whole.

fsync = off makes no such promise, and the docs do not pretend otherwise. Turning it off risks unrecoverable data corruption on power failure or system crash, and is advisable only if you can easily recreate the entire database from external data.

So the two settings are not points on a shared scale. One trades a bounded, well-defined quantity of recent work for speed. The other trades the integrity of everything.

The one honest use

There is a legitimate use for fsync = off, and it has a single test: if this cluster were destroyed right now, would you shrug and rebuild it from a script?

Restoring a large dump into a fresh cluster passes that test, because the dump is still sitting there. So do CI databases created and dropped per run, local development instances you can initdb again in ten seconds, and bulk loads you would restart from the source files anyway. In all of those, the worst case is that you run the job again, and fsync = off can make the job substantially faster while you do.

What does not pass the test is any cluster holding data you cannot regenerate, no matter how good the hardware underneath it. A battery-backed or flash-backed write cache does not make fsync = off safe. It makes fsync() cheap, which is a different and much better bargain: you keep the ordering guarantee and pay almost nothing for it.

Turning it back on is not instantaneous

This is the piece of small print that catches people, and it follows directly from the sighup context.

Because you can reload rather than restart, flipping fsync back to on feels immediate, and the server does start issuing sync calls right away. But every write that happened while it was off is still sitting in the kernel’s page cache, unsynced, and reloading does not retroactively flush any of it. For a window after you turn the setting back on, you have a cluster that believes it is durable and is not.

The documentation spells out the remedy. To recover reliably after changing fsync from off to on, you have to force all modified buffers in the kernel to durable storage, either with the cluster shut down or while fsync is on, by running initdb --sync-only, running sync, unmounting the file system, or rebooting the server.

That makes the standard bulk-load pattern a three-step operation rather than two. Turn fsync off, run the load, and then force everything down before you begin treating the cluster as real. The middle of those three is the one everybody skips.

What to do instead

If fsync is genuinely costing you time, measure before you reach for the switch. pg_test_fsync will tell you how the available wal_sync_method options actually perform on your storage, and a surprising number there often means the method is badly chosen or the storage is not what you were told it was.

If what you want is faster commits, synchronous_commit = off is the sanctioned way to get most of that benefit, and it can be set per transaction, so the transactions that genuinely need durability can keep it while the ones that do not go fast. That is the trade the documentation itself points you toward: much of the performance benefit of turning off fsync, without the attendant risks of data corruption.

One companion note. The docs suggest that if you turn fsync off you should consider turning full_page_writes off too, the reasoning being that you have already given up the guarantee the second one exists to protect. That parameter is next in the alphabet and gets its own entry.

Leave it on

The thing that ought to settle this is what happened when the community discovered that fsync() could fail to mean what it said even when you called it faithfully. The answer, after considerable argument, was to make the database crash on the spot rather than continue on an unverified assumption, which is the behavior data_sync_retry still encodes today.

That is how seriously PostgreSQL takes the possibility that a sync call it did make might have lied. Against that backdrop, deciding not to make the call at all is a strange thing to do on purpose, and outside of data you were prepared to throw away, nobody should.