PostgreSQL源码分析—WAL日志读取函数ReadPageInternal
我们使用test_decoding对ReadPageInternal()函数代码进行探究,代码选自pg12,xlogreader.c
```c
/*
* Read a single xlog page including at least [pageptr, reqLen] of valid data
* via the read_page() callback.
*
* Returns -1 if the required page cannot be read for some reason; errormsg_buf
* is set in that case (unless the error occurs in the read_page callback).
*
* We fetch the page from a reader-local cache if we know we have the required
* data and if there hasn't been any error since caching the data.
*/
static int
ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen)
{
int readLen;
uint32 targetPageOff;
XLogSegNo targetSegNo;
XLogPageHeader hdr;
Assert((pageptr % XLOG_BLCKSZ) == 0);
XLByteToSeg(pageptr, targetSegNo, state->wal_segment_size);
targetPageOff = XLogSegmentOffset(pageptr, state->wal_segment_size);
/* check whether we have all the requested data already */
if (targetSegNo == state->readSegNo && targetPageOff == state->readOff &&
reqLen <= state->readLen)
return state->readLen;
/*
* Data is not in our buffer.
*
* Every time we actually read the page, even if we looked at parts of it
* before, we need to do verification as the read_page callback might now
* be rereading data from a different source.
*
* Whenever switching to a new WAL segment, we read the first page of the
* file and validate its header, even if that's not where the target
* record is. This is so that we can check the additional identification
* info that is present in the first page's "long" header.
*/
if (targetSegNo != state->readSegNo && targetPageOff != 0)
{
XLogRecPtr targetSegmentPtr = pageptr - targetPageOff;
readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ,
state->currRecPtr,
state->readBuf, &state->readPageTLI);
if (readLen < 0)
goto err;
/* we can be sure to have enough WAL available, we scrolled back */
Assert(readLen == XLOG_BLCKSZ);
if (!XLogReaderValidatePageHeader(state, targetSegmentPtr,
state->readBuf))
goto err;
}
/*
* First, read the requested data length, but at least a short page header
* so that we can validate it.
*/
readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD),
state->currRecPtr,
state->readBuf, &state->readPageTLI);
if (readLen < 0)
goto err;
Assert(readLen <= XLOG_BLCKSZ);
/* Do we have enough data to check the header length? */
if (readLen <= SizeOfXLogShortPHD)
goto err;
Assert(readLen >= reqLen);
hdr = (XLogPageHeader) state->readBuf;
/* still not enough */
if (readLen < XLogPageHeaderSize(hdr))
{
readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr),
state->currRecPtr,
state->readBuf, &state->readPageTLI);
if (readLen < 0)
goto err;
}
/*
* Now that we know we have the full header, validate it.
*/
if (!XLogReaderValidatePageHeader(state, pageptr, (char *) hdr))
goto err;
/* update read state information */
state->readSegNo = targetSegNo;
state->readOff = targetPageOff;
state->readLen = readLen;
return readLen;
err:
XLogReaderInvalReadState(state);
return -1;
}
ReadPageInternal()函数通过read_page()回调函数读取包含至少[pageptr, reqLen]有效数据的单个xlog页面。 如果由于某种原因无法读取所需页面,则返回-1;在这种情况下会设置errormsg_buf(除非错误发生在read_page回调中)。 如果我们知道我们有所需的数据,并且自从缓存这些数据以来没有发生任何错误,那么我们会从读取器本地的缓存中获取该页面。
这里read_page指向logical_read_local_xlog_page()函数
1、首先检查我们是否已经有了所有请求的数据
if (targetSegNo == state->readSegNo && targetPageOff == state->readOff &&
reqLen <= state->readLen)
2、若数据不在我们的缓冲区中。每次我们实际读取页面时,即使我们之前已经查看过其中的部分数据,我们仍需要进行验证,因为read_page回调函数可能现在正在从不同的源重新读取数据。每当切换到新的WAL段时,我们会读取文件的第一个页面并验证其头部,即使目标记录并不在那里。这样做是为了检查第一个页面的“长”头部中包含的附加识别信息。
if (targetSegNo != state->readSegNo && targetPageOff != 0)
{
XLogRecPtr targetSegmentPtr = pageptr - targetPageOff;
readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ,
state->currRecPtr,
state->readBuf, &state->readPageTLI);
if (readLen < 0)
goto err;
/* we can be sure to have enough WAL available, we scrolled back */
Assert(readLen == XLOG_BLCKSZ);
if (!XLogReaderValidatePageHeader(state, targetSegmentPtr,
state->readBuf))
goto err;
}
3、之后读取请求的数据长度,但至少读取一个短的页面头部,方便我们严重,这样可以确保数据的完整性和准确性
readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD),
state->currRecPtr,
state->readBuf, &state->readPageTLI);
if (readLen < 0)
goto err;
Assert(readLen <= XLOG_BLCKSZ);
4、判断我们是否有足够的数据来检查头部信息
if (readLen <= SizeOfXLogShortPHD)
goto err;
5、数据不够的话,继续读取数据
if (readLen < XLogPageHeaderSize(hdr))
{
readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr),
state->currRecPtr,
state->readBuf, &state->readPageTLI);
if (readLen < 0)
goto err;
}
6、有完整的数据页头部信息后,对其进行验证
if (!XLogReaderValidatePageHeader(state, pageptr, (char *) hdr))
goto err;
7、更新XLogReaderState的信息
state->readSegNo = targetSegNo;
state->readOff = targetPageOff;
state->readLen = readLen;