Skip to content

Commit 64e4c7b

Browse files
ayushslghackorum
authored andcommitted
Batch fsyncs when recycling WAL segments, guarded by a timeline-qualified durability frontier
The checkpointer recycles old WAL segments with durable_rename(), which fsyncs the renamed file and pg_wal once per segment, so a checkpoint that recycles many segments pays one filesystem journal flush each. Batch it instead: do all of a pass's renames and removals first, then fsync the recycled files and fsync pg_wal once. Deferring the fsync is a crash hazard, since a recycled segment can be written by the WAL path before its rename is durable and issue_xlog_fsync() never fsyncs pg_wal. XLogCtl keeps a durability frontier (InstalledDurableTLI, InstalledDurableSeg), advanced by the checkpointer after the batched fsync; XLogFileInit() consults it before handing out a segment and fsyncs the segment and pg_wal itself if it is not yet covered. The frontier is timeline-qualified so a stale value cannot mask an undurable rename after promotion. Archive-status files are dropped only after the batch is durable. An injection point and 056_wal_recycle_durability.pl exercise the crash window.
1 parent b597835 commit 64e4c7b

2 files changed

Lines changed: 363 additions & 23 deletions

File tree

src/backend/access/transam/xlog.c

Lines changed: 243 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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,
708718
static void XLogWrite(XLogwrtRqst WriteRqst, TimeLineID tli, bool flexible);
709719
static bool InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
710720
bool find_free, XLogSegNo max_segno,
711-
TimeLineID tli);
721+
TimeLineID tli, bool batch);
712722
static void XLogFileClose(void);
723+
static void AdvanceInstalledDurableSeg(TimeLineID tli, XLogSegNo segno);
724+
static void EnsureXLogSegDirDurable(int fd, XLogSegNo segno, TimeLineID tli);
713725
static void PreallocXlogFiles(XLogRecPtr endptr, TimeLineID tli);
714726
static void RemoveTempXlogFiles(void);
715727
static void RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr,
716728
XLogRecPtr endptr, TimeLineID insertTLI);
717729
static 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);
720733
static void UpdateLastRemovedPtr(char *filename);
721734
static void ValidateXLOGDirectoryStructure(void);
722735
static 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
*/
36123633
static bool
36133634
InstallXLogFileSegment(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)
40584244
static void
40594245
RemoveXlogFile(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

Comments
 (0)