mirror of
https://github.com/postgrespro/pg_probackup.git
synced 2025-01-09 14:45:47 +02:00
04834e73c7
This commit improves the performance of page-level, or differential backup, by not having to scan anymore all the pages of a relation file, something that can be very long on large data sets, but by scanning the list of blocks changed by WAL records since the last full or differential backup. As a restriction and to avoid potential data corruption should hint-bit updates occur on a page, backups can only be taken from a server that has wal_log_hints or data checksums enabled. Base patch by Yury Zhuravlev, heavily modified by me.
245 lines
6.1 KiB
C
245 lines
6.1 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* parsexlog.c
|
|
* Functions for reading Write-Ahead-Log
|
|
*
|
|
* Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres_fe.h"
|
|
|
|
#include "pg_arman.h"
|
|
|
|
#include <unistd.h>
|
|
|
|
#include "commands/dbcommands_xlog.h"
|
|
#include "catalog/storage_xlog.h"
|
|
|
|
/*
|
|
* RmgrNames is an array of resource manager names, to make error messages
|
|
* a bit nicer.
|
|
*/
|
|
#define PG_RMGR(symname,name,redo,desc,identify,startup,cleanup) \
|
|
name,
|
|
|
|
static const char *RmgrNames[RM_MAX_ID + 1] = {
|
|
#include "access/rmgrlist.h"
|
|
};
|
|
|
|
static void extractPageInfo(XLogReaderState *record);
|
|
|
|
static int xlogreadfd = -1;
|
|
static XLogSegNo xlogreadsegno = -1;
|
|
static char xlogfpath[MAXPGPATH];
|
|
|
|
typedef struct XLogPageReadPrivate
|
|
{
|
|
const char *archivedir;
|
|
TimeLineID tli;
|
|
} XLogPageReadPrivate;
|
|
|
|
static int SimpleXLogPageRead(XLogReaderState *xlogreader,
|
|
XLogRecPtr targetPagePtr,
|
|
int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
|
|
TimeLineID *pageTLI);
|
|
|
|
/*
|
|
* Read WAL from the archive directory, starting from 'startpoint' on the
|
|
* given timeline, until 'endpoint'. Make note of the data blocks touched
|
|
* by the WAL records, and return them in a page map.
|
|
*/
|
|
void
|
|
extractPageMap(const char *archivedir, XLogRecPtr startpoint, TimeLineID tli,
|
|
XLogRecPtr endpoint)
|
|
{
|
|
XLogRecord *record;
|
|
XLogReaderState *xlogreader;
|
|
char *errormsg;
|
|
XLogPageReadPrivate private;
|
|
|
|
private.archivedir = archivedir;
|
|
private.tli = tli;
|
|
xlogreader = XLogReaderAllocate(&SimpleXLogPageRead, &private);
|
|
if (xlogreader == NULL)
|
|
elog(ERROR, "out of memory");
|
|
|
|
do
|
|
{
|
|
record = XLogReadRecord(xlogreader, startpoint, &errormsg);
|
|
if (record == NULL)
|
|
{
|
|
XLogRecPtr errptr;
|
|
|
|
errptr = startpoint ? startpoint : xlogreader->EndRecPtr;
|
|
|
|
if (errormsg)
|
|
elog(ERROR, "could not read WAL record at %X/%X: %s",
|
|
(uint32) (errptr >> 32), (uint32) (errptr),
|
|
errormsg);
|
|
else
|
|
elog(ERROR, "could not read WAL record at %X/%X",
|
|
(uint32) (startpoint >> 32),
|
|
(uint32) (startpoint));
|
|
}
|
|
|
|
extractPageInfo(xlogreader);
|
|
|
|
startpoint = InvalidXLogRecPtr; /* continue reading at next record */
|
|
|
|
} while (xlogreader->ReadRecPtr != endpoint);
|
|
|
|
XLogReaderFree(xlogreader);
|
|
if (xlogreadfd != -1)
|
|
{
|
|
close(xlogreadfd);
|
|
xlogreadfd = -1;
|
|
}
|
|
}
|
|
|
|
/* XLogreader callback function, to read a WAL page */
|
|
static int
|
|
SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
|
|
int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
|
|
TimeLineID *pageTLI)
|
|
{
|
|
XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data;
|
|
uint32 targetPageOff;
|
|
XLogRecPtr targetSegEnd;
|
|
XLogSegNo targetSegNo;
|
|
|
|
XLByteToSeg(targetPagePtr, targetSegNo);
|
|
XLogSegNoOffsetToRecPtr(targetSegNo + 1, 0, targetSegEnd);
|
|
targetPageOff = targetPagePtr % XLogSegSize;
|
|
|
|
/*
|
|
* See if we need to switch to a new segment because the requested record
|
|
* is not in the currently open one.
|
|
*/
|
|
if (xlogreadfd >= 0 && !XLByteInSeg(targetPagePtr, xlogreadsegno))
|
|
{
|
|
close(xlogreadfd);
|
|
xlogreadfd = -1;
|
|
}
|
|
|
|
XLByteToSeg(targetPagePtr, xlogreadsegno);
|
|
|
|
if (xlogreadfd < 0)
|
|
{
|
|
char xlogfname[MAXFNAMELEN];
|
|
|
|
XLogFileName(xlogfname, private->tli, xlogreadsegno);
|
|
snprintf(xlogfpath, MAXPGPATH, "%s/%s", private->archivedir,
|
|
xlogfname);
|
|
elog(LOG, "opening WAL segment \"%s\"", xlogfpath);
|
|
|
|
xlogreadfd = open(xlogfpath, O_RDONLY | PG_BINARY, 0);
|
|
|
|
if (xlogreadfd < 0)
|
|
{
|
|
elog(WARNING, "could not open WAL segment \"%s\": %s",
|
|
xlogfpath, strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* At this point, we have the right segment open.
|
|
*/
|
|
Assert(xlogreadfd != -1);
|
|
|
|
/* Read the requested page */
|
|
if (lseek(xlogreadfd, (off_t) targetPageOff, SEEK_SET) < 0)
|
|
{
|
|
elog(WARNING, "could not seek in file \"%s\": %s", xlogfpath,
|
|
strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if (read(xlogreadfd, readBuf, XLOG_BLCKSZ) != XLOG_BLCKSZ)
|
|
{
|
|
elog(WARNING, "could not read from file \"%s\": %s",
|
|
xlogfpath, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
Assert(targetSegNo == xlogreadsegno);
|
|
|
|
*pageTLI = private->tli;
|
|
return XLOG_BLCKSZ;
|
|
}
|
|
|
|
/*
|
|
* Extract information on which blocks the current record modifies.
|
|
*/
|
|
static void
|
|
extractPageInfo(XLogReaderState *record)
|
|
{
|
|
int block_id;
|
|
RmgrId rmid = XLogRecGetRmid(record);
|
|
uint8 info = XLogRecGetInfo(record);
|
|
uint8 rminfo = info & ~XLR_INFO_MASK;
|
|
|
|
/* Is this a special record type that I recognize? */
|
|
|
|
if (rmid == RM_DBASE_ID && rminfo == XLOG_DBASE_CREATE)
|
|
{
|
|
/*
|
|
* New databases can be safely ignored. They would be completely
|
|
* copied if found.
|
|
*/
|
|
}
|
|
else if (rmid == RM_DBASE_ID && rminfo == XLOG_DBASE_DROP)
|
|
{
|
|
/*
|
|
* An existing database was dropped. It is fine to ignore that
|
|
* they will be removed appropriately.
|
|
*/
|
|
}
|
|
else if (rmid == RM_SMGR_ID && rminfo == XLOG_SMGR_CREATE)
|
|
{
|
|
/*
|
|
* We can safely ignore these. The file will be removed when
|
|
* combining the backups in the case of differential on.
|
|
*/
|
|
}
|
|
else if (rmid == RM_SMGR_ID && rminfo == XLOG_SMGR_TRUNCATE)
|
|
{
|
|
/*
|
|
* We can safely ignore these. When we compare the sizes later on,
|
|
* we'll notice that they differ, and copy the missing tail from
|
|
* source system.
|
|
*/
|
|
}
|
|
else if (info & XLR_SPECIAL_REL_UPDATE)
|
|
{
|
|
/*
|
|
* This record type modifies a relation file in some special way, but
|
|
* we don't recognize the type. That's bad - we don't know how to
|
|
* track that change.
|
|
*/
|
|
elog(ERROR, "WAL record modifies a relation, but record type is not recognized\n"
|
|
"lsn: %X/%X, rmgr: %s, info: %02X",
|
|
(uint32) (record->ReadRecPtr >> 32), (uint32) (record->ReadRecPtr),
|
|
RmgrNames[rmid], info);
|
|
}
|
|
|
|
for (block_id = 0; block_id <= record->max_block_id; block_id++)
|
|
{
|
|
RelFileNode rnode;
|
|
ForkNumber forknum;
|
|
BlockNumber blkno;
|
|
|
|
if (!XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blkno))
|
|
continue;
|
|
|
|
/* We only care about the main fork; others are copied in toto */
|
|
if (forknum != MAIN_FORKNUM)
|
|
continue;
|
|
|
|
process_block_change(forknum, rnode, blkno);
|
|
}
|
|
}
|