Race between KeepFileRestoredFromArchive() and restartpoint
Noah Misch <noah@leadboat.com>
From: Noah Misch <noah@leadboat.com>
To: pgsql-hackers@postgresql.org
Date: 2021-02-02T15:14:16Z
Lists: pgsql-hackers
Attachments
- repro-KeepFileRestoredFromArchive-v0.patch (text/plain) patch v0
A race with KeepFileRestoredFromArchive() can cause a restartpoint to fail, as seen once on the buildfarm[1]. The attached patch adds a test case; it applies atop the "stop events" patch[2]. We have two systems for adding long-term pg_wal directory entries. KeepFileRestoredFromArchive() adds them during archive recovery, while InstallXLogFileSegment() does so at all times. Unfortunately, InstallXLogFileSegment() happens throughout archive recovery, via the checkpointer recycling segments and calling PreallocXlogFiles(). Multiple processes can run InstallXLogFileSegment(), which uses ControlFileLock to represent the authority to modify the directory listing of pg_wal. KeepFileRestoredFromArchive() just assumes it controls pg_wal. Recycling and preallocation are wasteful during archive recovery, because KeepFileRestoredFromArchive() unlinks every entry in its path. I propose to fix the race by adding an XLogCtl flag indicating which regime currently owns the right to add long-term pg_wal directory entries. In the archive recovery regime, the checkpointer will not preallocate and will unlink old segments instead of recycling them (like wal_recycle=off). XLogFileInit() will fail. Notable alternatives: - Release ControlFileLock at the end of XLogFileInit(), not at the end of InstallXLogFileSegment(). Add ControlFileLock acquisition to KeepFileRestoredFromArchive(). This provides adequate mutual exclusion, but XLogFileInit() could return a file descriptor for an unlinked file. That's fine for PreallocXlogFiles(), but it feels wrong. - During restartpoints, never preallocate or recycle segments. (Just delete obsolete WAL.) By denying those benefits, this presumably makes streaming recovery less efficient. - Make KeepFileRestoredFromArchive() call XLogFileInit() to open a segment, then copy bytes. This is simple, but it multiplies I/O. That might be negligible on account of caching, or it might not be. A variant, incurring extra fsyncs, would be to use durable_rename() to replace the segment we get from XLogFileInit(). - Make KeepFileRestoredFromArchive() rename without first unlinking. This avoids checkpoint failure, but a race could trigger noise from the LOG message in InstallXLogFileSegment -> durable_rename_excl. Does anyone prefer some alternative? It's probably not worth back-patching anything for a restartpoint failure this rare, because most restartpoint outcomes are not user-visible. Thanks, nm [1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=mandrill&dt=2020-10-05%2023%3A02%3A17 [2] https://postgr.es/m/CAPpHfdtSEOHX8dSk9Qp%2BZ%2B%2Bi4BGQoffKip6JDWngEA%2Bg7Z-XmQ%40mail.gmail.com
Commits
-
Skip WAL recycling and preallocation during archive recovery.
- a5b0c06daae9 13.21 landed
- 014a508009df 14.18 landed
- cc2c7d65fc27 15.0 landed
-
Don't ERROR on PreallocXlogFiles() race condition.
- cbed472a9341 13.21 landed
- 675b771ca5f2 14.18 landed
- 2b3e4672f760 15.0 landed
-
Remove XLogFileInit() ability to unlink a pre-existing file.
- d0b6acaf04a2 13.21 landed
- b494640e8297 14.18 landed
- 421484f79c0b 15.0 landed
-
In XLogFileInit(), fix *use_existent postcondition to suit callers.
- 20e5ef3ca70c 13.21 landed
- 8967dddf086e 14.18 landed
- 85656bc3050f 15.0 landed
-
Remove XLogFileInit() ability to skip ControlFileLock.
- df8ec9634ccd 13.21 landed
- 6b168c1299c5 14.18 landed
- c53c6b98d38a 15.0 landed
-
Add HINT for restartpoint race with KeepFileRestoredFromArchive().
- cf86fddc1c92 11.17 landed
- 41f613fc257c 12.12 landed
- 2ffcb7dce806 10.22 landed
- ad8ebcfe9662 13.8 landed
- 8ad6c5dbbe5a 14.5 landed
-
Complete TODO item:
- 63653f7ffaba 7.3.1 cited