@@ -466,6 +466,16 @@ typedef struct XLogCtlData
466466
467467 XLogSegNo lastRemovedSegNo ; /* latest removed/recycled XLOG segment */
468468
469+ /*
470+ * Directory-durability frontier: the highest WAL segment whose pg_wal
471+ * entry is known durable, on timeline InstalledDurableTLI. Lets batched
472+ * recycling defer fsyncs without the write path entering a segment whose
473+ * rename is not yet flushed. Timeline-qualified so a stale value cannot
474+ * carry across a promotion. Protected by info_lck.
475+ */
476+ TimeLineID InstalledDurableTLI ;
477+ XLogSegNo InstalledDurableSeg ;
478+
469479 /* Fake LSN counter, for unlogged relations. */
470480 pg_atomic_uint64 unloggedLSN ;
471481
@@ -708,15 +718,18 @@ static void AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli,
708718static void XLogWrite (XLogwrtRqst WriteRqst , TimeLineID tli , bool flexible );
709719static bool InstallXLogFileSegment (XLogSegNo * segno , char * tmppath ,
710720 bool find_free , XLogSegNo max_segno ,
711- TimeLineID tli );
721+ TimeLineID tli , bool batch );
712722static void XLogFileClose (void );
723+ static void AdvanceInstalledDurableSeg (TimeLineID tli , XLogSegNo segno );
724+ static void EnsureXLogSegDirDurable (int fd , XLogSegNo segno , TimeLineID tli );
713725static void PreallocXlogFiles (XLogRecPtr endptr , TimeLineID tli );
714726static void RemoveTempXlogFiles (void );
715727static void RemoveOldXlogFiles (XLogSegNo segno , XLogRecPtr lastredoptr ,
716728 XLogRecPtr endptr , TimeLineID insertTLI );
717729static void RemoveXlogFile (const struct dirent * segment_de ,
718730 XLogSegNo recycleSegNo , XLogSegNo * endlogSegNo ,
719- TimeLineID insertTLI );
731+ TimeLineID insertTLI , bool batch ,
732+ List * * recycled_paths , List * * cleanup_names );
720733static void UpdateLastRemovedPtr (char * filename );
721734static void ValidateXLOGDirectoryStructure (void );
722735static void CleanupBackupHistory (void );
@@ -3395,7 +3408,7 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
33953408 */
33963409 max_segno = logsegno + CheckPointSegments ;
33973410 if (InstallXLogFileSegment (& installed_segno , tmppath , true, max_segno ,
3398- logtli ))
3411+ logtli , false ))
33993412 {
34003413 * added = true;
34013414 elog (DEBUG2 , "done creating and filling new WAL file" );
@@ -3436,16 +3449,24 @@ XLogFileInit(XLogSegNo logsegno, TimeLineID logtli)
34363449 Assert (logtli != 0 );
34373450
34383451 fd = XLogFileInitInternal (logsegno , logtli , & ignore_added , path );
3439- if (fd >= 0 )
3440- return fd ;
3441-
3442- /* Now open original target segment (might not be file I just made) */
3443- fd = BasicOpenFile (path , O_RDWR | PG_BINARY | O_CLOEXEC |
3444- get_sync_bit (wal_sync_method ));
34453452 if (fd < 0 )
3446- ereport (ERROR ,
3447- (errcode_for_file_access (),
3448- errmsg ("could not open file \"%s\": %m" , path )));
3453+ {
3454+ /* Now open original target segment (might not be file I just made) */
3455+ fd = BasicOpenFile (path , O_RDWR | PG_BINARY | O_CLOEXEC |
3456+ get_sync_bit (wal_sync_method ));
3457+ if (fd < 0 )
3458+ ereport (ERROR ,
3459+ (errcode_for_file_access (),
3460+ errmsg ("could not open file \"%s\": %m" , path )));
3461+ }
3462+
3463+ /*
3464+ * About to write WAL here. If the checkpointer recycled this segment with
3465+ * a deferred fsync, make its rename durable first. Covers both the reuse
3466+ * and reopen paths above; PreallocXlogFiles() only pre-creates and calls
3467+ * XLogFileInitInternal() directly, so it does not reach here.
3468+ */
3469+ EnsureXLogSegDirDurable (fd , logsegno , logtli );
34493470 return fd ;
34503471}
34513472
@@ -3579,7 +3600,7 @@ XLogFileCopy(TimeLineID destTLI, XLogSegNo destsegno,
35793600 /*
35803601 * Now move the segment into place with its final name.
35813602 */
3582- if (!InstallXLogFileSegment (& destsegno , tmppath , false, 0 , destTLI ))
3603+ if (!InstallXLogFileSegment (& destsegno , tmppath , false, 0 , destTLI , false ))
35833604 elog (ERROR , "InstallXLogFileSegment should not have failed" );
35843605}
35853606
@@ -3611,7 +3632,8 @@ XLogFileCopy(TimeLineID destTLI, XLogSegNo destsegno,
36113632 */
36123633static bool
36133634InstallXLogFileSegment (XLogSegNo * segno , char * tmppath ,
3614- bool find_free , XLogSegNo max_segno , TimeLineID tli )
3635+ bool find_free , XLogSegNo max_segno , TimeLineID tli ,
3636+ bool batch )
36153637{
36163638 char path [MAXPGPATH ];
36173639 struct stat stat_buf ;
@@ -3649,7 +3671,26 @@ InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
36493671 }
36503672
36513673 Assert (access (path , F_OK ) != 0 && errno == ENOENT );
3652- if (durable_rename (tmppath , path , LOG ) != 0 )
3674+
3675+ if (batch )
3676+ {
3677+ /*
3678+ * Plain rename; the caller fsyncs the renamed files and pg_wal once the
3679+ * whole pass is done, so the filesystem can coalesce the flushes. The
3680+ * pre-rename source fsync durable_rename() does is skipped: a WAL
3681+ * segment is already durable by the time it is recycled.
3682+ */
3683+ if (rename (tmppath , path ) < 0 )
3684+ {
3685+ LWLockRelease (ControlFileLock );
3686+ ereport (LOG ,
3687+ (errcode_for_file_access (),
3688+ errmsg ("could not rename file \"%s\" to \"%s\": %m" ,
3689+ tmppath , path )));
3690+ return false;
3691+ }
3692+ }
3693+ else if (durable_rename (tmppath , path , LOG ) != 0 )
36533694 {
36543695 LWLockRelease (ControlFileLock );
36553696 /* durable_rename already emitted log message */
@@ -3658,6 +3699,14 @@ InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
36583699
36593700 LWLockRelease (ControlFileLock );
36603701
3702+ /*
3703+ * The non-batched path used durable_rename(), so the entry is durable now;
3704+ * advance the frontier. In batched mode the caller advances it after its
3705+ * bulk pg_wal fsync.
3706+ */
3707+ if (!batch )
3708+ AdvanceInstalledDurableSeg (tli , * segno );
3709+
36613710 return true;
36623711}
36633712
@@ -3872,6 +3921,103 @@ UpdateLastRemovedPtr(char *filename)
38723921 SpinLockRelease (& XLogCtl -> info_lck );
38733922}
38743923
3924+ /*
3925+ * Advance the directory-durability frontier to at least (tli, segno).
3926+ * Called once a segment's rename into pg_wal has been made durable (by
3927+ * durable_rename(), or by the checkpointer's batched fsync of pg_wal).
3928+ *
3929+ * The frontier is timeline-qualified: a higher timeline always supersedes the
3930+ * previous one (its segments are different files needing their own fsync), and
3931+ * within a timeline the highest segment number wins.
3932+ */
3933+ static void
3934+ AdvanceInstalledDurableSeg (TimeLineID tli , XLogSegNo segno )
3935+ {
3936+ SpinLockAcquire (& XLogCtl -> info_lck );
3937+ if (tli > XLogCtl -> InstalledDurableTLI )
3938+ {
3939+ XLogCtl -> InstalledDurableTLI = tli ;
3940+ XLogCtl -> InstalledDurableSeg = segno ;
3941+ }
3942+ else if (tli == XLogCtl -> InstalledDurableTLI &&
3943+ segno > XLogCtl -> InstalledDurableSeg )
3944+ XLogCtl -> InstalledDurableSeg = segno ;
3945+ SpinLockRelease (& XLogCtl -> info_lck );
3946+ }
3947+
3948+ /*
3949+ * Ensure segment (segno, tli)'s pg_wal entry is durable before it is written.
3950+ *
3951+ * Batched recycling renames segments but defers the pg_wal fsync, so if the
3952+ * write path reaches a just-renamed segment first, its rename is not yet
3953+ * durable (issue_xlog_fsync() fsyncs only the file, never pg_wal). If the
3954+ * frontier does not already cover it, fsync the file and pg_wal here; the
3955+ * timeline-qualified check keeps a stale frontier from carrying across a
3956+ * promotion. Normally the checkpointer stays ahead and this is just a
3957+ * spinlock-protected compare.
3958+ */
3959+ static void
3960+ EnsureXLogSegDirDurable (int fd , XLogSegNo segno , TimeLineID tli )
3961+ {
3962+ bool durable ;
3963+
3964+ SpinLockAcquire (& XLogCtl -> info_lck );
3965+ durable = (tli == XLogCtl -> InstalledDurableTLI &&
3966+ segno <= XLogCtl -> InstalledDurableSeg );
3967+ SpinLockRelease (& XLogCtl -> info_lck );
3968+
3969+ if (durable )
3970+ return ;
3971+
3972+ /*
3973+ * fsync the file (carries the rename where we cannot fsync a directory,
3974+ * e.g. Windows) and fsync pg_wal (carries it on most Unix filesystems).
3975+ */
3976+ if (pg_fsync (fd ) != 0 )
3977+ {
3978+ char path [MAXPGPATH ];
3979+
3980+ XLogFilePath (path , tli , segno , wal_segment_size );
3981+ ereport (data_sync_elevel (ERROR ),
3982+ (errcode_for_file_access (),
3983+ errmsg ("could not fsync file \"%s\": %m" , path )));
3984+ }
3985+ fsync_fname (XLOGDIR , true);
3986+
3987+ AdvanceInstalledDurableSeg (tli , segno );
3988+ }
3989+
3990+ /*
3991+ * fsync a recycled segment file by name, tolerating concurrent removal.
3992+ * Unlike fsync_fname(), ENOENT is not an error: if another process removed
3993+ * the segment after we renamed it, its durability is no longer our concern.
3994+ */
3995+ static void
3996+ fsync_fname_recycled (const char * fname )
3997+ {
3998+ int fd ;
3999+
4000+ fd = BasicOpenFile (fname , O_RDWR | PG_BINARY | O_CLOEXEC );
4001+ if (fd < 0 )
4002+ {
4003+ if (errno == ENOENT )
4004+ return ;
4005+ ereport (data_sync_elevel (ERROR ),
4006+ (errcode_for_file_access (),
4007+ errmsg ("could not open file \"%s\": %m" , fname )));
4008+ }
4009+
4010+ if (pg_fsync (fd ) != 0 )
4011+ ereport (data_sync_elevel (ERROR ),
4012+ (errcode_for_file_access (),
4013+ errmsg ("could not fsync file \"%s\": %m" , fname )));
4014+
4015+ if (close (fd ) != 0 )
4016+ ereport (data_sync_elevel (ERROR ),
4017+ (errcode_for_file_access (),
4018+ errmsg ("could not close file \"%s\": %m" , fname )));
4019+ }
4020+
38754021/*
38764022 * Remove all temporary log files in pg_wal
38774023 *
@@ -3920,6 +4066,9 @@ RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr,
39204066 char lastoff [MAXFNAMELEN ];
39214067 XLogSegNo endlogSegNo ;
39224068 XLogSegNo recycleSegNo ;
4069+ List * recycled_paths = NIL ;
4070+ List * cleanup_names = NIL ;
4071+ ListCell * lc ;
39234072
39244073 /* Initialize info about where to try to recycle to */
39254074 XLByteToSeg (endptr , endlogSegNo , wal_segment_size );
@@ -3962,12 +4111,48 @@ RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr,
39624111 /* Update the last removed location in shared memory first */
39634112 UpdateLastRemovedPtr (xlde -> d_name );
39644113
3965- RemoveXlogFile (xlde , recycleSegNo , & endlogSegNo , insertTLI );
4114+ RemoveXlogFile (xlde , recycleSegNo , & endlogSegNo , insertTLI ,
4115+ true, & recycled_paths , & cleanup_names );
39664116 }
39674117 }
39684118 }
39694119
39704120 FreeDir (xldir );
4121+
4122+ /*
4123+ * Test hook: pause here, after the renames but before they are made
4124+ * durable, so a test can drive the write frontier into a just-recycled
4125+ * segment and crash, exercising the write-path barrier.
4126+ */
4127+ INJECTION_POINT ("wal-recycle-before-batch-fsync" , NULL );
4128+
4129+ /*
4130+ * Make the whole pass durable at once: fsync each recycled file, then
4131+ * fsync pg_wal a single time. Renaming first and fsyncing afterwards lets
4132+ * the filesystem coalesce what used to be one journal flush per segment.
4133+ * The per-file fsyncs mostly do nothing (the contents were already durable)
4134+ * but persist the rename where we cannot fsync a directory, e.g. Windows.
4135+ * A segment concurrently removed (e.g. by promotion cleanup) is skipped.
4136+ */
4137+ foreach (lc , recycled_paths )
4138+ fsync_fname_recycled ((char * ) lfirst (lc ));
4139+
4140+ if (recycled_paths != NIL || cleanup_names != NIL )
4141+ {
4142+ fsync_fname (XLOGDIR , true);
4143+ AdvanceInstalledDurableSeg (insertTLI , endlogSegNo - 1 );
4144+ }
4145+
4146+ /*
4147+ * Now that the batch is durable, drop the old segments' archive-status
4148+ * files. Doing it earlier could, after a crash that lost a rename, leave
4149+ * an old segment whose .done marker was already gone and re-archive it.
4150+ */
4151+ foreach (lc , cleanup_names )
4152+ XLogArchiveCleanup ((char * ) lfirst (lc ));
4153+
4154+ list_free_deep (recycled_paths );
4155+ list_free_deep (cleanup_names );
39714156}
39724157
39734158/*
@@ -4035,7 +4220,8 @@ RemoveNonParentXlogFiles(XLogRecPtr switchpoint, TimeLineID newTLI)
40354220 * - but seems safer to let them be archived and removed later.
40364221 */
40374222 if (!XLogArchiveIsReady (xlde -> d_name ))
4038- RemoveXlogFile (xlde , recycleSegNo , & endLogSegNo , newTLI );
4223+ RemoveXlogFile (xlde , recycleSegNo , & endLogSegNo , newTLI ,
4224+ false, NULL , NULL );
40394225 }
40404226 }
40414227
@@ -4058,7 +4244,8 @@ RemoveNonParentXlogFiles(XLogRecPtr switchpoint, TimeLineID newTLI)
40584244static void
40594245RemoveXlogFile (const struct dirent * segment_de ,
40604246 XLogSegNo recycleSegNo , XLogSegNo * endlogSegNo ,
4061- TimeLineID insertTLI )
4247+ TimeLineID insertTLI , bool batch ,
4248+ List * * recycled_paths , List * * cleanup_names )
40624249{
40634250 char path [MAXPGPATH ];
40644251#ifdef WIN32
@@ -4078,12 +4265,25 @@ RemoveXlogFile(const struct dirent *segment_de,
40784265 XLogCtl -> InstallXLogFileSegmentActive && /* callee rechecks this */
40794266 get_dirent_type (path , segment_de , false, DEBUG2 ) == PGFILETYPE_REG &&
40804267 InstallXLogFileSegment (endlogSegNo , path ,
4081- true, recycleSegNo , insertTLI ))
4268+ true, recycleSegNo , insertTLI , batch ))
40824269 {
40834270 ereport (DEBUG2 ,
40844271 (errmsg_internal ("recycled write-ahead log file \"%s\"" ,
40854272 segname )));
40864273 CheckpointStats .ckpt_segs_recycled ++ ;
4274+
4275+ /*
4276+ * Batched mode only renamed the segment; remember its new path so the
4277+ * caller can fsync it once all renames are done.
4278+ */
4279+ if (recycled_paths != NULL )
4280+ {
4281+ char dstpath [MAXPGPATH ];
4282+
4283+ XLogFilePath (dstpath , insertTLI , * endlogSegNo , wal_segment_size );
4284+ * recycled_paths = lappend (* recycled_paths , pstrdup (dstpath ));
4285+ }
4286+
40874287 /* Needn't recheck that slot on future iterations */
40884288 (* endlogSegNo )++ ;
40894289 }
@@ -4117,19 +4317,39 @@ RemoveXlogFile(const struct dirent *segment_de,
41174317 path )));
41184318 return ;
41194319 }
4120- rc = durable_unlink (newpath , LOG );
4320+ if (batch )
4321+ rc = unlink (newpath );
4322+ else
4323+ rc = durable_unlink (newpath , LOG );
41214324#else
4122- rc = durable_unlink (path , LOG );
4325+ if (batch )
4326+ rc = unlink (path );
4327+ else
4328+ rc = durable_unlink (path , LOG );
41234329#endif
41244330 if (rc != 0 )
41254331 {
4126- /* Message already logged by durable_unlink() */
4332+ /*
4333+ * In batched mode the caller's pg_wal fsync makes the unlink durable;
4334+ * plain unlink() only sets errno, so report failures here.
4335+ */
4336+ if (batch )
4337+ ereport (LOG ,
4338+ (errcode_for_file_access (),
4339+ errmsg ("could not remove file \"%s\": %m" , path )));
41274340 return ;
41284341 }
41294342 CheckpointStats .ckpt_segs_removed ++ ;
41304343 }
41314344
4132- XLogArchiveCleanup (segname );
4345+ /*
4346+ * Batched mode defers archive-status cleanup to the caller (after the
4347+ * batch fsync); unbatched mode is already durable, so clean up now.
4348+ */
4349+ if (cleanup_names != NULL )
4350+ * cleanup_names = lappend (* cleanup_names , pstrdup (segname ));
4351+ else
4352+ XLogArchiveCleanup (segname );
41334353}
41344354
41354355/*
0 commit comments