February 14, 2019·5 min read·engineeringpostgresqldurabilitystorage

PostgreSQL 11.2 stops retrying failed fsync calls

PostgreSQL changes its response to failed disk synchronization because a successful retry can conceal lost data. The same update repairs constraints and indexes affected by newer table features.

A successful retry does not always mean that the failed operation eventually completed. PostgreSQL 11.2 changes its handling of fsync() failures because some operating systems discard the dirty buffers after a failed write. A second synchronization request can then succeed with nothing left to write. The database sees success, although the data it expected to preserve has disappeared.

The February 14 release announcement describes the new response. PostgreSQL panics after the failure instead of retrying by default. Recovery can then replay the write-ahead log, which may contain the only remaining copy of the data. The 11.2 release notes credit Craig Ringer and Thomas Munro with this change.

I value PostgreSQL's transaction behavior because it makes failures easier to reason about. This update concerns a lower level of that promise. Transaction semantics depend on the database retaining a trustworthy account of what reached storage, and a misleading success response undermines that account.

Why another synchronization request is insufficient

The announcement distinguishes the failure of a synchronization call from the continued availability of its input. Under the old behavior, retrying assumed that the operating system still retained the modified buffers. That assumption is necessary for the retry to complete the original job. Once those buffers have been discarded, a successful call cannot reconstruct their contents.

This is a possible corruption scenario, even though the reported operating-system behavior is rare. The error handling has to account for what remains after the error. An interface that returns failure without preserving the pending data gives its caller less opportunity to recover than an interface that keeps the original buffers available.

PostgreSQL's new response uses another representation of the changes. The release notes explain that WAL may retain the only remaining copy. Raising a panic allows recovery to replay that record. This is expensive compared with a successful retry, but the retry is useful only when the operating system still has the data that needs writing.

The project describes few alternatives and acknowledges the inefficiency. That is a reasonable tradeoff for a database. Continuing after a storage error should require evidence that the database can still preserve its contents. Availability gained by accepting a false success result would make subsequent database behavior harder to trust.

The change also shows why an application-level retry policy is an incomplete guide to database storage. Application retries often assume that the request can be reconstructed from arguments still held by the caller. Here, the object being synchronized has state in the kernel. Its survival after failure depends on the operating system's behavior.

The configuration option encodes a specific assumption

A new server setting, data_sync_retry, controls the behavior. Setting it to on restores retrying. The announcement limits that advice to operators who are certain that their kernel does not discard dirty data buffers in this situation. The relevant distinction is the kernel's error semantics, not whether a restart would be inconvenient.

The default response therefore deserves to remain the default unless that assumption has been established. A configuration change that merely suppresses a disruptive failure can also suppress the recovery path introduced to protect the data. An error message and a restarted server are visible consequences. Lost writes followed by apparent success are much harder to identify from ordinary application behavior.

My preference is to keep the conservative behavior here. PostgreSQL is often selected because applications need the database to enforce correctness. A storage failure is precisely where that preference should influence configuration, even when recovery interrupts service.

The update covers every supported branch listed in the announcement, including 10.7, 9.6.12, 9.5.16 and 9.4.21. This is not a feature available only to users adopting the newest major version. The storage concern applies across those branches, and the maintenance releases carry the changed default to them.

New table features also need constraint correctness

The rest of 11.2 includes fixes that affect the meaning of stored data. Unique indexes with INCLUDE columns on partitioned tables did not check uniqueness correctly. The update also ensures that partitions honor their parent table's NOT NULL constraints. These are cases where a declared schema must remain effective through the implementation of partitioning.

Attaching and detaching partitions receive related repairs. Foreign-key enforcement triggers now follow those operations correctly. Detaching a partition uses a safer table lock level because the previous level could permit concurrent DDL with bad results. These changes concern the coordination needed to preserve schema rules while the partition structure changes.

The fast-default mechanism also receives an index-corruption fix. The notes identify the affected case as a column added to an existing populated table with a constant non-null default. Another fix adjusts fast-default values during a later type change. A shortcut that avoids immediate physical work still has to preserve the value that every later database operation should observe.

These repairs matter to schema evolution in particular. A migration may finish successfully while a subsequent index operation encounters a representation the implementation handled incorrectly. Transactional DDL remains valuable, but it does not remove the need for correctness in the mechanisms that represent newly added columns or maintain partition constraints.

Some performance changes are reversed for correctness

The GIN vacuum fix partially reverses a performance improvement introduced in 10.0. That earlier change reduced the number of locked index pages during deletion of a posting-tree page. The resulting implementation could deadlock with concurrent insertions, so the project removes that optimization pending closer analysis.

This is useful context for interpreting maintenance releases. A fix may deliberately restore more locking. The release does not claim that fewer locks are always preferable, and its explanation identifies the concurrency failure that makes the older optimization unsuitable. Other GIN fixes concern hot-standby replay and reclaiming empty pages too early for concurrent searches.

Applying 11.2 to an existing 11.x installation does not require a dump and restore. The announcement describes shutting down PostgreSQL and replacing its binaries, while noting that skipped maintenance releases can have additional instructions. The release repairs both failure handling and ordinary execution, including cases where an index or constraint could give an incorrect result without a storage device reporting any error.