agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH 4/5] Use only xlogreader.c:XLogRead() 8+ messages / 2 participants [nested] [flat]
* [PATCH 4/5] Use only xlogreader.c:XLogRead() @ 2019-07-09 09:54 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 8+ messages in thread From: Antonin Houska @ 2019-07-09 09:54 UTC (permalink / raw) The implementations in xlogutils.c and walsender.c are just renamed now, to be removed by the following diff. --- src/backend/access/transam/xlogreader.c | 128 ++++++++++++++++++++++++++++++++ src/backend/access/transam/xlogutils.c | 40 ++++++++-- src/backend/replication/walsender.c | 125 ++++++++++++++++++++++++++++++- src/bin/pg_waldump/pg_waldump.c | 59 ++++++++++++++- src/include/access/xlogreader.h | 19 +++++ 5 files changed, 359 insertions(+), 12 deletions(-) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index bbe383240d..3f8db2f25d 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -17,6 +17,8 @@ */ #include "postgres.h" +#include <unistd.h> + #include "access/transam.h" #include "access/xlogrecord.h" #include "access/xlog_internal.h" @@ -26,6 +28,7 @@ #include "replication/origin.h" #ifndef FRONTEND +#include "pgstat.h" #include "utils/memutils.h" #endif @@ -1009,7 +1012,132 @@ XLogSegmentInit(XLogSegment *seg, int size) seg->tli = 0; seg->dir = NULL; seg->size = size; + seg->last_req = 0; +} + +/* + * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'. If + * tli is passed, get the data from timeline *tli. 'pos' is the current + * position in the XLOG file and openSegment is a callback that opens the next + * segment for reading. + * + * Returns true if the call succeeded, false if it failed. Caller should check + * errno in the case of failure. seg->last_req might also be useful for error + * messages. + * + * XXX probably this should be improved to suck data directly from the + * WAL buffers when possible. + */ +bool +XLogRead(char *buf, XLogRecPtr startptr, Size count, + TimeLineID *tli, XLogSegment *seg, XLogOpenSegment openSegment) +{ + char *p; + XLogRecPtr recptr; + Size nbytes; + + p = buf; + recptr = startptr; + nbytes = count; + + while (nbytes > 0) + { + int readbytes; + + seg->off = XLogSegmentOffset(recptr, seg->size); + + if (seg->file < 0 || + !XLByteInSeg(recptr, seg->num, seg->size) || + (tli != NULL && *tli != seg->tli)) + { + XLogSegNo nextSegNo; + + /* Switch to another logfile segment */ + if (seg->file >= 0) + close(seg->file); + + XLByteToSeg(recptr, nextSegNo, seg->size); + + /* Open the next segment in the caller's way. */ + openSegment(nextSegNo, tli, seg); + + /* + * If the function is called by the XLOG reader, the reader will + * eventually set both "num" and "off". However we need to care + * about them too because the function can also be used directly, + * see walsender.c. + */ + seg->num = nextSegNo; + seg->off = 0; + } + + /* How many bytes are within this segment? */ + if (nbytes > (seg->size - seg->off)) + seg->last_req = seg->size - seg->off; + else + seg->last_req = nbytes; + +#ifndef FRONTEND + pgstat_report_wait_start(WAIT_EVENT_WAL_READ); +#endif + + /* + * Failure to read the data does not necessarily imply non-zero errno. + * Set it to zero so that caller can distinguish the failure that does + * not affect errno. + */ + errno = 0; + + readbytes = pg_pread(seg->file, p, seg->last_req, seg->off); + +#ifndef FRONTEND + pgstat_report_wait_end(); +#endif + + if (readbytes <= 0) + return false; + + /* Update state for read */ + recptr += readbytes; + nbytes -= readbytes; + p += readbytes; + + /* + * If the function is called by the XLOG reader, the reader will + * eventually set this field. However we need to care about it too + * because the function can also be used directly (see walsender.c). + */ + seg->off += readbytes; + } + + return true; +} + +#ifndef FRONTEND +/* + * Backend-specific code to handle errors encountered by XLogRead(). + */ +void +XLogReadProcessError(XLogSegment *seg) +{ + if (errno != 0) + { + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from log segment %s, offset %u, length %zu: %m", + XLogFileNameP(seg->tli, seg->num), seg->off, + (Size) seg->last_req))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not read from log segment %s, offset %u: length %zu", + XLogFileNameP(seg->tli, seg->num), seg->off, + (Size) seg->last_req))); + } } +#endif /* ---------------------------------------- * Functions for decoding the data and block references in a record. diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 836c2e2927..899bf1b551 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -653,8 +653,8 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum, * frontend). Probably these should be merged at some point. */ static void -XLogRead(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, - Size count) +XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, + Size count) { char *p; XLogRecPtr recptr; @@ -896,6 +896,35 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa } } +/* + * Callback for XLogRead() to open the next segment. + */ +static void +read_local_xlog_page_open_segment(XLogSegNo nextSegNo, TimeLineID *tli, + XLogSegment *seg) +{ + char path[MAXPGPATH]; + + XLogFilePath(path, *tli, nextSegNo, seg->size); + seg->file = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (seg->file < 0) + { + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + path))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } + + seg->tli = *tli; +} + /* * read_page callback for reading local xlog files * @@ -1022,10 +1051,9 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * as 'count', read the whole page anyway. It's guaranteed to be * zero-padded up to the page boundary if it's incomplete. */ - XLogRead(cur_page, state->seg.size, state->seg.tli, targetPagePtr, - XLOG_BLCKSZ); - state->seg.tli = pageTLI; - + if (!XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ, &pageTLI, + &state->seg, read_local_xlog_page_open_segment)) + XLogReadProcessError(&state->seg); /* number of valid bytes in the buffer */ return count; } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 3b5fe2cb94..c64a4bf9f8 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -247,7 +247,9 @@ static void LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time); static TimeOffset LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now); static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch); -static void XLogRead(char *buf, XLogRecPtr startptr, Size count); +static void WalSndOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli, + XLogSegment *seg); +static void XLogReadOld(char *buf, XLogRecPtr startptr, Size count); /* Initialize walsender process before entering the main command loop */ @@ -782,7 +784,9 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req count = flushptr - targetPagePtr; /* part of the page available */ /* now actually read the data, we know it's there */ - XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ); + if (!XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ, NULL, sendSeg, + WalSndOpenSegment)) + XLogReadProcessError(sendSeg); return count; } @@ -2359,7 +2363,7 @@ WalSndKill(int code, Datum arg) * more than one. */ static void -XLogRead(char *buf, XLogRecPtr startptr, Size count) +XLogReadOld(char *buf, XLogRecPtr startptr, Size count) { char *p; XLogRecPtr recptr; @@ -2532,6 +2536,76 @@ retry: } } +/* + * Callback for XLogRead() to open the next segment. + */ +void +WalSndOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli, XLogSegment *seg) +{ + char path[MAXPGPATH]; + + /* + * The timeline is determined below, caller should not do anything about + * it. + */ + Assert(tli == NULL); + + /*------- + * When reading from a historic timeline, and there is a timeline switch + * within this segment, read from the WAL segment belonging to the new + * timeline. + * + * For example, imagine that this server is currently on timeline 5, and + * we're streaming timeline 4. The switch from timeline 4 to 5 happened at + * 0/13002088. In pg_wal, we have these files: + * + * ... + * 000000040000000000000012 + * 000000040000000000000013 + * 000000050000000000000013 + * 000000050000000000000014 + * ... + * + * In this situation, when requested to send the WAL from segment 0x13, on + * timeline 4, we read the WAL from file 000000050000000000000013. Archive + * recovery prefers files from newer timelines, so if the segment was + * restored from the archive on this server, the file belonging to the old + * timeline, 000000040000000000000013, might not exist. Their contents are + * equal up to the switchpoint, because at a timeline switch, the used + * portion of the old segment is copied to the new file. ------- + */ + seg->tli = sendTimeLine; + if (sendTimeLineIsHistoric) + { + XLogSegNo endSegNo; + + XLByteToSeg(sendTimeLineValidUpto, endSegNo, seg->size); + if (seg->num == endSegNo) + seg->tli = sendTimeLineNextTLI; + } + + XLogFilePath(path, seg->tli, nextSegNo, seg->size); + seg->file = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (seg->file < 0) + { + /* + * If the file is not found, assume it's because the standby asked for + * a too old WAL segment that has already been removed or recycled. + */ + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + XLogFileNameP(seg->tli, seg->num)))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } +} + /* * Send out the WAL in its normal physical/stored form. * @@ -2549,6 +2623,7 @@ XLogSendPhysical(void) XLogRecPtr startptr; XLogRecPtr endptr; Size nbytes; + XLogSegNo segno; /* If requested switch the WAL sender to the stopping state. */ if (got_STOPPING) @@ -2764,7 +2839,49 @@ XLogSendPhysical(void) * calls. */ enlargeStringInfo(&output_message, nbytes); - XLogRead(&output_message.data[output_message.len], startptr, nbytes); + +retry: + if (!XLogRead(&output_message.data[output_message.len], startptr, nbytes, + NULL, /* WalSndOpenSegment will determine TLI */ + sendSeg, + WalSndOpenSegment)) + XLogReadProcessError(sendSeg); + + /* + * After reading into the buffer, check that what we read was valid. We do + * this after reading, because even though the segment was present when we + * opened it, it might get recycled or removed while we read it. The + * read() succeeds in that case, but the data we tried to read might + * already have been overwritten with new WAL records. + */ + XLByteToSeg(startptr, segno, wal_segment_size); + CheckXLogRemoved(segno, ThisTimeLineID); + + /* + * During recovery, the currently-open WAL file might be replaced with the + * file of the same name retrieved from archive. So we always need to + * check what we read was valid after reading into the buffer. If it's + * invalid, we try to open and read the file again. + */ + if (am_cascading_walsender) + { + WalSnd *walsnd = MyWalSnd; + bool reload; + + SpinLockAcquire(&walsnd->mutex); + reload = walsnd->needreload; + walsnd->needreload = false; + SpinLockRelease(&walsnd->mutex); + + if (reload && sendSeg->file >= 0) + { + close(sendSeg->file); + sendSeg->file = -1; + + goto retry; + } + } + output_message.len += nbytes; output_message.data[output_message.len] = '\0'; diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index a16793bb8b..3e09519f88 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -296,6 +296,45 @@ identify_target_directory(XLogDumpPrivate *private, char *directory, fatal_error("could not find any WAL file"); } +static void +XLogDumpOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli, XLogSegment *seg) +{ + char fname[MAXPGPATH]; + int tries; + + XLogFileName(fname, *tli, nextSegNo, seg->size); + + /* + * In follow mode there is a short period of time after the server has + * written the end of the previous file before the new file is available. + * So we loop for 5 seconds looking for the file to appear before giving + * up. + */ + for (tries = 0; tries < 10; tries++) + { + seg->file = open_file_in_directory(seg->dir, fname); + if (seg->file >= 0) + break; + if (errno == ENOENT) + { + int save_errno = errno; + + /* File not there yet, try again */ + pg_usleep(500 * 1000); + + errno = save_errno; + continue; + } + /* Any other error, fall through and fail */ + break; + } + + if (seg->file < 0) + fatal_error("could not find file \"%s\": %s", + fname, strerror(errno)); + seg->tli = *tli; +} + /* * Read count bytes from a segment file in the specified directory, for the * given timeline, containing the specified record pointer; store the data in @@ -441,8 +480,24 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, } } - XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr, - readBuff, count); + if (!XLogRead(readBuff, targetPagePtr, count, &private->timeline, + &state->seg, XLogDumpOpenSegment)) + { + XLogSegment *seg = &state->seg; + int err = errno; + char fname[MAXPGPATH]; + int save_errno = errno; + + XLogFileName(fname, seg->tli, seg->num, seg->size); + errno = save_errno; + + if (errno != 0) + fatal_error("could not read from log file %s, offset %u, length %zu: %s", + fname, seg->off, (Size) seg->last_req, strerror(err)); + else + fatal_error("could not read from log file %s, offset %u: length: %zu", + fname, seg->off, (Size) seg->last_req); + } return count; } diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index 26da3613f7..856e1b2e00 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -39,6 +39,7 @@ typedef struct XLogSegment char *dir; /* directory (only needed by frontends) */ int size; /* segment size */ + int last_req; /* the amount of data requested last time */ } XLogSegment; typedef struct XLogReaderState XLogReaderState; @@ -221,7 +222,25 @@ extern bool XLogReaderValidatePageHeader(XLogReaderState *state, extern XLogRecPtr XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr); #endif /* FRONTEND */ +/* + * Callback to open the specified XLOG segment nextSegNo in timeline *tli for + * reading, and assign the descriptor to ->file. BasicOpenFile() is the + * preferred way to open the segment file in backend code, whereas open(2) + * should be used in frontend. + * + * If NULL is passed for tli, the callback must determine the timeline + * itself. In any case it's supposed to eventually set ->tli. + */ +typedef void (*XLogOpenSegment) (XLogSegNo nextSegNo, TimeLineID *tli, + XLogSegment *seg); + extern void XLogSegmentInit(XLogSegment *seg, int size); +extern bool XLogRead(char *buf, XLogRecPtr startptr, Size count, + TimeLineID *tli, XLogSegment *seg, + XLogOpenSegment openSegment); +#ifndef FRONTEND +void XLogReadProcessError(XLogSegment *seg); +#endif /* Functions for decoding an XLogRecord */ -- 2.16.4 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v04-0005-Remove-the-old-implemenations-of-XLogRead.patch ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH 3/4] Use only xlogreader.c:XLogRead() @ 2019-09-09 09:53 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 8+ messages in thread From: Antonin Houska @ 2019-09-09 09:53 UTC (permalink / raw) The implementations in xlogutils.c and walsender.c are just renamed now, to be removed by the following diff. --- src/backend/access/transam/xlogreader.c | 128 ++++++++++++++++++++++++ src/backend/access/transam/xlogutils.c | 40 ++++++-- src/backend/replication/walsender.c | 125 ++++++++++++++++++++++- src/bin/pg_waldump/pg_waldump.c | 59 ++++++++++- src/include/access/xlogreader.h | 19 ++++ 5 files changed, 359 insertions(+), 12 deletions(-) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index 7b4ec81493..2a80bc5823 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -17,6 +17,8 @@ */ #include "postgres.h" +#include <unistd.h> + #include "access/transam.h" #include "access/xlogrecord.h" #include "access/xlog_internal.h" @@ -27,6 +29,7 @@ #ifndef FRONTEND #include "miscadmin.h" +#include "pgstat.h" #include "utils/memutils.h" #endif @@ -1011,7 +1014,132 @@ XLogSegmentInit(XLogSegment *seg, int size) seg->tli = 0; seg->dir = NULL; seg->size = size; + seg->last_req = 0; +} + +/* + * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'. If + * tli is passed, get the data from timeline *tli. 'pos' is the current + * position in the XLOG file and openSegment is a callback that opens the next + * segment for reading. + * + * Returns true if the call succeeded, false if it failed. Caller should check + * errno in the case of failure. seg->last_req might also be useful for error + * messages. + * + * XXX probably this should be improved to suck data directly from the + * WAL buffers when possible. + */ +bool +XLogRead(char *buf, XLogRecPtr startptr, Size count, + TimeLineID *tli, XLogSegment *seg, XLogOpenSegment openSegment) +{ + char *p; + XLogRecPtr recptr; + Size nbytes; + + p = buf; + recptr = startptr; + nbytes = count; + + while (nbytes > 0) + { + int readbytes; + + seg->off = XLogSegmentOffset(recptr, seg->size); + + if (seg->file < 0 || + !XLByteInSeg(recptr, seg->num, seg->size) || + (tli != NULL && *tli != seg->tli)) + { + XLogSegNo nextSegNo; + + /* Switch to another logfile segment */ + if (seg->file >= 0) + close(seg->file); + + XLByteToSeg(recptr, nextSegNo, seg->size); + + /* Open the next segment in the caller's way. */ + openSegment(nextSegNo, tli, seg); + + /* + * If the function is called by the XLOG reader, the reader will + * eventually set both "num" and "off". However we need to care + * about them too because the function can also be used directly, + * see walsender.c. + */ + seg->num = nextSegNo; + seg->off = 0; + } + + /* How many bytes are within this segment? */ + if (nbytes > (seg->size - seg->off)) + seg->last_req = seg->size - seg->off; + else + seg->last_req = nbytes; + +#ifndef FRONTEND + pgstat_report_wait_start(WAIT_EVENT_WAL_READ); +#endif + + /* + * Failure to read the data does not necessarily imply non-zero errno. + * Set it to zero so that caller can distinguish the failure that does + * not affect errno. + */ + errno = 0; + + readbytes = pg_pread(seg->file, p, seg->last_req, seg->off); + +#ifndef FRONTEND + pgstat_report_wait_end(); +#endif + + if (readbytes <= 0) + return false; + + /* Update state for read */ + recptr += readbytes; + nbytes -= readbytes; + p += readbytes; + + /* + * If the function is called by the XLOG reader, the reader will + * eventually set this field. However we need to care about it too + * because the function can also be used directly (see walsender.c). + */ + seg->off += readbytes; + } + + return true; +} + +#ifndef FRONTEND +/* + * Backend-specific code to handle errors encountered by XLogRead(). + */ +void +XLogReadProcessError(XLogSegment *seg) +{ + if (errno != 0) + { + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from log segment %s, offset %u, length %zu: %m", + XLogFileNameP(seg->tli, seg->num), seg->off, + (Size) seg->last_req))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not read from log segment %s, offset %u: length %zu", + XLogFileNameP(seg->tli, seg->num), seg->off, + (Size) seg->last_req))); + } } +#endif /* ---------------------------------------- * Functions for decoding the data and block references in a record. diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 424bb06919..83b014e04c 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -653,8 +653,8 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum, * frontend). Probably these should be merged at some point. */ static void -XLogRead(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, - Size count) +XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, + Size count) { char *p; XLogRecPtr recptr; @@ -896,6 +896,35 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa } } +/* + * Callback for XLogRead() to open the next segment. + */ +static void +read_local_xlog_page_open_segment(XLogSegNo nextSegNo, TimeLineID *tli, + XLogSegment *seg) +{ + char path[MAXPGPATH]; + + XLogFilePath(path, *tli, nextSegNo, seg->size); + seg->file = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (seg->file < 0) + { + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + path))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } + + seg->tli = *tli; +} + /* * read_page callback for reading local xlog files * @@ -1022,10 +1051,9 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * as 'count', read the whole page anyway. It's guaranteed to be * zero-padded up to the page boundary if it's incomplete. */ - XLogRead(cur_page, state->seg.size, state->seg.tli, targetPagePtr, - XLOG_BLCKSZ); - state->seg.tli = pageTLI; - + if (!XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ, &pageTLI, + &state->seg, read_local_xlog_page_open_segment)) + XLogReadProcessError(&state->seg); /* number of valid bytes in the buffer */ return count; } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index f5630a63cf..0685c320b4 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -247,7 +247,9 @@ static void LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time); static TimeOffset LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now); static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch); -static void XLogRead(char *buf, XLogRecPtr startptr, Size count); +static void WalSndOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli, + XLogSegment *seg); +static void XLogReadOld(char *buf, XLogRecPtr startptr, Size count); /* Initialize walsender process before entering the main command loop */ @@ -782,7 +784,9 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req count = flushptr - targetPagePtr; /* part of the page available */ /* now actually read the data, we know it's there */ - XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ); + if (!XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ, NULL, sendSeg, + WalSndOpenSegment)) + XLogReadProcessError(sendSeg); return count; } @@ -2359,7 +2363,7 @@ WalSndKill(int code, Datum arg) * more than one. */ static void -XLogRead(char *buf, XLogRecPtr startptr, Size count) +XLogReadOld(char *buf, XLogRecPtr startptr, Size count) { char *p; XLogRecPtr recptr; @@ -2532,6 +2536,76 @@ retry: } } +/* + * Callback for XLogRead() to open the next segment. + */ +void +WalSndOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli, XLogSegment *seg) +{ + char path[MAXPGPATH]; + + /* + * The timeline is determined below, caller should not do anything about + * it. + */ + Assert(tli == NULL); + + /*------- + * When reading from a historic timeline, and there is a timeline switch + * within this segment, read from the WAL segment belonging to the new + * timeline. + * + * For example, imagine that this server is currently on timeline 5, and + * we're streaming timeline 4. The switch from timeline 4 to 5 happened at + * 0/13002088. In pg_wal, we have these files: + * + * ... + * 000000040000000000000012 + * 000000040000000000000013 + * 000000050000000000000013 + * 000000050000000000000014 + * ... + * + * In this situation, when requested to send the WAL from segment 0x13, on + * timeline 4, we read the WAL from file 000000050000000000000013. Archive + * recovery prefers files from newer timelines, so if the segment was + * restored from the archive on this server, the file belonging to the old + * timeline, 000000040000000000000013, might not exist. Their contents are + * equal up to the switchpoint, because at a timeline switch, the used + * portion of the old segment is copied to the new file. ------- + */ + seg->tli = sendTimeLine; + if (sendTimeLineIsHistoric) + { + XLogSegNo endSegNo; + + XLByteToSeg(sendTimeLineValidUpto, endSegNo, seg->size); + if (seg->num == endSegNo) + seg->tli = sendTimeLineNextTLI; + } + + XLogFilePath(path, seg->tli, nextSegNo, seg->size); + seg->file = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (seg->file < 0) + { + /* + * If the file is not found, assume it's because the standby asked for + * a too old WAL segment that has already been removed or recycled. + */ + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + XLogFileNameP(seg->tli, seg->num)))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } +} + /* * Send out the WAL in its normal physical/stored form. * @@ -2549,6 +2623,7 @@ XLogSendPhysical(void) XLogRecPtr startptr; XLogRecPtr endptr; Size nbytes; + XLogSegNo segno; /* If requested switch the WAL sender to the stopping state. */ if (got_STOPPING) @@ -2764,7 +2839,49 @@ XLogSendPhysical(void) * calls. */ enlargeStringInfo(&output_message, nbytes); - XLogRead(&output_message.data[output_message.len], startptr, nbytes); + +retry: + if (!XLogRead(&output_message.data[output_message.len], startptr, nbytes, + NULL, /* WalSndOpenSegment will determine TLI */ + sendSeg, + WalSndOpenSegment)) + XLogReadProcessError(sendSeg); + + /* + * After reading into the buffer, check that what we read was valid. We do + * this after reading, because even though the segment was present when we + * opened it, it might get recycled or removed while we read it. The + * read() succeeds in that case, but the data we tried to read might + * already have been overwritten with new WAL records. + */ + XLByteToSeg(startptr, segno, wal_segment_size); + CheckXLogRemoved(segno, ThisTimeLineID); + + /* + * During recovery, the currently-open WAL file might be replaced with the + * file of the same name retrieved from archive. So we always need to + * check what we read was valid after reading into the buffer. If it's + * invalid, we try to open and read the file again. + */ + if (am_cascading_walsender) + { + WalSnd *walsnd = MyWalSnd; + bool reload; + + SpinLockAcquire(&walsnd->mutex); + reload = walsnd->needreload; + walsnd->needreload = false; + SpinLockRelease(&walsnd->mutex); + + if (reload && sendSeg->file >= 0) + { + close(sendSeg->file); + sendSeg->file = -1; + + goto retry; + } + } + output_message.len += nbytes; output_message.data[output_message.len] = '\0'; diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index a16793bb8b..3e09519f88 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -296,6 +296,45 @@ identify_target_directory(XLogDumpPrivate *private, char *directory, fatal_error("could not find any WAL file"); } +static void +XLogDumpOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli, XLogSegment *seg) +{ + char fname[MAXPGPATH]; + int tries; + + XLogFileName(fname, *tli, nextSegNo, seg->size); + + /* + * In follow mode there is a short period of time after the server has + * written the end of the previous file before the new file is available. + * So we loop for 5 seconds looking for the file to appear before giving + * up. + */ + for (tries = 0; tries < 10; tries++) + { + seg->file = open_file_in_directory(seg->dir, fname); + if (seg->file >= 0) + break; + if (errno == ENOENT) + { + int save_errno = errno; + + /* File not there yet, try again */ + pg_usleep(500 * 1000); + + errno = save_errno; + continue; + } + /* Any other error, fall through and fail */ + break; + } + + if (seg->file < 0) + fatal_error("could not find file \"%s\": %s", + fname, strerror(errno)); + seg->tli = *tli; +} + /* * Read count bytes from a segment file in the specified directory, for the * given timeline, containing the specified record pointer; store the data in @@ -441,8 +480,24 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, } } - XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr, - readBuff, count); + if (!XLogRead(readBuff, targetPagePtr, count, &private->timeline, + &state->seg, XLogDumpOpenSegment)) + { + XLogSegment *seg = &state->seg; + int err = errno; + char fname[MAXPGPATH]; + int save_errno = errno; + + XLogFileName(fname, seg->tli, seg->num, seg->size); + errno = save_errno; + + if (errno != 0) + fatal_error("could not read from log file %s, offset %u, length %zu: %s", + fname, seg->off, (Size) seg->last_req, strerror(err)); + else + fatal_error("could not read from log file %s, offset %u: length: %zu", + fname, seg->off, (Size) seg->last_req); + } return count; } diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index c2724fff74..4731023ccc 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -43,6 +43,7 @@ typedef struct XLogSegment char *dir; /* directory (only needed by frontends) */ int size; /* segment size */ + int last_req; /* the amount of data requested last time */ } XLogSegment; typedef struct XLogReaderState XLogReaderState; @@ -225,7 +226,25 @@ extern bool XLogReaderValidatePageHeader(XLogReaderState *state, extern XLogRecPtr XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr); #endif /* FRONTEND */ +/* + * Callback to open the specified XLOG segment nextSegNo in timeline *tli for + * reading, and assign the descriptor to ->file. BasicOpenFile() is the + * preferred way to open the segment file in backend code, whereas open(2) + * should be used in frontend. + * + * If NULL is passed for tli, the callback must determine the timeline + * itself. In any case it's supposed to eventually set ->tli. + */ +typedef void (*XLogOpenSegment) (XLogSegNo nextSegNo, TimeLineID *tli, + XLogSegment *seg); + extern void XLogSegmentInit(XLogSegment *seg, int size); +extern bool XLogRead(char *buf, XLogRecPtr startptr, Size count, + TimeLineID *tli, XLogSegment *seg, + XLogOpenSegment openSegment); +#ifndef FRONTEND +void XLogReadProcessError(XLogSegment *seg); +#endif /* Functions for decoding an XLogRecord */ -- 2.22.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v05-0005-Remove-the-old-implemenations-of-XLogRead.patch ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH 3/4] Use only xlogreader.c:XLogRead() @ 2019-09-09 09:53 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 8+ messages in thread From: Antonin Houska @ 2019-09-09 09:53 UTC (permalink / raw) The implementations in xlogutils.c and walsender.c are just renamed now, to be removed by the following diff. --- src/backend/access/transam/xlogreader.c | 128 ++++++++++++++++++++++++ src/backend/access/transam/xlogutils.c | 40 ++++++-- src/backend/replication/walsender.c | 125 ++++++++++++++++++++++- src/bin/pg_waldump/pg_waldump.c | 59 ++++++++++- src/include/access/xlogreader.h | 19 ++++ 5 files changed, 359 insertions(+), 12 deletions(-) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index 7b4ec81493..2a80bc5823 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -17,6 +17,8 @@ */ #include "postgres.h" +#include <unistd.h> + #include "access/transam.h" #include "access/xlogrecord.h" #include "access/xlog_internal.h" @@ -27,6 +29,7 @@ #ifndef FRONTEND #include "miscadmin.h" +#include "pgstat.h" #include "utils/memutils.h" #endif @@ -1011,7 +1014,132 @@ XLogSegmentInit(XLogSegment *seg, int size) seg->tli = 0; seg->dir = NULL; seg->size = size; + seg->last_req = 0; +} + +/* + * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'. If + * tli is passed, get the data from timeline *tli. 'pos' is the current + * position in the XLOG file and openSegment is a callback that opens the next + * segment for reading. + * + * Returns true if the call succeeded, false if it failed. Caller should check + * errno in the case of failure. seg->last_req might also be useful for error + * messages. + * + * XXX probably this should be improved to suck data directly from the + * WAL buffers when possible. + */ +bool +XLogRead(char *buf, XLogRecPtr startptr, Size count, + TimeLineID *tli, XLogSegment *seg, XLogOpenSegment openSegment) +{ + char *p; + XLogRecPtr recptr; + Size nbytes; + + p = buf; + recptr = startptr; + nbytes = count; + + while (nbytes > 0) + { + int readbytes; + + seg->off = XLogSegmentOffset(recptr, seg->size); + + if (seg->file < 0 || + !XLByteInSeg(recptr, seg->num, seg->size) || + (tli != NULL && *tli != seg->tli)) + { + XLogSegNo nextSegNo; + + /* Switch to another logfile segment */ + if (seg->file >= 0) + close(seg->file); + + XLByteToSeg(recptr, nextSegNo, seg->size); + + /* Open the next segment in the caller's way. */ + openSegment(nextSegNo, tli, seg); + + /* + * If the function is called by the XLOG reader, the reader will + * eventually set both "num" and "off". However we need to care + * about them too because the function can also be used directly, + * see walsender.c. + */ + seg->num = nextSegNo; + seg->off = 0; + } + + /* How many bytes are within this segment? */ + if (nbytes > (seg->size - seg->off)) + seg->last_req = seg->size - seg->off; + else + seg->last_req = nbytes; + +#ifndef FRONTEND + pgstat_report_wait_start(WAIT_EVENT_WAL_READ); +#endif + + /* + * Failure to read the data does not necessarily imply non-zero errno. + * Set it to zero so that caller can distinguish the failure that does + * not affect errno. + */ + errno = 0; + + readbytes = pg_pread(seg->file, p, seg->last_req, seg->off); + +#ifndef FRONTEND + pgstat_report_wait_end(); +#endif + + if (readbytes <= 0) + return false; + + /* Update state for read */ + recptr += readbytes; + nbytes -= readbytes; + p += readbytes; + + /* + * If the function is called by the XLOG reader, the reader will + * eventually set this field. However we need to care about it too + * because the function can also be used directly (see walsender.c). + */ + seg->off += readbytes; + } + + return true; +} + +#ifndef FRONTEND +/* + * Backend-specific code to handle errors encountered by XLogRead(). + */ +void +XLogReadProcessError(XLogSegment *seg) +{ + if (errno != 0) + { + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from log segment %s, offset %u, length %zu: %m", + XLogFileNameP(seg->tli, seg->num), seg->off, + (Size) seg->last_req))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not read from log segment %s, offset %u: length %zu", + XLogFileNameP(seg->tli, seg->num), seg->off, + (Size) seg->last_req))); + } } +#endif /* ---------------------------------------- * Functions for decoding the data and block references in a record. diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 424bb06919..83b014e04c 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -653,8 +653,8 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum, * frontend). Probably these should be merged at some point. */ static void -XLogRead(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, - Size count) +XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, + Size count) { char *p; XLogRecPtr recptr; @@ -896,6 +896,35 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa } } +/* + * Callback for XLogRead() to open the next segment. + */ +static void +read_local_xlog_page_open_segment(XLogSegNo nextSegNo, TimeLineID *tli, + XLogSegment *seg) +{ + char path[MAXPGPATH]; + + XLogFilePath(path, *tli, nextSegNo, seg->size); + seg->file = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (seg->file < 0) + { + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + path))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } + + seg->tli = *tli; +} + /* * read_page callback for reading local xlog files * @@ -1022,10 +1051,9 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * as 'count', read the whole page anyway. It's guaranteed to be * zero-padded up to the page boundary if it's incomplete. */ - XLogRead(cur_page, state->seg.size, state->seg.tli, targetPagePtr, - XLOG_BLCKSZ); - state->seg.tli = pageTLI; - + if (!XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ, &pageTLI, + &state->seg, read_local_xlog_page_open_segment)) + XLogReadProcessError(&state->seg); /* number of valid bytes in the buffer */ return count; } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index f5630a63cf..0685c320b4 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -247,7 +247,9 @@ static void LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time); static TimeOffset LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now); static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch); -static void XLogRead(char *buf, XLogRecPtr startptr, Size count); +static void WalSndOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli, + XLogSegment *seg); +static void XLogReadOld(char *buf, XLogRecPtr startptr, Size count); /* Initialize walsender process before entering the main command loop */ @@ -782,7 +784,9 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req count = flushptr - targetPagePtr; /* part of the page available */ /* now actually read the data, we know it's there */ - XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ); + if (!XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ, NULL, sendSeg, + WalSndOpenSegment)) + XLogReadProcessError(sendSeg); return count; } @@ -2359,7 +2363,7 @@ WalSndKill(int code, Datum arg) * more than one. */ static void -XLogRead(char *buf, XLogRecPtr startptr, Size count) +XLogReadOld(char *buf, XLogRecPtr startptr, Size count) { char *p; XLogRecPtr recptr; @@ -2532,6 +2536,76 @@ retry: } } +/* + * Callback for XLogRead() to open the next segment. + */ +void +WalSndOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli, XLogSegment *seg) +{ + char path[MAXPGPATH]; + + /* + * The timeline is determined below, caller should not do anything about + * it. + */ + Assert(tli == NULL); + + /*------- + * When reading from a historic timeline, and there is a timeline switch + * within this segment, read from the WAL segment belonging to the new + * timeline. + * + * For example, imagine that this server is currently on timeline 5, and + * we're streaming timeline 4. The switch from timeline 4 to 5 happened at + * 0/13002088. In pg_wal, we have these files: + * + * ... + * 000000040000000000000012 + * 000000040000000000000013 + * 000000050000000000000013 + * 000000050000000000000014 + * ... + * + * In this situation, when requested to send the WAL from segment 0x13, on + * timeline 4, we read the WAL from file 000000050000000000000013. Archive + * recovery prefers files from newer timelines, so if the segment was + * restored from the archive on this server, the file belonging to the old + * timeline, 000000040000000000000013, might not exist. Their contents are + * equal up to the switchpoint, because at a timeline switch, the used + * portion of the old segment is copied to the new file. ------- + */ + seg->tli = sendTimeLine; + if (sendTimeLineIsHistoric) + { + XLogSegNo endSegNo; + + XLByteToSeg(sendTimeLineValidUpto, endSegNo, seg->size); + if (seg->num == endSegNo) + seg->tli = sendTimeLineNextTLI; + } + + XLogFilePath(path, seg->tli, nextSegNo, seg->size); + seg->file = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (seg->file < 0) + { + /* + * If the file is not found, assume it's because the standby asked for + * a too old WAL segment that has already been removed or recycled. + */ + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + XLogFileNameP(seg->tli, seg->num)))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } +} + /* * Send out the WAL in its normal physical/stored form. * @@ -2549,6 +2623,7 @@ XLogSendPhysical(void) XLogRecPtr startptr; XLogRecPtr endptr; Size nbytes; + XLogSegNo segno; /* If requested switch the WAL sender to the stopping state. */ if (got_STOPPING) @@ -2764,7 +2839,49 @@ XLogSendPhysical(void) * calls. */ enlargeStringInfo(&output_message, nbytes); - XLogRead(&output_message.data[output_message.len], startptr, nbytes); + +retry: + if (!XLogRead(&output_message.data[output_message.len], startptr, nbytes, + NULL, /* WalSndOpenSegment will determine TLI */ + sendSeg, + WalSndOpenSegment)) + XLogReadProcessError(sendSeg); + + /* + * After reading into the buffer, check that what we read was valid. We do + * this after reading, because even though the segment was present when we + * opened it, it might get recycled or removed while we read it. The + * read() succeeds in that case, but the data we tried to read might + * already have been overwritten with new WAL records. + */ + XLByteToSeg(startptr, segno, wal_segment_size); + CheckXLogRemoved(segno, ThisTimeLineID); + + /* + * During recovery, the currently-open WAL file might be replaced with the + * file of the same name retrieved from archive. So we always need to + * check what we read was valid after reading into the buffer. If it's + * invalid, we try to open and read the file again. + */ + if (am_cascading_walsender) + { + WalSnd *walsnd = MyWalSnd; + bool reload; + + SpinLockAcquire(&walsnd->mutex); + reload = walsnd->needreload; + walsnd->needreload = false; + SpinLockRelease(&walsnd->mutex); + + if (reload && sendSeg->file >= 0) + { + close(sendSeg->file); + sendSeg->file = -1; + + goto retry; + } + } + output_message.len += nbytes; output_message.data[output_message.len] = '\0'; diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index a16793bb8b..3e09519f88 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -296,6 +296,45 @@ identify_target_directory(XLogDumpPrivate *private, char *directory, fatal_error("could not find any WAL file"); } +static void +XLogDumpOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli, XLogSegment *seg) +{ + char fname[MAXPGPATH]; + int tries; + + XLogFileName(fname, *tli, nextSegNo, seg->size); + + /* + * In follow mode there is a short period of time after the server has + * written the end of the previous file before the new file is available. + * So we loop for 5 seconds looking for the file to appear before giving + * up. + */ + for (tries = 0; tries < 10; tries++) + { + seg->file = open_file_in_directory(seg->dir, fname); + if (seg->file >= 0) + break; + if (errno == ENOENT) + { + int save_errno = errno; + + /* File not there yet, try again */ + pg_usleep(500 * 1000); + + errno = save_errno; + continue; + } + /* Any other error, fall through and fail */ + break; + } + + if (seg->file < 0) + fatal_error("could not find file \"%s\": %s", + fname, strerror(errno)); + seg->tli = *tli; +} + /* * Read count bytes from a segment file in the specified directory, for the * given timeline, containing the specified record pointer; store the data in @@ -441,8 +480,24 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, } } - XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr, - readBuff, count); + if (!XLogRead(readBuff, targetPagePtr, count, &private->timeline, + &state->seg, XLogDumpOpenSegment)) + { + XLogSegment *seg = &state->seg; + int err = errno; + char fname[MAXPGPATH]; + int save_errno = errno; + + XLogFileName(fname, seg->tli, seg->num, seg->size); + errno = save_errno; + + if (errno != 0) + fatal_error("could not read from log file %s, offset %u, length %zu: %s", + fname, seg->off, (Size) seg->last_req, strerror(err)); + else + fatal_error("could not read from log file %s, offset %u: length: %zu", + fname, seg->off, (Size) seg->last_req); + } return count; } diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index c2724fff74..4731023ccc 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -43,6 +43,7 @@ typedef struct XLogSegment char *dir; /* directory (only needed by frontends) */ int size; /* segment size */ + int last_req; /* the amount of data requested last time */ } XLogSegment; typedef struct XLogReaderState XLogReaderState; @@ -225,7 +226,25 @@ extern bool XLogReaderValidatePageHeader(XLogReaderState *state, extern XLogRecPtr XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr); #endif /* FRONTEND */ +/* + * Callback to open the specified XLOG segment nextSegNo in timeline *tli for + * reading, and assign the descriptor to ->file. BasicOpenFile() is the + * preferred way to open the segment file in backend code, whereas open(2) + * should be used in frontend. + * + * If NULL is passed for tli, the callback must determine the timeline + * itself. In any case it's supposed to eventually set ->tli. + */ +typedef void (*XLogOpenSegment) (XLogSegNo nextSegNo, TimeLineID *tli, + XLogSegment *seg); + extern void XLogSegmentInit(XLogSegment *seg, int size); +extern bool XLogRead(char *buf, XLogRecPtr startptr, Size count, + TimeLineID *tli, XLogSegment *seg, + XLogOpenSegment openSegment); +#ifndef FRONTEND +void XLogReadProcessError(XLogSegment *seg); +#endif /* Functions for decoding an XLogRecord */ -- 2.22.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v05-0005-Remove-the-old-implemenations-of-XLogRead.patch ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH 5/6] Use only xlogreader.c:XLogRead() @ 2019-09-23 05:40 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 8+ messages in thread From: Antonin Houska @ 2019-09-23 05:40 UTC (permalink / raw) The implementations in xlogutils.c and walsender.c are just renamed now, to be removed by the following diff. --- src/backend/access/transam/xlogreader.c | 157 ++++++++++++++++++++++++ src/backend/access/transam/xlogutils.c | 46 ++++++- src/backend/replication/walsender.c | 139 ++++++++++++++++++++- src/bin/pg_waldump/pg_waldump.c | 64 +++++++++- src/include/access/xlogreader.h | 42 +++++++ 5 files changed, 436 insertions(+), 12 deletions(-) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index 4de5530b3e..7f77fa95cb 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -17,6 +17,8 @@ */ #include "postgres.h" +#include <unistd.h> + #include "access/transam.h" #include "access/xlogrecord.h" #include "access/xlog_internal.h" @@ -27,6 +29,7 @@ #ifndef FRONTEND #include "miscadmin.h" +#include "pgstat.h" #include "utils/memutils.h" #endif @@ -1015,6 +1018,160 @@ WALOpenSegmentInit(WALOpenSegment *seg, int size) #endif } +/* + * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'. If + * tli_p is passed, get the data from timeline *tli_p. 'pos' is the current + * position in the XLOG file and openSegment is a callback that opens the next + * segment for reading. + * + * Returns error information if the data could not be read or NULL if + * succeeded. + * + * XXX probably this should be improved to suck data directly from the + * WAL buffers when possible. + */ +XLogReadError * +XLogRead(char *buf, XLogRecPtr startptr, Size count, + TimeLineID *tli_p, WALOpenSegment *seg, WALSegmentOpen openSegment) +{ + char *p; + XLogRecPtr recptr; + Size nbytes; + + p = buf; + recptr = startptr; + nbytes = count; + + while (nbytes > 0) + { + int segbytes; + int readbytes; + + seg->off = XLogSegmentOffset(recptr, seg->size); + + if (seg->file < 0 || + !XLByteInSeg(recptr, seg->num, seg->size) || + (tli_p != NULL && *tli_p != seg->tli)) + { + XLogSegNo nextSegNo; + TimeLineID tli = InvalidTimeLineID; + int file; + + /* Switch to another logfile segment */ + if (seg->file >= 0) + close(seg->file); + + XLByteToSeg(recptr, nextSegNo, seg->size); + + /* If we have the TLI, let's pass it to the callback. */ + if (tli_p != NULL) + tli = *tli_p; + + /* Open the next segment in the caller's way. */ + openSegment(nextSegNo, &tli, &file, seg); + + /* + * If we passed InvalidTimeLineID, the callback should have + * determined the correct TLI and returned it. + */ + Assert(tli != InvalidTimeLineID); + + /* Update the open segment info. */ + seg->tli = tli; + seg->file = file; + + /* + * If the function is called by the XLOG reader, the reader will + * eventually set both "num" and "off". However we need to care + * about them too because the function can also be used directly, + * see walsender.c. + */ + seg->num = nextSegNo; + seg->off = 0; + } + + /* How many bytes are within this segment? */ + if (nbytes > (seg->size - seg->off)) + segbytes = seg->size - seg->off; + else + segbytes = nbytes; + +#ifndef FRONTEND + pgstat_report_wait_start(WAIT_EVENT_WAL_READ); +#endif + + /* + * Failure to read the data does not necessarily imply non-zero errno. + * Set it to zero so that caller can distinguish the failure that does + * not affect errno. + */ + errno = 0; + + readbytes = pg_pread(seg->file, p, segbytes, seg->off); + +#ifndef FRONTEND + pgstat_report_wait_end(); +#endif + + if (readbytes <= 0) + { + XLogReadError *errinfo; + + errinfo = (XLogReadError *) palloc(sizeof(XLogReadError)); + errinfo->read_errno = errno; + errinfo->readbytes = readbytes; + errinfo->reqbytes = segbytes; + errinfo->seg = seg; + + return errinfo; + } + + /* Update state for read */ + recptr += readbytes; + nbytes -= readbytes; + p += readbytes; + + /* + * If the function is called by the XLOG reader, the reader will + * eventually set this field. However we need to care about it too + * because the function can also be used directly (see walsender.c). + */ + seg->off += readbytes; + } + + return NULL; +} + +#ifndef FRONTEND +/* + * Backend-specific convenience code to handle read errors encountered by + * XLogRead(). + */ +void +XLogReadProcessError(XLogReadError *errinfo) +{ + WALOpenSegment *seg = errinfo->seg; + + if (errinfo->readbytes < 0) + { + errno = errinfo->read_errno; + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from log segment %s, offset %u, length %zu: %m", + XLogFileNameP(seg->tli, seg->num), seg->off, + (Size) errinfo->reqbytes))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not read from log segment %s, offset %u: length %zu", + XLogFileNameP(seg->tli, seg->num), seg->off, + (Size) errinfo->reqbytes))); + } +} +#endif + /* ---------------------------------------- * Functions for decoding the data and block references in a record. * ---------------------------------------- diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 424bb06919..38c3196168 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -653,8 +653,8 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum, * frontend). Probably these should be merged at some point. */ static void -XLogRead(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, - Size count) +XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, + Size count) { char *p; XLogRecPtr recptr; @@ -896,6 +896,39 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa } } +/* + * Callback for XLogRead() to open the next segment. + */ +static void +read_local_xlog_page_segment_open(XLogSegNo nextSegNo, TimeLineID *tli_p, + int *file_p, WALOpenSegment *seg) +{ + TimeLineID tli = *tli_p; + char path[MAXPGPATH]; + int file; + + Assert(tli != InvalidTimeLineID); + + XLogFilePath(path, tli, nextSegNo, seg->size); + file = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (file < 0) + { + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + path))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } + + *file_p = file; +} + /* * read_page callback for reading local xlog files * @@ -915,6 +948,7 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, loc; int count; TimeLineID pageTLI; + XLogReadError *errinfo; loc = targetPagePtr + reqLen; @@ -1022,10 +1056,10 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * as 'count', read the whole page anyway. It's guaranteed to be * zero-padded up to the page boundary if it's incomplete. */ - XLogRead(cur_page, state->seg.size, state->seg.tli, targetPagePtr, - XLOG_BLCKSZ); - state->seg.tli = pageTLI; - + if ((errinfo = XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ, &pageTLI, + &state->seg, + read_local_xlog_page_segment_open)) != NULL) + XLogReadProcessError(errinfo); /* number of valid bytes in the buffer */ return count; } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index a617e20ab6..a7b3e0ecbe 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -247,9 +247,12 @@ static void LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time); static TimeOffset LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now); static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch); -static void XLogRead(char *buf, XLogRecPtr startptr, Size count); +static void WalSndSegmentOpen(XLogSegNo nextSegNo, TimeLineID *tli_p, + int *file_p, WALOpenSegment *seg); +static void XLogReadOld(char *buf, XLogRecPtr startptr, Size count); + /* Initialize walsender process before entering the main command loop */ void InitWalSender(void) @@ -763,6 +766,7 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req { XLogRecPtr flushptr; int count; + XLogReadError *errinfo; XLogReadDetermineTimeline(state, targetPagePtr, reqLen); sendTimeLineIsHistoric = (state->currTLI != ThisTimeLineID); @@ -783,7 +787,13 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req count = flushptr - targetPagePtr; /* part of the page available */ /* now actually read the data, we know it's there */ - XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ); + if ((errinfo = XLogRead(cur_page, + targetPagePtr, + XLOG_BLCKSZ, + NULL, /* WalSndSegmentOpen will determine TLI */ + sendSeg, + WalSndSegmentOpen)) != NULL) + XLogReadProcessError(errinfo); return count; } @@ -2360,7 +2370,7 @@ WalSndKill(int code, Datum arg) * more than one. */ static void -XLogRead(char *buf, XLogRecPtr startptr, Size count) +XLogReadOld(char *buf, XLogRecPtr startptr, Size count) { char *p; XLogRecPtr recptr; @@ -2533,6 +2543,81 @@ retry: } } +/* + * Callback for XLogRead() to open the next segment. + */ +void +WalSndSegmentOpen(XLogSegNo nextSegNo, TimeLineID *tli_p, int *file_p, + WALOpenSegment *seg) +{ + TimeLineID tli = *tli_p; + char path[MAXPGPATH]; + int file; + + /* + * The timeline is determined below, caller should not pass it. + */ + Assert(tli == InvalidTimeLineID); + + /*------- + * When reading from a historic timeline, and there is a timeline switch + * within this segment, read from the WAL segment belonging to the new + * timeline. + * + * For example, imagine that this server is currently on timeline 5, and + * we're streaming timeline 4. The switch from timeline 4 to 5 happened at + * 0/13002088. In pg_wal, we have these files: + * + * ... + * 000000040000000000000012 + * 000000040000000000000013 + * 000000050000000000000013 + * 000000050000000000000014 + * ... + * + * In this situation, when requested to send the WAL from segment 0x13, on + * timeline 4, we read the WAL from file 000000050000000000000013. Archive + * recovery prefers files from newer timelines, so if the segment was + * restored from the archive on this server, the file belonging to the old + * timeline, 000000040000000000000013, might not exist. Their contents are + * equal up to the switchpoint, because at a timeline switch, the used + * portion of the old segment is copied to the new file. ------- + */ + tli = sendTimeLine; + if (sendTimeLineIsHistoric) + { + XLogSegNo endSegNo; + + XLByteToSeg(sendTimeLineValidUpto, endSegNo, seg->size); + if (seg->num == endSegNo) + tli = sendTimeLineNextTLI; + } + + XLogFilePath(path, tli, nextSegNo, seg->size); + file = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (file < 0) + { + /* + * If the file is not found, assume it's because the standby asked for + * a too old WAL segment that has already been removed or recycled. + */ + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + XLogFileNameP(tli, nextSegNo)))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } + + *file_p = file; + *tli_p = tli; +} + /* * Send out the WAL in its normal physical/stored form. * @@ -2550,6 +2635,8 @@ XLogSendPhysical(void) XLogRecPtr startptr; XLogRecPtr endptr; Size nbytes; + XLogSegNo segno; + XLogReadError *errinfo; /* If requested switch the WAL sender to the stopping state. */ if (got_STOPPING) @@ -2765,7 +2852,51 @@ XLogSendPhysical(void) * calls. */ enlargeStringInfo(&output_message, nbytes); - XLogRead(&output_message.data[output_message.len], startptr, nbytes); + +retry: + if ((errinfo = XLogRead(&output_message.data[output_message.len], + startptr, + nbytes, + NULL, /* WalSndSegmentOpen will determine TLI */ + sendSeg, + WalSndSegmentOpen)) != NULL) + XLogReadProcessError(errinfo); + + /* + * After reading into the buffer, check that what we read was valid. We do + * this after reading, because even though the segment was present when we + * opened it, it might get recycled or removed while we read it. The + * read() succeeds in that case, but the data we tried to read might + * already have been overwritten with new WAL records. + */ + XLByteToSeg(startptr, segno, wal_segment_size); + CheckXLogRemoved(segno, ThisTimeLineID); + + /* + * During recovery, the currently-open WAL file might be replaced with the + * file of the same name retrieved from archive. So we always need to + * check what we read was valid after reading into the buffer. If it's + * invalid, we try to open and read the file again. + */ + if (am_cascading_walsender) + { + WalSnd *walsnd = MyWalSnd; + bool reload; + + SpinLockAcquire(&walsnd->mutex); + reload = walsnd->needreload; + walsnd->needreload = false; + SpinLockRelease(&walsnd->mutex); + + if (reload && sendSeg->file >= 0) + { + close(sendSeg->file); + sendSeg->file = -1; + + goto retry; + } + } + output_message.len += nbytes; output_message.data[output_message.len] = '\0'; diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index a16793bb8b..cd5f589f03 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -296,6 +296,51 @@ identify_target_directory(XLogDumpPrivate *private, char *directory, fatal_error("could not find any WAL file"); } +static void +WALDumpOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli_p, int *file_p, + WALOpenSegment *seg) +{ + TimeLineID tli = *tli_p; + char fname[MAXPGPATH]; + int file; + int tries; + + Assert(tli != InvalidTimeLineID); + + XLogFileName(fname, tli, nextSegNo, seg->size); + + /* + * In follow mode there is a short period of time after the server has + * written the end of the previous file before the new file is available. + * So we loop for 5 seconds looking for the file to appear before giving + * up. + */ + for (tries = 0; tries < 10; tries++) + { + file = open_file_in_directory(seg->dir, fname); + if (file >= 0) + break; + if (errno == ENOENT) + { + int save_errno = errno; + + /* File not there yet, try again */ + pg_usleep(500 * 1000); + + errno = save_errno; + continue; + } + /* Any other error, fall through and fail */ + break; + } + + if (file < 0) + fatal_error("could not find file \"%s\": %s", + fname, strerror(errno)); + + *file_p = file; +} + /* * Read count bytes from a segment file in the specified directory, for the * given timeline, containing the specified record pointer; store the data in @@ -427,6 +472,7 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, { XLogDumpPrivate *private = state->private_data; int count = XLOG_BLCKSZ; + XLogReadError *errinfo; if (private->endptr != InvalidXLogRecPtr) { @@ -441,8 +487,22 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, } } - XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr, - readBuff, count); + if ((errinfo = XLogRead(readBuff, targetPagePtr, count, &private->timeline, + &state->seg, WALDumpOpenSegment)) != NULL) + { + WALOpenSegment *seg = errinfo->seg; + char fname[MAXPGPATH]; + + XLogFileName(fname, seg->tli, seg->num, seg->size); + + if (errno != 0) + fatal_error("could not read from log file %s, offset %u, length %zu: %s", + fname, seg->off, (Size) errinfo->reqbytes, + strerror(errinfo->read_errno)); + else + fatal_error("could not read from log file %s, offset %u: length: %zu", + fname, seg->off, (Size) errinfo->reqbytes); + } return count; } diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index b9d99d524e..3d9742b81b 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -227,8 +227,50 @@ extern bool XLogReaderValidatePageHeader(XLogReaderState *state, extern XLogRecPtr XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr); #endif /* FRONTEND */ +/* + * Callback to open the specified WAL segment for reading. + * + * "nextSegNo" is the number of the segment to be opened. + * + * "tli_p" is an input/output argument. If *tli_p is valid, it's the timeline + * the new segment should be in. If *tli_p==InvalidTimeLineID, the callback + * needs to determine the timeline itself and put the result into *tli_p. + * + * "file_p" points to an address the segment file descriptor should be stored + * at. + * + * "seg" provides information on the currently open segment. The callback is + * not supposed to change this info. + * + * BasicOpenFile() is the preferred way to open the segment file in backend + * code, whereas open(2) should be used in frontend. + */ +typedef void (*WALSegmentOpen) (XLogSegNo nextSegNo, TimeLineID *tli_p, + int *file_p, WALOpenSegment *seg); + extern void WALOpenSegmentInit(WALOpenSegment *seg, int size); +/* + * Error information that both backend and frontend caller can process. + * + * XXX Should the name be WALReadError? If so, we probably need to rename + * XLogRead() and XLogReadProcessError() too. + */ +typedef struct XLogReadError +{ + int read_errno; /* errno set by the last read(). */ + int readbytes; /* Bytes read by the last read(). */ + int reqbytes; /* Bytes requested to be read. */ + WALOpenSegment *seg; /* Segment we tried to read from. */ +} XLogReadError; + +extern XLogReadError *XLogRead(char *buf, XLogRecPtr startptr, Size count, + TimeLineID *tli_p, WALOpenSegment *seg, + WALSegmentOpen openSegment); +#ifndef FRONTEND +void XLogReadProcessError(XLogReadError *errinfo); +#endif + /* Functions for decoding an XLogRecord */ extern bool DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, -- 2.20.1 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v06-0006-Remove-the-old-implemenations-of-XLogRead.patch ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH 5/6] Use only xlogreader.c:XLogRead() @ 2019-09-23 05:40 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 8+ messages in thread From: Antonin Houska @ 2019-09-23 05:40 UTC (permalink / raw) The implementations in xlogutils.c and walsender.c are just renamed now, to be removed by the following diff. --- src/backend/access/transam/xlogreader.c | 157 ++++++++++++++++++++++++ src/backend/access/transam/xlogutils.c | 46 ++++++- src/backend/replication/walsender.c | 139 ++++++++++++++++++++- src/bin/pg_waldump/pg_waldump.c | 64 +++++++++- src/include/access/xlogreader.h | 42 +++++++ 5 files changed, 436 insertions(+), 12 deletions(-) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index 4de5530b3e..7f77fa95cb 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -17,6 +17,8 @@ */ #include "postgres.h" +#include <unistd.h> + #include "access/transam.h" #include "access/xlogrecord.h" #include "access/xlog_internal.h" @@ -27,6 +29,7 @@ #ifndef FRONTEND #include "miscadmin.h" +#include "pgstat.h" #include "utils/memutils.h" #endif @@ -1015,6 +1018,160 @@ WALOpenSegmentInit(WALOpenSegment *seg, int size) #endif } +/* + * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'. If + * tli_p is passed, get the data from timeline *tli_p. 'pos' is the current + * position in the XLOG file and openSegment is a callback that opens the next + * segment for reading. + * + * Returns error information if the data could not be read or NULL if + * succeeded. + * + * XXX probably this should be improved to suck data directly from the + * WAL buffers when possible. + */ +XLogReadError * +XLogRead(char *buf, XLogRecPtr startptr, Size count, + TimeLineID *tli_p, WALOpenSegment *seg, WALSegmentOpen openSegment) +{ + char *p; + XLogRecPtr recptr; + Size nbytes; + + p = buf; + recptr = startptr; + nbytes = count; + + while (nbytes > 0) + { + int segbytes; + int readbytes; + + seg->off = XLogSegmentOffset(recptr, seg->size); + + if (seg->file < 0 || + !XLByteInSeg(recptr, seg->num, seg->size) || + (tli_p != NULL && *tli_p != seg->tli)) + { + XLogSegNo nextSegNo; + TimeLineID tli = InvalidTimeLineID; + int file; + + /* Switch to another logfile segment */ + if (seg->file >= 0) + close(seg->file); + + XLByteToSeg(recptr, nextSegNo, seg->size); + + /* If we have the TLI, let's pass it to the callback. */ + if (tli_p != NULL) + tli = *tli_p; + + /* Open the next segment in the caller's way. */ + openSegment(nextSegNo, &tli, &file, seg); + + /* + * If we passed InvalidTimeLineID, the callback should have + * determined the correct TLI and returned it. + */ + Assert(tli != InvalidTimeLineID); + + /* Update the open segment info. */ + seg->tli = tli; + seg->file = file; + + /* + * If the function is called by the XLOG reader, the reader will + * eventually set both "num" and "off". However we need to care + * about them too because the function can also be used directly, + * see walsender.c. + */ + seg->num = nextSegNo; + seg->off = 0; + } + + /* How many bytes are within this segment? */ + if (nbytes > (seg->size - seg->off)) + segbytes = seg->size - seg->off; + else + segbytes = nbytes; + +#ifndef FRONTEND + pgstat_report_wait_start(WAIT_EVENT_WAL_READ); +#endif + + /* + * Failure to read the data does not necessarily imply non-zero errno. + * Set it to zero so that caller can distinguish the failure that does + * not affect errno. + */ + errno = 0; + + readbytes = pg_pread(seg->file, p, segbytes, seg->off); + +#ifndef FRONTEND + pgstat_report_wait_end(); +#endif + + if (readbytes <= 0) + { + XLogReadError *errinfo; + + errinfo = (XLogReadError *) palloc(sizeof(XLogReadError)); + errinfo->read_errno = errno; + errinfo->readbytes = readbytes; + errinfo->reqbytes = segbytes; + errinfo->seg = seg; + + return errinfo; + } + + /* Update state for read */ + recptr += readbytes; + nbytes -= readbytes; + p += readbytes; + + /* + * If the function is called by the XLOG reader, the reader will + * eventually set this field. However we need to care about it too + * because the function can also be used directly (see walsender.c). + */ + seg->off += readbytes; + } + + return NULL; +} + +#ifndef FRONTEND +/* + * Backend-specific convenience code to handle read errors encountered by + * XLogRead(). + */ +void +XLogReadProcessError(XLogReadError *errinfo) +{ + WALOpenSegment *seg = errinfo->seg; + + if (errinfo->readbytes < 0) + { + errno = errinfo->read_errno; + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from log segment %s, offset %u, length %zu: %m", + XLogFileNameP(seg->tli, seg->num), seg->off, + (Size) errinfo->reqbytes))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not read from log segment %s, offset %u: length %zu", + XLogFileNameP(seg->tli, seg->num), seg->off, + (Size) errinfo->reqbytes))); + } +} +#endif + /* ---------------------------------------- * Functions for decoding the data and block references in a record. * ---------------------------------------- diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 424bb06919..38c3196168 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -653,8 +653,8 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum, * frontend). Probably these should be merged at some point. */ static void -XLogRead(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, - Size count) +XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, + Size count) { char *p; XLogRecPtr recptr; @@ -896,6 +896,39 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa } } +/* + * Callback for XLogRead() to open the next segment. + */ +static void +read_local_xlog_page_segment_open(XLogSegNo nextSegNo, TimeLineID *tli_p, + int *file_p, WALOpenSegment *seg) +{ + TimeLineID tli = *tli_p; + char path[MAXPGPATH]; + int file; + + Assert(tli != InvalidTimeLineID); + + XLogFilePath(path, tli, nextSegNo, seg->size); + file = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (file < 0) + { + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + path))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } + + *file_p = file; +} + /* * read_page callback for reading local xlog files * @@ -915,6 +948,7 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, loc; int count; TimeLineID pageTLI; + XLogReadError *errinfo; loc = targetPagePtr + reqLen; @@ -1022,10 +1056,10 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * as 'count', read the whole page anyway. It's guaranteed to be * zero-padded up to the page boundary if it's incomplete. */ - XLogRead(cur_page, state->seg.size, state->seg.tli, targetPagePtr, - XLOG_BLCKSZ); - state->seg.tli = pageTLI; - + if ((errinfo = XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ, &pageTLI, + &state->seg, + read_local_xlog_page_segment_open)) != NULL) + XLogReadProcessError(errinfo); /* number of valid bytes in the buffer */ return count; } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index a617e20ab6..a7b3e0ecbe 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -247,9 +247,12 @@ static void LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time); static TimeOffset LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now); static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch); -static void XLogRead(char *buf, XLogRecPtr startptr, Size count); +static void WalSndSegmentOpen(XLogSegNo nextSegNo, TimeLineID *tli_p, + int *file_p, WALOpenSegment *seg); +static void XLogReadOld(char *buf, XLogRecPtr startptr, Size count); + /* Initialize walsender process before entering the main command loop */ void InitWalSender(void) @@ -763,6 +766,7 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req { XLogRecPtr flushptr; int count; + XLogReadError *errinfo; XLogReadDetermineTimeline(state, targetPagePtr, reqLen); sendTimeLineIsHistoric = (state->currTLI != ThisTimeLineID); @@ -783,7 +787,13 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req count = flushptr - targetPagePtr; /* part of the page available */ /* now actually read the data, we know it's there */ - XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ); + if ((errinfo = XLogRead(cur_page, + targetPagePtr, + XLOG_BLCKSZ, + NULL, /* WalSndSegmentOpen will determine TLI */ + sendSeg, + WalSndSegmentOpen)) != NULL) + XLogReadProcessError(errinfo); return count; } @@ -2360,7 +2370,7 @@ WalSndKill(int code, Datum arg) * more than one. */ static void -XLogRead(char *buf, XLogRecPtr startptr, Size count) +XLogReadOld(char *buf, XLogRecPtr startptr, Size count) { char *p; XLogRecPtr recptr; @@ -2533,6 +2543,81 @@ retry: } } +/* + * Callback for XLogRead() to open the next segment. + */ +void +WalSndSegmentOpen(XLogSegNo nextSegNo, TimeLineID *tli_p, int *file_p, + WALOpenSegment *seg) +{ + TimeLineID tli = *tli_p; + char path[MAXPGPATH]; + int file; + + /* + * The timeline is determined below, caller should not pass it. + */ + Assert(tli == InvalidTimeLineID); + + /*------- + * When reading from a historic timeline, and there is a timeline switch + * within this segment, read from the WAL segment belonging to the new + * timeline. + * + * For example, imagine that this server is currently on timeline 5, and + * we're streaming timeline 4. The switch from timeline 4 to 5 happened at + * 0/13002088. In pg_wal, we have these files: + * + * ... + * 000000040000000000000012 + * 000000040000000000000013 + * 000000050000000000000013 + * 000000050000000000000014 + * ... + * + * In this situation, when requested to send the WAL from segment 0x13, on + * timeline 4, we read the WAL from file 000000050000000000000013. Archive + * recovery prefers files from newer timelines, so if the segment was + * restored from the archive on this server, the file belonging to the old + * timeline, 000000040000000000000013, might not exist. Their contents are + * equal up to the switchpoint, because at a timeline switch, the used + * portion of the old segment is copied to the new file. ------- + */ + tli = sendTimeLine; + if (sendTimeLineIsHistoric) + { + XLogSegNo endSegNo; + + XLByteToSeg(sendTimeLineValidUpto, endSegNo, seg->size); + if (seg->num == endSegNo) + tli = sendTimeLineNextTLI; + } + + XLogFilePath(path, tli, nextSegNo, seg->size); + file = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (file < 0) + { + /* + * If the file is not found, assume it's because the standby asked for + * a too old WAL segment that has already been removed or recycled. + */ + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + XLogFileNameP(tli, nextSegNo)))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } + + *file_p = file; + *tli_p = tli; +} + /* * Send out the WAL in its normal physical/stored form. * @@ -2550,6 +2635,8 @@ XLogSendPhysical(void) XLogRecPtr startptr; XLogRecPtr endptr; Size nbytes; + XLogSegNo segno; + XLogReadError *errinfo; /* If requested switch the WAL sender to the stopping state. */ if (got_STOPPING) @@ -2765,7 +2852,51 @@ XLogSendPhysical(void) * calls. */ enlargeStringInfo(&output_message, nbytes); - XLogRead(&output_message.data[output_message.len], startptr, nbytes); + +retry: + if ((errinfo = XLogRead(&output_message.data[output_message.len], + startptr, + nbytes, + NULL, /* WalSndSegmentOpen will determine TLI */ + sendSeg, + WalSndSegmentOpen)) != NULL) + XLogReadProcessError(errinfo); + + /* + * After reading into the buffer, check that what we read was valid. We do + * this after reading, because even though the segment was present when we + * opened it, it might get recycled or removed while we read it. The + * read() succeeds in that case, but the data we tried to read might + * already have been overwritten with new WAL records. + */ + XLByteToSeg(startptr, segno, wal_segment_size); + CheckXLogRemoved(segno, ThisTimeLineID); + + /* + * During recovery, the currently-open WAL file might be replaced with the + * file of the same name retrieved from archive. So we always need to + * check what we read was valid after reading into the buffer. If it's + * invalid, we try to open and read the file again. + */ + if (am_cascading_walsender) + { + WalSnd *walsnd = MyWalSnd; + bool reload; + + SpinLockAcquire(&walsnd->mutex); + reload = walsnd->needreload; + walsnd->needreload = false; + SpinLockRelease(&walsnd->mutex); + + if (reload && sendSeg->file >= 0) + { + close(sendSeg->file); + sendSeg->file = -1; + + goto retry; + } + } + output_message.len += nbytes; output_message.data[output_message.len] = '\0'; diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index a16793bb8b..cd5f589f03 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -296,6 +296,51 @@ identify_target_directory(XLogDumpPrivate *private, char *directory, fatal_error("could not find any WAL file"); } +static void +WALDumpOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli_p, int *file_p, + WALOpenSegment *seg) +{ + TimeLineID tli = *tli_p; + char fname[MAXPGPATH]; + int file; + int tries; + + Assert(tli != InvalidTimeLineID); + + XLogFileName(fname, tli, nextSegNo, seg->size); + + /* + * In follow mode there is a short period of time after the server has + * written the end of the previous file before the new file is available. + * So we loop for 5 seconds looking for the file to appear before giving + * up. + */ + for (tries = 0; tries < 10; tries++) + { + file = open_file_in_directory(seg->dir, fname); + if (file >= 0) + break; + if (errno == ENOENT) + { + int save_errno = errno; + + /* File not there yet, try again */ + pg_usleep(500 * 1000); + + errno = save_errno; + continue; + } + /* Any other error, fall through and fail */ + break; + } + + if (file < 0) + fatal_error("could not find file \"%s\": %s", + fname, strerror(errno)); + + *file_p = file; +} + /* * Read count bytes from a segment file in the specified directory, for the * given timeline, containing the specified record pointer; store the data in @@ -427,6 +472,7 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, { XLogDumpPrivate *private = state->private_data; int count = XLOG_BLCKSZ; + XLogReadError *errinfo; if (private->endptr != InvalidXLogRecPtr) { @@ -441,8 +487,22 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, } } - XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr, - readBuff, count); + if ((errinfo = XLogRead(readBuff, targetPagePtr, count, &private->timeline, + &state->seg, WALDumpOpenSegment)) != NULL) + { + WALOpenSegment *seg = errinfo->seg; + char fname[MAXPGPATH]; + + XLogFileName(fname, seg->tli, seg->num, seg->size); + + if (errno != 0) + fatal_error("could not read from log file %s, offset %u, length %zu: %s", + fname, seg->off, (Size) errinfo->reqbytes, + strerror(errinfo->read_errno)); + else + fatal_error("could not read from log file %s, offset %u: length: %zu", + fname, seg->off, (Size) errinfo->reqbytes); + } return count; } diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index b9d99d524e..3d9742b81b 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -227,8 +227,50 @@ extern bool XLogReaderValidatePageHeader(XLogReaderState *state, extern XLogRecPtr XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr); #endif /* FRONTEND */ +/* + * Callback to open the specified WAL segment for reading. + * + * "nextSegNo" is the number of the segment to be opened. + * + * "tli_p" is an input/output argument. If *tli_p is valid, it's the timeline + * the new segment should be in. If *tli_p==InvalidTimeLineID, the callback + * needs to determine the timeline itself and put the result into *tli_p. + * + * "file_p" points to an address the segment file descriptor should be stored + * at. + * + * "seg" provides information on the currently open segment. The callback is + * not supposed to change this info. + * + * BasicOpenFile() is the preferred way to open the segment file in backend + * code, whereas open(2) should be used in frontend. + */ +typedef void (*WALSegmentOpen) (XLogSegNo nextSegNo, TimeLineID *tli_p, + int *file_p, WALOpenSegment *seg); + extern void WALOpenSegmentInit(WALOpenSegment *seg, int size); +/* + * Error information that both backend and frontend caller can process. + * + * XXX Should the name be WALReadError? If so, we probably need to rename + * XLogRead() and XLogReadProcessError() too. + */ +typedef struct XLogReadError +{ + int read_errno; /* errno set by the last read(). */ + int readbytes; /* Bytes read by the last read(). */ + int reqbytes; /* Bytes requested to be read. */ + WALOpenSegment *seg; /* Segment we tried to read from. */ +} XLogReadError; + +extern XLogReadError *XLogRead(char *buf, XLogRecPtr startptr, Size count, + TimeLineID *tli_p, WALOpenSegment *seg, + WALSegmentOpen openSegment); +#ifndef FRONTEND +void XLogReadProcessError(XLogReadError *errinfo); +#endif + /* Functions for decoding an XLogRecord */ extern bool DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, -- 2.20.1 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v06-0006-Remove-the-old-implemenations-of-XLogRead.patch ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH 1/2] Use only xlogreader.c:XLogRead() @ 2019-09-26 11:51 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 8+ messages in thread From: Antonin Houska @ 2019-09-26 11:51 UTC (permalink / raw) The implementations in xlogutils.c and walsender.c are just renamed now, to be removed by the following diff. --- src/backend/access/transam/xlogreader.c | 153 ++++++++++++++++++++++++ src/backend/access/transam/xlogutils.c | 45 ++++++- src/backend/replication/walsender.c | 138 ++++++++++++++++++++- src/bin/pg_waldump/pg_waldump.c | 60 +++++++++- src/include/access/xlogreader.h | 47 ++++++++ 5 files changed, 433 insertions(+), 10 deletions(-) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index c8b0d2303d..a58c5d1633 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -17,6 +17,8 @@ */ #include "postgres.h" +#include <unistd.h> + #include "access/transam.h" #include "access/xlogrecord.h" #include "access/xlog_internal.h" @@ -27,6 +29,7 @@ #ifndef FRONTEND #include "miscadmin.h" +#include "pgstat.h" #include "utils/memutils.h" #endif @@ -1016,6 +1019,156 @@ out: #endif /* FRONTEND */ +/* + * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'. If + * tli_p is passed, get the data from timeline *tli_p. 'pos' is the current + * position in the XLOG file and openSegment is a callback that opens the next + * segment for reading. + * + * Returns error information if the data could not be read or NULL if + * succeeded. + * + * XXX probably this should be improved to suck data directly from the + * WAL buffers when possible. + */ +XLogReadError * +XLogRead(char *buf, XLogRecPtr startptr, Size count, TimeLineID *tli_p, + WALOpenSegment *seg, WALSegmentContext *segcxt, + WALSegmentOpen openSegment) +{ + char *p; + XLogRecPtr recptr; + Size nbytes; + static XLogReadError errinfo; + + p = buf; + recptr = startptr; + nbytes = count; + + while (nbytes > 0) + { + int segbytes; + int readbytes; + + seg->ws_off = XLogSegmentOffset(recptr, segcxt->ws_segsize); + + if (seg->ws_file < 0 || + !XLByteInSeg(recptr, seg->ws_segno, segcxt->ws_segsize) || + (tli_p != NULL && *tli_p != seg->ws_tli)) + { + XLogSegNo nextSegNo; + TimeLineID tli; + int file; + + /* Switch to another logfile segment */ + if (seg->ws_file >= 0) + close(seg->ws_file); + + XLByteToSeg(recptr, nextSegNo, segcxt->ws_segsize); + + /* + * If we have the TLI, let's pass it to the callback. If NULL is + * passed, the callback has to find the TLI itself. + */ + if (tli_p != NULL) + tli = *tli_p; + + /* Open the next segment in the caller's way. */ + openSegment(nextSegNo, &tli, &file, seg, segcxt); + + /* Update the open segment info. */ + seg->ws_tli = tli; + seg->ws_file = file; + + /* + * If the function is called by the XLOG reader, the reader will + * eventually set both "ws_segno" and "ws_off", however the XLOG + * reader is not necessarily involved. Furthermore, we need to set + * the current values for this function to work. + */ + seg->ws_segno = nextSegNo; + seg->ws_off = 0; + } + + /* How many bytes are within this segment? */ + if (nbytes > (segcxt->ws_segsize - seg->ws_off)) + segbytes = segcxt->ws_segsize - seg->ws_off; + else + segbytes = nbytes; + +#ifndef FRONTEND + pgstat_report_wait_start(WAIT_EVENT_WAL_READ); +#endif + + /* + * Failure to read the data does not necessarily imply non-zero errno. + * Set it to zero so that caller can distinguish the failure that does + * not affect errno. + */ + errno = 0; + + readbytes = pg_pread(seg->ws_file, p, segbytes, seg->ws_off); + +#ifndef FRONTEND + pgstat_report_wait_end(); +#endif + + if (readbytes <= 0) + { + errinfo.read_errno = errno; + errinfo.readbytes = readbytes; + errinfo.reqbytes = segbytes; + errinfo.seg = seg; + return &errinfo; + } + + /* Update state for read */ + recptr += readbytes; + nbytes -= readbytes; + p += readbytes; + + /* + * If the function is called by the XLOG reader, the reader will + * eventually set this field. However we need to care about it too + * because the function can also be used directly (see walsender.c). + */ + seg->ws_off += readbytes; + } + + return NULL; +} + +#ifndef FRONTEND +/* + * Backend-specific convenience code to handle read errors encountered by + * XLogRead(). + */ +void +XLogReadProcessError(XLogReadError *errinfo) +{ + WALOpenSegment *seg = errinfo->seg; + + if (errinfo->readbytes < 0) + { + errno = errinfo->read_errno; + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from log segment %s, offset %u, length %zu: %m", + XLogFileNameP(seg->ws_tli, seg->ws_segno), + seg->ws_off, (Size) errinfo->reqbytes))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not read from log segment %s, offset %u: length %zu", + XLogFileNameP(seg->ws_tli, seg->ws_segno), + seg->ws_off, + (Size) errinfo->reqbytes))); + } +} +#endif + /* ---------------------------------------- * Functions for decoding the data and block references in a record. * ---------------------------------------- diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 5f1e5ba75d..09d42d3112 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -653,8 +653,8 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum, * frontend). Probably these should be merged at some point. */ static void -XLogRead(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, - Size count) +XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, + Size count) { char *p; XLogRecPtr recptr; @@ -896,6 +896,38 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa } } +/* + * Callback for XLogRead() to open the next segment. + */ +static void +read_local_xlog_page_segment_open(XLogSegNo nextSegNo, TimeLineID *tli_p, + int *file_p, WALOpenSegment *seg, + WALSegmentContext *segcxt) +{ + TimeLineID tli = *tli_p; + char path[MAXPGPATH]; + int file; + + XLogFilePath(path, tli, nextSegNo, segcxt->ws_segsize); + file = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (file < 0) + { + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + path))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } + + *file_p = file; +} + /* * read_page callback for reading local xlog files * @@ -914,6 +946,7 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, XLogRecPtr read_upto, loc; int count; + XLogReadError *errinfo; loc = targetPagePtr + reqLen; @@ -1020,8 +1053,12 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * as 'count', read the whole page anyway. It's guaranteed to be * zero-padded up to the page boundary if it's incomplete. */ - XLogRead(cur_page, state->segcxt.ws_segsize, state->seg.ws_tli, targetPagePtr, - XLOG_BLCKSZ); + if ((errinfo = XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ, + &state->seg.ws_tli, + &state->seg, + &state->segcxt, + read_local_xlog_page_segment_open)) != NULL) + XLogReadProcessError(errinfo); /* number of valid bytes in the buffer */ return count; diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index eb4a98cc91..dcb84693fc 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -248,9 +248,14 @@ static void LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time); static TimeOffset LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now); static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch); -static void XLogRead(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count); +static void WalSndSegmentOpen(XLogSegNo nextSegNo, TimeLineID *tli_p, + int *file_p, WALOpenSegment *seg, + WALSegmentContext *segcxt); +static void XLogReadOld(WALSegmentContext *segcxt, char *buf, + XLogRecPtr startptr, Size count); + /* Initialize walsender process before entering the main command loop */ void InitWalSender(void) @@ -766,6 +771,8 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req { XLogRecPtr flushptr; int count; + XLogReadError *errinfo; + XLogSegNo segno; XLogReadDetermineTimeline(state, targetPagePtr, reqLen); sendTimeLineIsHistoric = (state->currTLI != ThisTimeLineID); @@ -786,7 +793,24 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req count = flushptr - targetPagePtr; /* part of the page available */ /* now actually read the data, we know it's there */ - XLogRead(sendCxt, cur_page, targetPagePtr, XLOG_BLCKSZ); + if ((errinfo = XLogRead(cur_page, + targetPagePtr, + XLOG_BLCKSZ, + NULL, /* WalSndSegmentOpen will determine TLI */ + sendSeg, + sendCxt, + WalSndSegmentOpen)) != NULL) + XLogReadProcessError(errinfo); + + /* + * After reading into the buffer, check that what we read was valid. We do + * this after reading, because even though the segment was present when we + * opened it, it might get recycled or removed while we read it. The + * read() succeeds in that case, but the data we tried to read might + * already have been overwritten with new WAL records. + */ + XLByteToSeg(targetPagePtr, segno, sendCxt->ws_segsize); + CheckXLogRemoved(segno, sendSeg->ws_tli); return count; } @@ -2363,7 +2387,7 @@ WalSndKill(int code, Datum arg) * more than one. */ static void -XLogRead(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count) +XLogReadOld(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count) { char *p; XLogRecPtr recptr; @@ -2536,6 +2560,71 @@ retry: } } +/* + * Callback for XLogRead() to open the next segment. + */ +void +WalSndSegmentOpen(XLogSegNo nextSegNo, TimeLineID *tli_p, int *file_p, + WALOpenSegment *seg, WALSegmentContext *segcxt) +{ + char path[MAXPGPATH]; + + /*------- + * When reading from a historic timeline, and there is a timeline switch + * within this segment, read from the WAL segment belonging to the new + * timeline. + * + * For example, imagine that this server is currently on timeline 5, and + * we're streaming timeline 4. The switch from timeline 4 to 5 happened at + * 0/13002088. In pg_wal, we have these files: + * + * ... + * 000000040000000000000012 + * 000000040000000000000013 + * 000000050000000000000013 + * 000000050000000000000014 + * ... + * + * In this situation, when requested to send the WAL from segment 0x13, on + * timeline 4, we read the WAL from file 000000050000000000000013. Archive + * recovery prefers files from newer timelines, so if the segment was + * restored from the archive on this server, the file belonging to the old + * timeline, 000000040000000000000013, might not exist. Their contents are + * equal up to the switchpoint, because at a timeline switch, the used + * portion of the old segment is copied to the new file. ------- + */ + *tli_p = sendTimeLine; + if (sendTimeLineIsHistoric) + { + XLogSegNo endSegNo; + + XLByteToSeg(sendTimeLineValidUpto, endSegNo, segcxt->ws_segsize); + if (seg->ws_segno == endSegNo) + *tli_p = sendTimeLineNextTLI; + } + + XLogFilePath(path, *tli_p, nextSegNo, segcxt->ws_segsize); + *file_p = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (*file_p < 0) + { + /* + * If the file is not found, assume it's because the standby asked for + * a too old WAL segment that has already been removed or recycled. + */ + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + XLogFileNameP(*tli_p, nextSegNo)))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } +} + /* * Send out the WAL in its normal physical/stored form. * @@ -2553,6 +2642,8 @@ XLogSendPhysical(void) XLogRecPtr startptr; XLogRecPtr endptr; Size nbytes; + XLogSegNo segno; + XLogReadError *errinfo; /* If requested switch the WAL sender to the stopping state. */ if (got_STOPPING) @@ -2768,7 +2859,46 @@ XLogSendPhysical(void) * calls. */ enlargeStringInfo(&output_message, nbytes); - XLogRead(sendCxt, &output_message.data[output_message.len], startptr, nbytes); + +retry: + if ((errinfo = XLogRead(&output_message.data[output_message.len], + startptr, + nbytes, + NULL, /* WalSndSegmentOpen will determine TLI */ + sendSeg, + sendCxt, + WalSndSegmentOpen)) != NULL) + XLogReadProcessError(errinfo); + + /* See logical_read_xlog_page(). */ + XLByteToSeg(startptr, segno, sendCxt->ws_segsize); + CheckXLogRemoved(segno, sendSeg->ws_tli); + + /* + * During recovery, the currently-open WAL file might be replaced with the + * file of the same name retrieved from archive. So we always need to + * check what we read was valid after reading into the buffer. If it's + * invalid, we try to open and read the file again. + */ + if (am_cascading_walsender) + { + WalSnd *walsnd = MyWalSnd; + bool reload; + + SpinLockAcquire(&walsnd->mutex); + reload = walsnd->needreload; + walsnd->needreload = false; + SpinLockRelease(&walsnd->mutex); + + if (reload && sendSeg->ws_file >= 0) + { + close(sendSeg->ws_file); + sendSeg->ws_file = -1; + + goto retry; + } + } + output_message.len += nbytes; output_message.data[output_message.len] = '\0'; diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index b79208cd73..c97376101c 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -281,6 +281,46 @@ identify_target_directory(char *directory, char *fname) return NULL; /* not reached */ } +static void +WALDumpOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli_p, int *file_p, + WALOpenSegment *seg, WALSegmentContext *segcxt) +{ + TimeLineID tli = *tli_p; + char fname[MAXPGPATH]; + int tries; + + XLogFileName(fname, tli, nextSegNo, segcxt->ws_segsize); + + /* + * In follow mode there is a short period of time after the server has + * written the end of the previous file before the new file is available. + * So we loop for 5 seconds looking for the file to appear before giving + * up. + */ + for (tries = 0; tries < 10; tries++) + { + *file_p = open_file_in_directory(segcxt->ws_dir, fname); + if (*file_p >= 0) + break; + if (errno == ENOENT) + { + int save_errno = errno; + + /* File not there yet, try again */ + pg_usleep(500 * 1000); + + errno = save_errno; + continue; + } + /* Any other error, fall through and fail */ + break; + } + + if (*file_p < 0) + fatal_error("could not find file \"%s\": %s", + fname, strerror(errno)); +} + /* * Read count bytes from a segment file in the specified directory, for the * given timeline, containing the specified record pointer; store the data in @@ -412,6 +452,7 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, { XLogDumpPrivate *private = state->private_data; int count = XLOG_BLCKSZ; + XLogReadError *errinfo; if (private->endptr != InvalidXLogRecPtr) { @@ -426,8 +467,23 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, } } - XLogDumpXLogRead(state->segcxt.ws_dir, private->timeline, targetPagePtr, - readBuff, count); + if ((errinfo = XLogRead(readBuff, targetPagePtr, count, &private->timeline, + &state->seg, &state->segcxt, WALDumpOpenSegment)) != NULL) + { + WALOpenSegment *seg = errinfo->seg; + char fname[MAXPGPATH]; + + XLogFileName(fname, seg->ws_tli, seg->ws_segno, + state->segcxt.ws_segsize); + + if (errno != 0) + fatal_error("could not read from log file %s, offset %u, length %zu: %s", + fname, seg->ws_off, (Size) errinfo->reqbytes, + strerror(errinfo->read_errno)); + else + fatal_error("could not read from log file %s, offset %u: length: %zu", + fname, seg->ws_off, (Size) errinfo->reqbytes); + } return count; } diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index 1bbee386e8..bf4d105de3 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -218,6 +218,31 @@ extern XLogReaderState *XLogReaderAllocate(int wal_segment_size, extern void XLogReaderFree(XLogReaderState *state); /* Initialize supporting structures */ +/* + * Callback to open the specified WAL segment for reading. + * + * "nextSegNo" is the number of the segment to be opened. + * + * "tli_p" is an input/output argument. If *tli_p is valid, it's the timeline + * the new segment should be in. If *tli_p==InvalidTimeLineID, the callback + * needs to determine the timeline itself and put the result into *tli_p. + * + * "file_p" points to an address the segment file descriptor should be stored + * at. + * + * "seg" provides information on the currently open segment. The callback is + * not supposed to change this info. + * + * "segcxt" is additional information about the segment, which logically does + * not fit into "seg". + * + * BasicOpenFile() is the preferred way to open the segment file in backend + * code, whereas open(2) should be used in frontend. + */ +typedef void (*WALSegmentOpen) (XLogSegNo nextSegNo, TimeLineID *tli_p, + int *file_p, WALOpenSegment *seg, + WALSegmentContext *segcxt); + extern void WALOpenSegmentInit(WALOpenSegment *seg, WALSegmentContext *segcxt, int segsize, const char *waldir); @@ -232,6 +257,28 @@ extern bool XLogReaderValidatePageHeader(XLogReaderState *state, #ifdef FRONTEND extern XLogRecPtr XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr); #endif /* FRONTEND */ +/* + * Error information that both backend and frontend caller can process. + * + * XXX Should the name be WALReadError? If so, we probably need to rename + * XLogRead() and XLogReadProcessError() too. + */ +typedef struct XLogReadError +{ + int read_errno; /* errno set by the last read(). */ + int readbytes; /* Bytes read by the last read(). */ + int reqbytes; /* Bytes requested to be read. */ + WALOpenSegment *seg; /* Segment we tried to read from. */ +} XLogReadError; + +extern XLogReadError *XLogRead(char *buf, XLogRecPtr startptr, + Size count, TimeLineID *tli_p, + WALOpenSegment *seg, WALSegmentContext *segcxt, + WALSegmentOpen openSegment); +#ifndef FRONTEND +void XLogReadProcessError(XLogReadError *errinfo); +#endif + /* Functions for decoding an XLogRecord */ extern bool DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, -- 2.20.1 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v07-0006-Remove-the-old-implemenations-of-XLogRead.patch ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH 1/2] Use only xlogreader.c:XLogRead() @ 2019-10-04 10:07 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 8+ messages in thread From: Antonin Houska @ 2019-10-04 10:07 UTC (permalink / raw) The implementations in xlogutils.c and walsender.c are just renamed now, to be removed by the following diff. --- src/backend/access/transam/xlogreader.c | 141 ++++++++++++++++++++++- src/backend/access/transam/xlogutils.c | 61 ++++++++-- src/backend/replication/walsender.c | 143 +++++++++++++++++++++++- src/bin/pg_waldump/pg_waldump.c | 60 +++++++++- src/include/access/xlogreader.h | 42 +++++++ 5 files changed, 428 insertions(+), 19 deletions(-) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index c8b0d2303d..3e2167ca5a 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -17,6 +17,8 @@ */ #include "postgres.h" +#include <unistd.h> + #include "access/transam.h" #include "access/xlogrecord.h" #include "access/xlog_internal.h" @@ -27,6 +29,7 @@ #ifndef FRONTEND #include "miscadmin.h" +#include "pgstat.h" #include "utils/memutils.h" #endif @@ -626,7 +629,13 @@ ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen) if (!XLogReaderValidatePageHeader(state, pageptr, (char *) hdr)) goto err; - /* update read state information */ + /* + * Update read state information. + * + * Note that XLogRead(), if used, should have updated the "seg" too for + * its own reasons, however we cannot rely on ->read_page() to call + * XLogRead(). + */ state->seg.ws_segno = targetSegNo; state->seg.ws_off = targetPageOff; state->readLen = readLen; @@ -1016,6 +1025,136 @@ out: #endif /* FRONTEND */ +/* + * Read 'count' bytes from WAL fetched from timeline 'tli' into 'buf', + * starting at location 'startptr'. 'seg' is the last segment used, + * 'openSegment' is a callback to opens the next segment if needed and + * 'segcxt' is additional segment info that does not fit into 'seg'. + * + * 'errinfo' should point to XLogReadError structure which will receive error + * details in case the read fails. + * + * Returns true if succeeded, false if failed. + * + * XXX probably this should be improved to suck data directly from the + * WAL buffers when possible. + */ +bool +XLogRead(char *buf, XLogRecPtr startptr, Size count, TimeLineID tli, + WALOpenSegment *seg, WALSegmentContext *segcxt, + WALSegmentOpen openSegment, XLogReadError *errinfo) +{ + char *p; + XLogRecPtr recptr; + Size nbytes; + + p = buf; + recptr = startptr; + nbytes = count; + + while (nbytes > 0) + { + int segbytes; + int readbytes; + + seg->ws_off = XLogSegmentOffset(recptr, segcxt->ws_segsize); + + if (seg->ws_file < 0 || + !XLByteInSeg(recptr, seg->ws_segno, segcxt->ws_segsize) || + tli != seg->ws_tli) + { + XLogSegNo nextSegNo; + + /* Switch to another logfile segment */ + if (seg->ws_file >= 0) + close(seg->ws_file); + + XLByteToSeg(recptr, nextSegNo, segcxt->ws_segsize); + + /* Open the next segment in the caller's way. */ + openSegment(nextSegNo, segcxt, &tli, &seg->ws_file); + + /* Update the current segment info. */ + seg->ws_tli = tli; + seg->ws_segno = nextSegNo; + seg->ws_off = 0; + } + + /* How many bytes are within this segment? */ + if (nbytes > (segcxt->ws_segsize - seg->ws_off)) + segbytes = segcxt->ws_segsize - seg->ws_off; + else + segbytes = nbytes; + +#ifndef FRONTEND + pgstat_report_wait_start(WAIT_EVENT_WAL_READ); +#endif + + /* + * Failure to read the data does not necessarily imply non-zero errno. + * Set it to zero so that caller can distinguish the failure that does + * not affect errno. + */ + errno = 0; + + readbytes = pg_pread(seg->ws_file, p, segbytes, seg->ws_off); + +#ifndef FRONTEND + pgstat_report_wait_end(); +#endif + + if (readbytes <= 0) + { + errinfo->read_errno = errno; + errinfo->readbytes = readbytes; + errinfo->reqbytes = segbytes; + errinfo->seg = seg; + return false; + } + + /* Update state for read */ + recptr += readbytes; + nbytes -= readbytes; + p += readbytes; + + /* Update the current segment info. */ + seg->ws_off += readbytes; + } + + return true; +} + +#ifndef FRONTEND +/* + * Backend-specific convenience code to handle read errors encountered by + * XLogRead(). + */ +void +XLogReadProcessError(XLogReadError *errinfo) +{ + WALOpenSegment *seg = errinfo->seg; + + if (errinfo->readbytes < 0) + { + errno = errinfo->read_errno; + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from log segment %s, offset %u, length %zu: %m", + XLogFileNameP(seg->ws_tli, seg->ws_segno), + seg->ws_off, (Size) errinfo->reqbytes))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not read from log segment %s, offset %u: length %zu", + XLogFileNameP(seg->ws_tli, seg->ws_segno), + seg->ws_off, + (Size) errinfo->reqbytes))); + } +} +#endif + /* ---------------------------------------- * Functions for decoding the data and block references in a record. * ---------------------------------------- diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 5f1e5ba75d..007974ea99 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -653,8 +653,8 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum, * frontend). Probably these should be merged at some point. */ static void -XLogRead(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, - Size count) +XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr, + Size count) { char *p; XLogRecPtr recptr; @@ -896,6 +896,39 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa } } +/* + * Callback for XLogRead() to open the next segment. + */ +static void +read_local_xlog_page_segment_open(XLogSegNo nextSegNo, + WALSegmentContext *segcxt, + TimeLineID *tli_p, + int *file_p) +{ + TimeLineID tli = *tli_p; + char path[MAXPGPATH]; + int file; + + XLogFilePath(path, tli, nextSegNo, segcxt->ws_segsize); + file = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (file < 0) + { + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + path))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } + + *file_p = file; +} + /* * read_page callback for reading local xlog files * @@ -913,7 +946,9 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, { XLogRecPtr read_upto, loc; + TimeLineID tli; int count; + XLogReadError errinfo; loc = targetPagePtr + reqLen; @@ -932,7 +967,7 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, read_upto = GetFlushRecPtr(); else read_upto = GetXLogReplayRecPtr(&ThisTimeLineID); - state->seg.ws_tli = ThisTimeLineID; + tli = ThisTimeLineID; /* * Check which timeline to get the record from. @@ -982,14 +1017,14 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, read_upto = state->currTLIValidUntil; /* - * Setting ws_tli to our wanted record's TLI is slightly wrong; - * the page might begin on an older timeline if it contains a - * timeline switch, since its xlog segment will have been copied - * from the prior timeline. This is pretty harmless though, as - * nothing cares so long as the timeline doesn't go backwards. We - * should read the page header instead; FIXME someday. + * Setting tli to our wanted record's TLI is slightly wrong; the + * page might begin on an older timeline if it contains a timeline + * switch, since its xlog segment will have been copied from the + * prior timeline. This is pretty harmless though, as nothing + * cares so long as the timeline doesn't go backwards. We should + * read the page header instead; FIXME someday. */ - state->seg.ws_tli = state->currTLI; + tli = state->currTLI; /* No need to wait on a historical timeline */ break; @@ -1020,8 +1055,10 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * as 'count', read the whole page anyway. It's guaranteed to be * zero-padded up to the page boundary if it's incomplete. */ - XLogRead(cur_page, state->segcxt.ws_segsize, state->seg.ws_tli, targetPagePtr, - XLOG_BLCKSZ); + if (!XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ, tli, &state->seg, + &state->segcxt, read_local_xlog_page_segment_open, + &errinfo)) + XLogReadProcessError(&errinfo); /* number of valid bytes in the buffer */ return count; diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index eb4a98cc91..b21b143f99 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -248,9 +248,13 @@ static void LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time); static TimeOffset LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now); static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch); -static void XLogRead(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count); +static void WalSndSegmentOpen(XLogSegNo nextSegNo, WALSegmentContext *segcxt, + TimeLineID *tli_p, int *file_p); +static void XLogReadOld(WALSegmentContext *segcxt, char *buf, + XLogRecPtr startptr, Size count); + /* Initialize walsender process before entering the main command loop */ void InitWalSender(void) @@ -766,6 +770,8 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req { XLogRecPtr flushptr; int count; + XLogReadError errinfo; + XLogSegNo segno; XLogReadDetermineTimeline(state, targetPagePtr, reqLen); sendTimeLineIsHistoric = (state->currTLI != ThisTimeLineID); @@ -786,7 +792,27 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req count = flushptr - targetPagePtr; /* part of the page available */ /* now actually read the data, we know it's there */ - XLogRead(sendCxt, cur_page, targetPagePtr, XLOG_BLCKSZ); + if (!XLogRead(cur_page, + targetPagePtr, + XLOG_BLCKSZ, + sendSeg->ws_tli, /* Pass the current TLI because only + * WalSndSegmentOpen controls whether new + * TLI is needed. */ + sendSeg, + sendCxt, + WalSndSegmentOpen, + &errinfo)) + XLogReadProcessError(&errinfo); + + /* + * After reading into the buffer, check that what we read was valid. We do + * this after reading, because even though the segment was present when we + * opened it, it might get recycled or removed while we read it. The + * read() succeeds in that case, but the data we tried to read might + * already have been overwritten with new WAL records. + */ + XLByteToSeg(targetPagePtr, segno, sendCxt->ws_segsize); + CheckXLogRemoved(segno, sendSeg->ws_tli); return count; } @@ -2363,7 +2389,7 @@ WalSndKill(int code, Datum arg) * more than one. */ static void -XLogRead(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count) +XLogReadOld(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count) { char *p; XLogRecPtr recptr; @@ -2536,6 +2562,71 @@ retry: } } +/* + * Callback for XLogRead() to open the next segment. + */ +void +WalSndSegmentOpen(XLogSegNo nextSegNo, WALSegmentContext *segcxt, + TimeLineID *tli_p, int *file_p) +{ + char path[MAXPGPATH]; + + /*------- + * When reading from a historic timeline, and there is a timeline switch + * within this segment, read from the WAL segment belonging to the new + * timeline. + * + * For example, imagine that this server is currently on timeline 5, and + * we're streaming timeline 4. The switch from timeline 4 to 5 happened at + * 0/13002088. In pg_wal, we have these files: + * + * ... + * 000000040000000000000012 + * 000000040000000000000013 + * 000000050000000000000013 + * 000000050000000000000014 + * ... + * + * In this situation, when requested to send the WAL from segment 0x13, on + * timeline 4, we read the WAL from file 000000050000000000000013. Archive + * recovery prefers files from newer timelines, so if the segment was + * restored from the archive on this server, the file belonging to the old + * timeline, 000000040000000000000013, might not exist. Their contents are + * equal up to the switchpoint, because at a timeline switch, the used + * portion of the old segment is copied to the new file. ------- + */ + *tli_p = sendTimeLine; + if (sendTimeLineIsHistoric) + { + XLogSegNo endSegNo; + + XLByteToSeg(sendTimeLineValidUpto, endSegNo, segcxt->ws_segsize); + if (sendSeg->ws_segno == endSegNo) + *tli_p = sendTimeLineNextTLI; + } + + XLogFilePath(path, *tli_p, nextSegNo, segcxt->ws_segsize); + *file_p = BasicOpenFile(path, O_RDONLY | PG_BINARY); + + if (*file_p < 0) + { + /* + * If the file is not found, assume it's because the standby asked for + * a too old WAL segment that has already been removed or recycled. + */ + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + XLogFileNameP(*tli_p, nextSegNo)))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } +} + /* * Send out the WAL in its normal physical/stored form. * @@ -2553,6 +2644,8 @@ XLogSendPhysical(void) XLogRecPtr startptr; XLogRecPtr endptr; Size nbytes; + XLogSegNo segno; + XLogReadError errinfo; /* If requested switch the WAL sender to the stopping state. */ if (got_STOPPING) @@ -2768,7 +2861,49 @@ XLogSendPhysical(void) * calls. */ enlargeStringInfo(&output_message, nbytes); - XLogRead(sendCxt, &output_message.data[output_message.len], startptr, nbytes); + +retry: + if (!XLogRead(&output_message.data[output_message.len], + startptr, + nbytes, + sendSeg->ws_tli, /* Pass the current TLI because only + * WalSndSegmentOpen controls whether new + * TLI is needed. */ + sendSeg, + sendCxt, + WalSndSegmentOpen, + &errinfo)) + XLogReadProcessError(&errinfo); + + /* See logical_read_xlog_page(). */ + XLByteToSeg(startptr, segno, sendCxt->ws_segsize); + CheckXLogRemoved(segno, sendSeg->ws_tli); + + /* + * During recovery, the currently-open WAL file might be replaced with the + * file of the same name retrieved from archive. So we always need to + * check what we read was valid after reading into the buffer. If it's + * invalid, we try to open and read the file again. + */ + if (am_cascading_walsender) + { + WalSnd *walsnd = MyWalSnd; + bool reload; + + SpinLockAcquire(&walsnd->mutex); + reload = walsnd->needreload; + walsnd->needreload = false; + SpinLockRelease(&walsnd->mutex); + + if (reload && sendSeg->ws_file >= 0) + { + close(sendSeg->ws_file); + sendSeg->ws_file = -1; + + goto retry; + } + } + output_message.len += nbytes; output_message.data[output_message.len] = '\0'; diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index b79208cd73..4c49d1acf4 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -281,6 +281,46 @@ identify_target_directory(char *directory, char *fname) return NULL; /* not reached */ } +static void +WALDumpOpenSegment(XLogSegNo nextSegNo, WALSegmentContext *segcxt, + TimeLineID *tli_p, int *file_p) +{ + TimeLineID tli = *tli_p; + char fname[MAXPGPATH]; + int tries; + + XLogFileName(fname, tli, nextSegNo, segcxt->ws_segsize); + + /* + * In follow mode there is a short period of time after the server has + * written the end of the previous file before the new file is available. + * So we loop for 5 seconds looking for the file to appear before giving + * up. + */ + for (tries = 0; tries < 10; tries++) + { + *file_p = open_file_in_directory(segcxt->ws_dir, fname); + if (*file_p >= 0) + break; + if (errno == ENOENT) + { + int save_errno = errno; + + /* File not there yet, try again */ + pg_usleep(500 * 1000); + + errno = save_errno; + continue; + } + /* Any other error, fall through and fail */ + break; + } + + if (*file_p < 0) + fatal_error("could not find file \"%s\": %s", + fname, strerror(errno)); +} + /* * Read count bytes from a segment file in the specified directory, for the * given timeline, containing the specified record pointer; store the data in @@ -412,6 +452,7 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, { XLogDumpPrivate *private = state->private_data; int count = XLOG_BLCKSZ; + XLogReadError errinfo; if (private->endptr != InvalidXLogRecPtr) { @@ -426,8 +467,23 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, } } - XLogDumpXLogRead(state->segcxt.ws_dir, private->timeline, targetPagePtr, - readBuff, count); + if (!XLogRead(readBuff, targetPagePtr, count, private->timeline, + &state->seg, &state->segcxt, WALDumpOpenSegment, &errinfo)) + { + WALOpenSegment *seg = errinfo.seg; + char fname[MAXPGPATH]; + + XLogFileName(fname, seg->ws_tli, seg->ws_segno, + state->segcxt.ws_segsize); + + if (errno != 0) + fatal_error("could not read from log file %s, offset %u, length %zu: %s", + fname, seg->ws_off, (Size) errinfo.reqbytes, + strerror(errinfo.read_errno)); + else + fatal_error("could not read from log file %s, offset %u: length: %zu", + fname, seg->ws_off, (Size) errinfo.reqbytes); + } return count; } diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index 1bbee386e8..f066b6255d 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -218,6 +218,26 @@ extern XLogReaderState *XLogReaderAllocate(int wal_segment_size, extern void XLogReaderFree(XLogReaderState *state); /* Initialize supporting structures */ +/* + * Callback to open the specified WAL segment for reading. + * + * "nextSegNo" is the number of the segment to be opened. + * + * "segcxt" is additional information about the segment. + * + * "tli_p" is an input/output argument. XLogRead() uses it to pass the + * timeline in which the new segment should be found, but the callback can use + * it to return the TLI that it actually opened. + * + * "file_p" points to an address the segment file descriptor should be stored + * at. + * + * BasicOpenFile() is the preferred way to open the segment file in backend + * code, whereas open(2) should be used in frontend. + */ +typedef void (*WALSegmentOpen) (XLogSegNo nextSegNo, WALSegmentContext *segcxt, + TimeLineID *tli_p, int *file_p); + extern void WALOpenSegmentInit(WALOpenSegment *seg, WALSegmentContext *segcxt, int segsize, const char *waldir); @@ -232,6 +252,28 @@ extern bool XLogReaderValidatePageHeader(XLogReaderState *state, #ifdef FRONTEND extern XLogRecPtr XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr); #endif /* FRONTEND */ +/* + * Error information that both backend and frontend caller can process. + * + * XXX Should the name be WALReadError? If so, we probably need to rename + * XLogRead() and XLogReadProcessError() too. + */ +typedef struct XLogReadError +{ + int read_errno; /* errno set by the last read(). */ + int readbytes; /* Bytes read by the last read(). */ + int reqbytes; /* Bytes requested to be read. */ + WALOpenSegment *seg; /* Segment we tried to read from. */ +} XLogReadError; + +extern bool XLogRead(char *buf, XLogRecPtr startptr, Size count, + TimeLineID tli, WALOpenSegment *seg, + WALSegmentContext *segcxt, WALSegmentOpen openSegment, + XLogReadError *errinfo); +#ifndef FRONTEND +void XLogReadProcessError(XLogReadError *errinfo); +#endif + /* Functions for decoding an XLogRecord */ extern bool DecodeXLogRecord(XLogReaderState *state, XLogRecord *record, -- 2.20.1 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v08-0006-Remove-the-old-implemenations-of-XLogRead.patch ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH v6 1/4] pg_dump/pg_dumpall: bump minimum supported version to v10 @ 2026-04-17 17:15 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 8+ messages in thread From: Nathan Bossart @ 2026-04-17 17:15 UTC (permalink / raw) --- doc/src/sgml/ref/pg_dump.sgml | 14 +- doc/src/sgml/runtime.sgml | 2 +- src/bin/pg_dump/connectdb.c | 4 +- src/bin/pg_dump/pg_dump.c | 334 ++-------------------------------- src/bin/pg_dump/pg_dumpall.c | 28 --- 5 files changed, 23 insertions(+), 359 deletions(-) diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index 0e0d53926af..774be23b4f9 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -407,18 +407,6 @@ PostgreSQL documentation and there is no way to continue with the dump, so <application>pg_dump</application> has no choice but to abort the dump. </para> - <para> - To perform a parallel dump, the database server needs to support - synchronized snapshots, a feature that was introduced in - <productname>PostgreSQL</productname> 9.2 for primary servers and 10 - for standbys. With this feature, database clients can ensure they see - the same data set even though they use different connections. - <command>pg_dump -j</command> uses multiple database connections; it - connects to the database once with the leader process and once again - for each worker job. Without the synchronized snapshot feature, the - different worker jobs wouldn't be guaranteed to see the same data in - each connection, which could lead to an inconsistent backup. - </para> </listitem> </varlistentry> @@ -1717,7 +1705,7 @@ CREATE DATABASE foo WITH TEMPLATE template0; <productname>PostgreSQL</productname> server versions newer than <application>pg_dump</application>'s version. <application>pg_dump</application> can also dump from <productname>PostgreSQL</productname> servers older than its own version. - (Currently, servers back to version 9.2 are supported.) + (Currently, servers back to version 10 are supported.) However, <application>pg_dump</application> cannot dump from <productname>PostgreSQL</productname> servers newer than its own major version; it will refuse to even try, rather than risk making an invalid dump. diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index dfa292c2c3a..d9984910cc4 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -1776,7 +1776,7 @@ $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput version of <productname>PostgreSQL</productname>, to take advantage of enhancements that might have been made in these programs. Current releases of the - dump programs can read data from any server version back to 9.2. + dump programs can read data from any server version back to 10. </para> <para> diff --git a/src/bin/pg_dump/connectdb.c b/src/bin/pg_dump/connectdb.c index f3ce8b1cfb1..d145308dccf 100644 --- a/src/bin/pg_dump/connectdb.c +++ b/src/bin/pg_dump/connectdb.c @@ -212,11 +212,11 @@ ConnectDatabase(const char *dbname, const char *connection_string, my_version = PG_VERSION_NUM; /* - * We allow the server to be back to 9.2, and up to any minor release of + * We allow the server to be back to 10, and up to any minor release of * our own major version. (See also version check in pg_dump.c.) */ if (my_version != server_version_temp - && (server_version_temp < 90200 || + && (server_version_temp < 100000 || (server_version_temp / 100) > (my_version / 100))) { pg_log_error("aborting because of server version mismatch"); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index c56437d6057..65bd15ebfd2 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -977,10 +977,10 @@ main(int argc, char **argv) /* - * We allow the server to be back to 9.2, and up to any minor release of + * We allow the server to be back to 10, and up to any minor release of * our own major version. (See also version check in pg_dumpall.c.) */ - fout->minRemoteVersion = 90200; + fout->minRemoteVersion = 100000; fout->maxRemoteVersion = (PG_VERSION_NUM / 100) * 100 + 99; fout->numWorkers = numWorkers; @@ -1491,9 +1491,7 @@ setup_connection(Archive *AH, const char *dumpencoding, * Disable timeouts if supported. */ ExecuteSqlStatement(AH, "SET statement_timeout = 0"); - if (AH->remoteVersion >= 90300) ExecuteSqlStatement(AH, "SET lock_timeout = 0"); - if (AH->remoteVersion >= 90600) ExecuteSqlStatement(AH, "SET idle_in_transaction_session_timeout = 0"); if (AH->remoteVersion >= 170000) ExecuteSqlStatement(AH, "SET transaction_timeout = 0"); @@ -1507,13 +1505,10 @@ setup_connection(Archive *AH, const char *dumpencoding, /* * Adjust row-security mode, if supported. */ - if (AH->remoteVersion >= 90500) - { if (dopt->enable_row_security) ExecuteSqlStatement(AH, "SET row_security = on"); else ExecuteSqlStatement(AH, "SET row_security = off"); - } /* * For security reasons, we restrict the expansion of non-system views and @@ -1568,11 +1563,7 @@ setup_connection(Archive *AH, const char *dumpencoding, destroyPQExpBuffer(query); } else if (AH->numWorkers > 1) - { - if (AH->isStandby && AH->remoteVersion < 100000) - pg_fatal("parallel dumps from standby servers are not supported by this server version"); AH->sync_snapshot_id = get_synchronized_snapshot(AH); - } } /* Set up connection for a parallel worker process */ @@ -1942,13 +1933,11 @@ checkExtensionMembership(DumpableObject *dobj, Archive *fout) addObjectDependency(dobj, ext->dobj.dumpId); /* - * In 9.6 and above, mark the member object to have any non-initial ACLs + * Mark the member object to have any non-initial ACLs * dumped. (Any initial ACLs will be removed later, using data from * pg_init_privs, so that we'll dump only the delta from the extension's * initial setup.) * - * Prior to 9.6, we do not include any extension member components. - * * In binary upgrades, we still dump all components of the members * individually, since the idea is to exactly reproduce the database * contents rather than replace the extension contents with something @@ -1964,12 +1953,7 @@ checkExtensionMembership(DumpableObject *dobj, Archive *fout) if (fout->dopt->binary_upgrade) dobj->dump = ext->dobj.dump; else - { - if (fout->remoteVersion < 90600) - dobj->dump = DUMP_COMPONENT_NONE; - else dobj->dump = ext->dobj.dump_contains & (DUMP_COMPONENT_ACL); - } return true; } @@ -2000,11 +1984,10 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout) simple_oid_list_member(&schema_include_oids, nsinfo->dobj.catId.oid) ? DUMP_COMPONENT_ALL : DUMP_COMPONENT_NONE; - else if (fout->remoteVersion >= 90600 && - strcmp(nsinfo->dobj.name, "pg_catalog") == 0) + else if (strcmp(nsinfo->dobj.name, "pg_catalog") == 0) { /* - * In 9.6 and above, we dump out any ACLs defined in pg_catalog, if + * We dump out any ACLs defined in pg_catalog, if * they are interesting (and not the original ACLs which were set at * initdb time, see pg_init_privs). */ @@ -2213,8 +2196,7 @@ selectDumpableProcLang(ProcLangInfo *plang, Archive *fout) else { if (plang->dobj.catId.oid <= g_last_builtin_oid) - plang->dobj.dump = fout->remoteVersion < 90600 ? - DUMP_COMPONENT_NONE : DUMP_COMPONENT_ACL; + plang->dobj.dump = DUMP_COMPONENT_ACL; else plang->dobj.dump = DUMP_COMPONENT_ALL; } @@ -2231,13 +2213,6 @@ selectDumpableProcLang(ProcLangInfo *plang, Archive *fout) static void selectDumpableAccessMethod(AccessMethodInfo *method, Archive *fout) { - /* see getAccessMethods() comment about v9.6. */ - if (fout->remoteVersion < 90600) - { - method->dobj.dump = DUMP_COMPONENT_NONE; - return; - } - if (checkExtensionMembership(&method->dobj, fout)) return; /* extension membership overrides all else */ @@ -3123,10 +3098,6 @@ buildMatViewRefreshDependencies(Archive *fout) i_objid, i_refobjid; - /* No Mat Views before 9.3. */ - if (fout->remoteVersion < 90300) - return; - query = createPQExpBuffer(); appendPQExpBufferStr(query, "WITH RECURSIVE w AS " @@ -3325,10 +3296,7 @@ dumpDatabase(Archive *fout) "datcollate, datctype, datfrozenxid, " "datacl, acldefault('d', datdba) AS acldefault, " "datistemplate, datconnlimit, "); - if (fout->remoteVersion >= 90300) appendPQExpBufferStr(dbQry, "datminmxid, "); - else - appendPQExpBufferStr(dbQry, "0 AS datminmxid, "); if (fout->remoteVersion >= 170000) appendPQExpBufferStr(dbQry, "datlocprovider, datlocale, datcollversion, "); else if (fout->remoteVersion >= 150000) @@ -3670,17 +3638,11 @@ dumpDatabase(Archive *fout) ii_oid, ii_relminmxid; - if (fout->remoteVersion >= 90300) appendPQExpBuffer(loFrozenQry, "SELECT relfrozenxid, relminmxid, relfilenode, oid\n" "FROM pg_catalog.pg_class\n" "WHERE oid IN (%u, %u, %u, %u);\n", LargeObjectRelationId, LargeObjectLOidPNIndexId, LargeObjectMetadataRelationId, LargeObjectMetadataOidIndexId); - else - appendPQExpBuffer(loFrozenQry, "SELECT relfrozenxid, 0 AS relminmxid, relfilenode, oid\n" - "FROM pg_catalog.pg_class\n" - "WHERE oid IN (%u, %u);\n", - LargeObjectRelationId, LargeObjectLOidPNIndexId); lo_res = ExecuteSqlQuery(fout, loFrozenQry->data, PGRES_TUPLES_OK); @@ -4243,10 +4205,6 @@ getPolicies(Archive *fout, TableInfo tblinfo[], int numTables) j, ntups; - /* No policies before 9.5 */ - if (fout->remoteVersion < 90500) - return; - /* Skip if --no-policies was specified */ if (dopt->no_policies) return; @@ -4316,10 +4274,7 @@ getPolicies(Archive *fout, TableInfo tblinfo[], int numTables) printfPQExpBuffer(query, "SELECT pol.oid, pol.tableoid, pol.polrelid, pol.polname, pol.polcmd, "); - if (fout->remoteVersion >= 100000) appendPQExpBufferStr(query, "pol.polpermissive, "); - else - appendPQExpBufferStr(query, "'t' as polpermissive, "); appendPQExpBuffer(query, "CASE WHEN pol.polroles = '{0}' THEN NULL ELSE " " pg_catalog.array_to_string(ARRAY(SELECT pg_catalog.quote_ident(rolname) from pg_catalog.pg_roles WHERE oid = ANY(pol.polroles)), ', ') END AS polroles, " @@ -4534,7 +4489,7 @@ getPublications(Archive *fout) int i, ntups; - if (dopt->no_publications || fout->remoteVersion < 100000) + if (dopt->no_publications) return; query = createPQExpBuffer(); @@ -4897,7 +4852,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables) j, ntups; - if (dopt->no_publications || fout->remoteVersion < 100000) + if (dopt->no_publications) return; query = createPQExpBuffer(); @@ -5187,7 +5142,7 @@ getSubscriptions(Archive *fout) int i, ntups; - if (dopt->no_subscriptions || fout->remoteVersion < 100000) + if (dopt->no_subscriptions) return; if (!is_superuser(fout)) @@ -6675,24 +6630,12 @@ getAccessMethods(Archive *fout) query = createPQExpBuffer(); /* - * Select all access methods from pg_am table. v9.6 introduced CREATE - * ACCESS METHOD, so earlier versions usually have only built-in access - * methods. v9.6 also changed the access method API, replacing dozens of - * pg_am columns with amhandler. Even if a user created an access method - * by "INSERT INTO pg_am", we have no way to translate pre-v9.6 pg_am - * columns to a v9.6+ CREATE ACCESS METHOD. Hence, before v9.6, read - * pg_am just to facilitate findAccessMethodByOid() providing the - * OID-to-name mapping. + * Select all access methods from pg_am table. */ appendPQExpBufferStr(query, "SELECT tableoid, oid, amname, "); - if (fout->remoteVersion >= 90600) appendPQExpBufferStr(query, "amtype, " "amhandler::pg_catalog.regproc AS amhandler "); - else - appendPQExpBufferStr(query, - "'i'::pg_catalog.\"char\" AS amtype, " - "'-'::pg_catalog.regproc AS amhandler "); appendPQExpBufferStr(query, "FROM pg_am"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -6878,15 +6821,12 @@ getAggregates(Archive *fout) int i_proowner; int i_aggacl; int i_acldefault; + const char *agg_check; /* * Find all interesting aggregates. See comment in getFuncs() for the * rationale behind the filtering logic. */ - if (fout->remoteVersion >= 90600) - { - const char *agg_check; - agg_check = (fout->remoteVersion >= 110000 ? "p.prokind = 'a'" : "p.proisagg"); @@ -6916,29 +6856,6 @@ getAggregates(Archive *fout) "refclassid = 'pg_extension'::regclass AND " "deptype = 'e')"); appendPQExpBufferChar(query, ')'); - } - else - { - appendPQExpBufferStr(query, "SELECT tableoid, oid, proname AS aggname, " - "pronamespace AS aggnamespace, " - "pronargs, proargtypes, " - "proowner, " - "proacl AS aggacl, " - "acldefault('f', proowner) AS acldefault " - "FROM pg_proc p " - "WHERE proisagg AND (" - "pronamespace != " - "(SELECT oid FROM pg_namespace " - "WHERE nspname = 'pg_catalog')"); - if (dopt->binary_upgrade) - appendPQExpBufferStr(query, - " OR EXISTS(SELECT 1 FROM pg_depend WHERE " - "classid = 'pg_proc'::regclass AND " - "objid = p.oid AND " - "refclassid = 'pg_extension'::regclass AND " - "deptype = 'e')"); - appendPQExpBufferChar(query, ')'); - } res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -7021,6 +6938,7 @@ getFuncs(Archive *fout) int i_prorettype; int i_proacl; int i_acldefault; + const char *not_agg_check; /* * Find all interesting functions. This is a bit complicated: @@ -7039,14 +6957,10 @@ getFuncs(Archive *fout) * include them, since we want to dump extension members individually in * that mode. Also, if they are used by casts or transforms then we need * to gather the information about them, though they won't be dumped if - * they are built-in. Also, in 9.6 and up, include functions in + * they are built-in. Also, include functions in * pg_catalog if they have an ACL different from what's shown in * pg_init_privs (so we have to join to pg_init_privs; annoying). */ - if (fout->remoteVersion >= 90600) - { - const char *not_agg_check; - not_agg_check = (fout->remoteVersion >= 110000 ? "p.prokind <> 'a'" : "NOT p.proisagg"); @@ -7090,46 +7004,6 @@ getFuncs(Archive *fout) appendPQExpBufferStr(query, "\n OR p.proacl IS DISTINCT FROM pip.initprivs"); appendPQExpBufferChar(query, ')'); - } - else - { - appendPQExpBuffer(query, - "SELECT tableoid, oid, proname, prolang, " - "pronargs, proargtypes, prorettype, proacl, " - "acldefault('f', proowner) AS acldefault, " - "pronamespace, " - "proowner " - "FROM pg_proc p " - "WHERE NOT proisagg" - "\n AND NOT EXISTS (SELECT 1 FROM pg_depend " - "WHERE classid = 'pg_proc'::regclass AND " - "objid = p.oid AND deptype = 'i')" - "\n AND (" - "\n pronamespace != " - "(SELECT oid FROM pg_namespace " - "WHERE nspname = 'pg_catalog')" - "\n OR EXISTS (SELECT 1 FROM pg_cast" - "\n WHERE pg_cast.oid > '%u'::oid" - "\n AND p.oid = pg_cast.castfunc)", - g_last_builtin_oid); - - if (fout->remoteVersion >= 90500) - appendPQExpBuffer(query, - "\n OR EXISTS (SELECT 1 FROM pg_transform" - "\n WHERE pg_transform.oid > '%u'::oid" - "\n AND (p.oid = pg_transform.trffromsql" - "\n OR p.oid = pg_transform.trftosql))", - g_last_builtin_oid); - - if (dopt->binary_upgrade) - appendPQExpBufferStr(query, - "\n OR EXISTS(SELECT 1 FROM pg_depend WHERE " - "classid = 'pg_proc'::regclass AND " - "objid = p.oid AND " - "refclassid = 'pg_extension'::regclass AND " - "deptype = 'e')"); - appendPQExpBufferChar(query, ')'); - } res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -7378,64 +7252,31 @@ getTables(Archive *fout, int *numTables) appendPQExpBufferStr(query, "c.relhasoids, "); - if (fout->remoteVersion >= 90300) appendPQExpBufferStr(query, "c.relispopulated, "); - else - appendPQExpBufferStr(query, - "'t' as relispopulated, "); - if (fout->remoteVersion >= 90400) appendPQExpBufferStr(query, "c.relreplident, "); - else - appendPQExpBufferStr(query, - "'d' AS relreplident, "); - if (fout->remoteVersion >= 90500) appendPQExpBufferStr(query, "c.relrowsecurity, c.relforcerowsecurity, "); - else - appendPQExpBufferStr(query, - "false AS relrowsecurity, " - "false AS relforcerowsecurity, "); - if (fout->remoteVersion >= 90300) appendPQExpBufferStr(query, "c.relminmxid, tc.relminmxid AS tminmxid, "); - else - appendPQExpBufferStr(query, - "0 AS relminmxid, 0 AS tminmxid, "); - if (fout->remoteVersion >= 90300) appendPQExpBufferStr(query, "array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, " "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text " "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "); - else - appendPQExpBufferStr(query, - "c.reloptions, NULL AS checkoption, "); - if (fout->remoteVersion >= 90600) appendPQExpBufferStr(query, "am.amname, "); - else - appendPQExpBufferStr(query, - "NULL AS amname, "); - if (fout->remoteVersion >= 90600) appendPQExpBufferStr(query, "(d.deptype = 'i') IS TRUE AS is_identity_sequence, "); - else - appendPQExpBufferStr(query, - "false AS is_identity_sequence, "); - if (fout->remoteVersion >= 100000) appendPQExpBufferStr(query, "c.relispartition AS ispartition "); - else - appendPQExpBufferStr(query, - "false AS ispartition "); /* * Left join to pg_depend to pick up dependency info linking sequences to @@ -7453,9 +7294,8 @@ getTables(Archive *fout, int *numTables) "LEFT JOIN pg_tablespace tsp ON (tsp.oid = c.reltablespace)\n"); /* - * In 9.6 and up, left join to pg_am to pick up the amname. + * Left join to pg_am to pick up the amname. */ - if (fout->remoteVersion >= 90600) appendPQExpBufferStr(query, "LEFT JOIN pg_am am ON (c.relam = am.oid)\n"); @@ -8028,12 +7868,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) "t.reloptions AS indreloptions, "); - if (fout->remoteVersion >= 90400) appendPQExpBufferStr(query, "i.indisreplident, "); - else - appendPQExpBufferStr(query, - "false AS indisreplident, "); if (fout->remoteVersion >= 110000) appendPQExpBufferStr(query, @@ -8318,10 +8154,6 @@ getExtendedStatistics(Archive *fout) int i_stattarget; int i; - /* Extended statistics were new in v10 */ - if (fout->remoteVersion < 100000) - return; - query = createPQExpBuffer(); if (fout->remoteVersion < 130000) @@ -8988,10 +8820,6 @@ getEventTriggers(Archive *fout) i_evtenabled; int ntups; - /* Before 9.3, there are no event triggers */ - if (fout->remoteVersion < 90300) - return; - query = createPQExpBuffer(); appendPQExpBufferStr(query, @@ -9258,10 +9086,6 @@ getTransforms(Archive *fout) int i_trffromsql; int i_trftosql; - /* Transforms didn't exist pre-9.5 */ - if (fout->remoteVersion < 90500) - return; - query = createPQExpBuffer(); appendPQExpBufferStr(query, "SELECT tableoid, oid, " @@ -9489,12 +9313,8 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) appendPQExpBufferStr(q, "'' AS attcompression,\n"); - if (fout->remoteVersion >= 100000) appendPQExpBufferStr(q, "a.attidentity,\n"); - else - appendPQExpBufferStr(q, - "'' AS attidentity,\n"); if (fout->remoteVersion >= 110000) appendPQExpBufferStr(q, @@ -10897,8 +10717,6 @@ getAdditionalACLs(Archive *fout) PQclear(res); /* Fetch initial-privileges data */ - if (fout->remoteVersion >= 90600) - { printfPQExpBuffer(query, "SELECT objoid, classoid, objsubid, privtype, initprivs " "FROM pg_init_privs"); @@ -10966,7 +10784,6 @@ getAdditionalACLs(Archive *fout) } } PQclear(res); - } destroyPQExpBuffer(query); } @@ -11141,15 +10958,6 @@ fetchAttributeStats(Archive *fout) static bool restarted; int max_rels = MAX_ATTR_STATS_RELS; - /* - * Our query for retrieving statistics for multiple relations uses WITH - * ORDINALITY and multi-argument UNNEST(), both of which were introduced - * in v9.4. For older versions, we resort to gathering statistics for a - * single relation at a time. - */ - if (fout->remoteVersion < 90400) - max_rels = 1; - /* If we're just starting, set our TOC pointer. */ if (!te) te = AH->toc->next; @@ -11320,16 +11128,11 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg, const TocEntry *te) * The results must be in the order of the relations supplied in the * parameters to ensure we remain in sync as we walk through the TOC. * - * For v9.4 through v18, the redundant filter clause on s.tablename = + * For versions before 19, the redundant filter clause on s.tablename = * ANY(...) seems sufficient to convince the planner to use * pg_class_relname_nsp_index, which avoids a full scan of pg_stats. * In newer versions, pg_stats returns the table OIDs, eliminating the * need for that hack. - * - * Our query for retrieving statistics for multiple relations uses - * WITH ORDINALITY and multi-argument UNNEST(), both of which were - * introduced in v9.4. For older versions, we resort to gathering - * statistics for a single relation at a time. */ if (fout->remoteVersion >= 190000) appendPQExpBufferStr(query, @@ -11337,7 +11140,7 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg, const TocEntry *te) "JOIN unnest($1) WITH ORDINALITY AS u (tableid, ord) " "ON s.tableid = u.tableid " "ORDER BY u.ord, s.attname, s.inherited"); - else if (fout->remoteVersion >= 90400) + else appendPQExpBufferStr(query, "FROM pg_catalog.pg_stats s " "JOIN unnest($1, $2) WITH ORDINALITY AS u (schemaname, tablename, ord) " @@ -11345,12 +11148,6 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg, const TocEntry *te) "AND s.tablename = u.tablename " "WHERE s.tablename = ANY($2) " "ORDER BY u.ord, s.attname, s.inherited"); - else - appendPQExpBufferStr(query, - "FROM pg_catalog.pg_stats s " - "WHERE s.schemaname = $1[1] " - "AND s.tablename = $2[1] " - "ORDER BY s.attname, s.inherited"); ExecuteSqlStatement(fout, query->data); @@ -13672,19 +13469,11 @@ dumpFunc(Archive *fout, const FuncInfo *finfo) "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n" "proleakproof,\n"); - if (fout->remoteVersion >= 90500) appendPQExpBufferStr(query, "array_to_string(protrftypes, ' ') AS protrftypes,\n"); - else - appendPQExpBufferStr(query, - "NULL AS protrftypes,\n"); - if (fout->remoteVersion >= 90600) appendPQExpBufferStr(query, "proparallel,\n"); - else - appendPQExpBufferStr(query, - "'u' AS proparallel,\n"); if (fout->remoteVersion >= 110000) appendPQExpBufferStr(query, @@ -15174,14 +14963,9 @@ dumpCollation(Archive *fout, const CollInfo *collinfo) /* Get collation-specific details */ appendPQExpBufferStr(query, "SELECT "); - if (fout->remoteVersion >= 100000) appendPQExpBufferStr(query, "collprovider, " "collversion, "); - else - appendPQExpBufferStr(query, - "'c' AS collprovider, " - "NULL AS collversion, "); if (fout->remoteVersion >= 120000) appendPQExpBufferStr(query, @@ -15588,7 +15372,6 @@ dumpAgg(Archive *fout, const AggInfo *agginfo) "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n" "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"); - if (fout->remoteVersion >= 90400) appendPQExpBufferStr(query, "aggkind,\n" "aggmtransfn,\n" @@ -15600,31 +15383,12 @@ dumpAgg(Archive *fout, const AggInfo *agginfo) "aggtransspace,\n" "aggmtransspace,\n" "aggminitval,\n"); - else - appendPQExpBufferStr(query, - "'n' AS aggkind,\n" - "'-' AS aggmtransfn,\n" - "'-' AS aggminvtransfn,\n" - "'-' AS aggmfinalfn,\n" - "0 AS aggmtranstype,\n" - "false AS aggfinalextra,\n" - "false AS aggmfinalextra,\n" - "0 AS aggtransspace,\n" - "0 AS aggmtransspace,\n" - "NULL AS aggminitval,\n"); - if (fout->remoteVersion >= 90600) appendPQExpBufferStr(query, "aggcombinefn,\n" "aggserialfn,\n" "aggdeserialfn,\n" "proparallel,\n"); - else - appendPQExpBufferStr(query, - "'-' AS aggcombinefn,\n" - "'-' AS aggserialfn,\n" - "'-' AS aggdeserialfn,\n" - "'u' AS proparallel,\n"); if (fout->remoteVersion >= 110000) appendPQExpBufferStr(query, @@ -17084,8 +16848,6 @@ dumpTable(Archive *fout, const TableInfo *tbinfo) appendPQExpBufferStr(query, "PREPARE getColumnACLs(pg_catalog.oid) AS\n"); - if (fout->remoteVersion >= 90600) - { /* * In principle we should call acldefault('c', relowner) to * get the default ACL for a column. However, we don't @@ -17110,17 +16872,6 @@ dumpTable(Archive *fout, const TableInfo *tbinfo) "NOT at.attisdropped " "AND (at.attacl IS NOT NULL OR pip.initprivs IS NOT NULL) " "ORDER BY at.attnum"); - } - else - { - appendPQExpBufferStr(query, - "SELECT attname, attacl, '{}' AS acldefault, " - "NULL AS privtype, NULL AS initprivs " - "FROM pg_catalog.pg_attribute " - "WHERE attrelid = $1 AND NOT attisdropped " - "AND attacl IS NOT NULL " - "ORDER BY attnum"); - } ExecuteSqlStatement(fout, query->data); @@ -19410,16 +19161,10 @@ collectSequences(Archive *fout) const char *query; /* - * Before Postgres 10, sequence metadata is in the sequence itself. With - * some extra effort, we might be able to use the sorted table for those - * versions, but for now it seems unlikely to be worth it. - * * Since version 18, we can gather the sequence data in this query with * pg_get_sequence_data(), but we only do so for non-schema-only dumps. */ - if (fout->remoteVersion < 100000) - return; - else if (fout->remoteVersion < 180000 || + if (fout->remoteVersion < 180000 || (!fout->dopt->dumpData && !fout->dopt->sequence_data)) query = "SELECT seqrelid, format_type(seqtypid, NULL), " "seqstart, seqincrement, " @@ -19477,59 +19222,20 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) PQExpBuffer delqry = createPQExpBuffer(); char *qseqname; TableInfo *owning_tab = NULL; + SequenceItem key = {0}; qseqname = pg_strdup(fmtId(tbinfo->dobj.name)); /* - * For versions >= 10, the sequence information is gathered in a sorted + * The sequence information is gathered in a sorted * table before any calls to dumpSequence(). See collectSequences() for * more information. */ - if (fout->remoteVersion >= 100000) - { - SequenceItem key = {0}; - Assert(sequences); key.oid = tbinfo->dobj.catId.oid; seq = bsearch(&key, sequences, nsequences, sizeof(SequenceItem), SequenceItemCmp); - } - else - { - PGresult *res; - - /* - * Before PostgreSQL 10, sequence metadata is in the sequence itself. - * - * Note: it might seem that 'bigint' potentially needs to be - * schema-qualified, but actually that's a keyword. - */ - appendPQExpBuffer(query, - "SELECT 'bigint' AS sequence_type, " - "start_value, increment_by, max_value, min_value, " - "cache_value, is_cycled FROM %s", - fmtQualifiedDumpable(tbinfo)); - - res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); - - if (PQntuples(res) != 1) - pg_fatal(ngettext("query to get data of sequence \"%s\" returned %d row (expected 1)", - "query to get data of sequence \"%s\" returned %d rows (expected 1)", - PQntuples(res)), - tbinfo->dobj.name, PQntuples(res)); - - seq = pg_malloc0_object(SequenceItem); - seq->seqtype = parse_sequence_type(PQgetvalue(res, 0, 0)); - seq->startv = strtoi64(PQgetvalue(res, 0, 1), NULL, 10); - seq->incby = strtoi64(PQgetvalue(res, 0, 2), NULL, 10); - seq->maxv = strtoi64(PQgetvalue(res, 0, 3), NULL, 10); - seq->minv = strtoi64(PQgetvalue(res, 0, 4), NULL, 10); - seq->cache = strtoi64(PQgetvalue(res, 0, 5), NULL, 10); - seq->cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); - - PQclear(res); - } /* Calculate default limits for a sequence of this type */ is_ascending = (seq->incby >= 0); @@ -19708,8 +19414,6 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) tbinfo->dobj.namespace->dobj.name, tbinfo->rolname, tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId); - if (fout->remoteVersion < 100000) - pg_free(seq); destroyPQExpBuffer(query); destroyPQExpBuffer(delqry); free(qseqname); diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index b9653f0aefe..e68937c9934 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -785,17 +785,11 @@ dropRoles(PGconn *conn) int i_rolname; int i; - if (server_version >= 90600) printfPQExpBuffer(buf, "SELECT rolname " "FROM %s " "WHERE rolname !~ '^pg_' " "ORDER BY 1", role_catalog); - else - printfPQExpBuffer(buf, - "SELECT rolname " - "FROM %s " - "ORDER BY 1", role_catalog); res = executeQuery(conn, buf->data); @@ -849,7 +843,6 @@ dumpRoles(PGconn *conn) * Notes: rolconfig is dumped later, and pg_authid must be used for * extracting rolcomment regardless of role_catalog. */ - if (server_version >= 90600) printfPQExpBuffer(buf, "SELECT oid, rolname, rolsuper, rolinherit, " "rolcreaterole, rolcreatedb, " @@ -860,27 +853,6 @@ dumpRoles(PGconn *conn) "FROM %s " "WHERE rolname !~ '^pg_' " "ORDER BY 2", role_catalog); - else if (server_version >= 90500) - printfPQExpBuffer(buf, - "SELECT oid, rolname, rolsuper, rolinherit, " - "rolcreaterole, rolcreatedb, " - "rolcanlogin, rolconnlimit, rolpassword, " - "rolvaliduntil, rolreplication, rolbypassrls, " - "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, " - "rolname = current_user AS is_current_user " - "FROM %s " - "ORDER BY 2", role_catalog); - else - printfPQExpBuffer(buf, - "SELECT oid, rolname, rolsuper, rolinherit, " - "rolcreaterole, rolcreatedb, " - "rolcanlogin, rolconnlimit, rolpassword, " - "rolvaliduntil, rolreplication, " - "false as rolbypassrls, " - "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, " - "rolname = current_user AS is_current_user " - "FROM %s " - "ORDER BY 2", role_catalog); res = executeQuery(conn, buf->data); -- 2.50.1 (Apple Git-155) --aHl3HfeiUuO3zeNC Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v6-0002-pg_upgrade-bump-minimum-supported-version-to-v10.patch ^ permalink raw reply [nested|flat] 8+ messages in thread
end of thread, other threads:[~2026-04-17 17:15 UTC | newest] Thread overview: 8+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2019-07-09 09:54 [PATCH 4/5] Use only xlogreader.c:XLogRead() Antonin Houska <ah@cybertec.at> 2019-09-09 09:53 [PATCH 3/4] Use only xlogreader.c:XLogRead() Antonin Houska <ah@cybertec.at> 2019-09-09 09:53 [PATCH 3/4] Use only xlogreader.c:XLogRead() Antonin Houska <ah@cybertec.at> 2019-09-23 05:40 [PATCH 5/6] Use only xlogreader.c:XLogRead() Antonin Houska <ah@cybertec.at> 2019-09-23 05:40 [PATCH 5/6] Use only xlogreader.c:XLogRead() Antonin Houska <ah@cybertec.at> 2019-09-26 11:51 [PATCH 1/2] Use only xlogreader.c:XLogRead() Antonin Houska <ah@cybertec.at> 2019-10-04 10:07 [PATCH 1/2] Use only xlogreader.c:XLogRead() Antonin Houska <ah@cybertec.at> 2026-04-17 17:15 [PATCH v6 1/4] pg_dump/pg_dumpall: bump minimum supported version to v10 Nathan Bossart <nathan@postgresql.org>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox