0002-generic-xlog.11.patch

application/octet-stream

Filename: 0002-generic-xlog.11.patch
Type: application/octet-stream
Part: 1
Message: Re: WIP: Access method extendability

Patch

Format: unified
Series: patch 0002
File+
src/backend/access/rmgrdesc/genericdesc.c 0 0
src/backend/access/rmgrdesc/Makefile 0 0
src/backend/access/transam/generic_xlog.c 0 0
src/backend/access/transam/Makefile 0 0
src/backend/access/transam/rmgr.c 0 0
src/backend/replication/logical/decode.c 0 0
src/bin/pg_xlogdump/.gitignore 0 0
src/bin/pg_xlogdump/rmgrdesc.c 0 0
src/include/access/generic_xlog.h 0 0
src/include/access/rmgrlist.h 0 0
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
new file mode 100644
index c72a1f2..c0e38fd
*** a/src/backend/access/rmgrdesc/Makefile
--- b/src/backend/access/rmgrdesc/Makefile
*************** subdir = src/backend/access/rmgrdesc
*** 8,16 ****
  top_builddir = ../../../..
  include $(top_builddir)/src/Makefile.global
  
! OBJS = brindesc.o clogdesc.o committsdesc.o dbasedesc.o gindesc.o gistdesc.o \
! 	   hashdesc.o heapdesc.o mxactdesc.o nbtdesc.o relmapdesc.o \
! 	   replorigindesc.o seqdesc.o smgrdesc.o spgdesc.o \
  	   standbydesc.o tblspcdesc.o xactdesc.o xlogdesc.o
  
  include $(top_srcdir)/src/backend/common.mk
--- 8,16 ----
  top_builddir = ../../../..
  include $(top_builddir)/src/Makefile.global
  
! OBJS = brindesc.o clogdesc.o committsdesc.o dbasedesc.o genericdesc.o \
! 	   gindesc.o gistdesc.o hashdesc.o heapdesc.o mxactdesc.o nbtdesc.o \
! 	   relmapdesc.o replorigindesc.o seqdesc.o smgrdesc.o spgdesc.o \
  	   standbydesc.o tblspcdesc.o xactdesc.o xlogdesc.o
  
  include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/rmgrdesc/genericdesc.c b/src/backend/access/rmgrdesc/genericdesc.c
new file mode 100644
index ...3d035c2
*** a/src/backend/access/rmgrdesc/genericdesc.c
--- b/src/backend/access/rmgrdesc/genericdesc.c
***************
*** 0 ****
--- 1,58 ----
+ /*-------------------------------------------------------------------------
+  *
+  * genericdesc.c
+  *	  rmgr descriptor routines for access/transam/generic_xlog.c
+  *
+  *
+  * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * src/backend/access/rmgrdesc/genericdesc.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ 
+ #include "access/generic_xlog.h"
+ #include "lib/stringinfo.h"
+ #include "storage/relfilenode.h"
+ 
+ /*
+  * Description of generic xlog record: write page regions which this record
+  * overrides.
+  */
+ void
+ generic_desc(StringInfo buf, XLogReaderState *record)
+ {
+ 	Pointer		ptr = XLogRecGetData(record),
+ 				end = ptr + XLogRecGetDataLen(record);
+ 
+ 	while (ptr < end)
+ 	{
+ 		OffsetNumber	offset,
+ 						length;
+ 
+ 		memcpy(&offset, ptr, sizeof(offset));
+ 		ptr += sizeof(offset);
+ 		memcpy(&length, ptr, sizeof(length));
+ 		ptr += sizeof(length);
+ 		ptr += length;
+ 
+ 		if (ptr < end)
+ 			appendStringInfo(buf, "offset %u, length %u; ", offset, length);
+ 		else
+ 			appendStringInfo(buf, "offset %u, length %u", offset, length);
+ 	}
+ 
+ 	return;
+ }
+ 
+ /*
+  * Identification of generic xlog record: we don't distinguish any subtypes
+  * inside generic xlog records.
+  */
+ const char *
+ generic_identify(uint8 info)
+ {
+ 	return "Generic";
+ }
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
new file mode 100644
index 94455b2..16fbe47
*** a/src/backend/access/transam/Makefile
--- b/src/backend/access/transam/Makefile
*************** subdir = src/backend/access/transam
*** 12,19 ****
  top_builddir = ../../../..
  include $(top_builddir)/src/Makefile.global
  
! OBJS = clog.o commit_ts.o multixact.o parallel.o rmgr.o slru.o subtrans.o \
! 	timeline.o transam.o twophase.o twophase_rmgr.o varsup.o \
  	xact.o xlog.o xlogarchive.o xlogfuncs.o \
  	xloginsert.o xlogreader.o xlogutils.o
  
--- 12,19 ----
  top_builddir = ../../../..
  include $(top_builddir)/src/Makefile.global
  
! OBJS = clog.o commit_ts.o generic_xlog.o multixact.o parallel.o rmgr.o slru.o \
! 	subtrans.o timeline.o transam.o twophase.o twophase_rmgr.o varsup.o \
  	xact.o xlog.o xlogarchive.o xlogfuncs.o \
  	xloginsert.o xlogreader.o xlogutils.o
  
diff --git a/src/backend/access/transam/generic_xlog.c b/src/backend/access/transam/generic_xlog.c
new file mode 100644
index ...e6f26a5
*** a/src/backend/access/transam/generic_xlog.c
--- b/src/backend/access/transam/generic_xlog.c
***************
*** 0 ****
--- 1,508 ----
+ /*-------------------------------------------------------------------------
+  *
+  * generic_xlog.c
+  *	 Implementation of generic xlog records.
+  *
+  *
+  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * src/backend/access/transam/generic_xlog.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ 
+ #include "access/generic_xlog.h"
+ #include "access/xlogutils.h"
+ #include "miscadmin.h"
+ #include "utils/memutils.h"
+ 
+ /*-------------------------------------------------------------------------
+  * API for construction of generic xlog records
+  *
+  * This API allows user to construct generic xlog records which are
+  * describing difference between pages in general way.  Thus it's useful
+  * for extension which provides custom access methods because they couldn't
+  * register their own WAL redo routines.
+  *
+  * Generic xlog record should be constructed in following steps.
+  * 1) GenericXLogStart(relation) - start construction of generic xlog
+  *	  record for given relation.
+  * 2) GenericXLogRegister(buffer, isNew) - register one or more buffers
+  *	  for generic xlog record.  This function return a copy of page image
+  *	  where modifications should be performed.  The second argument
+  *	  indicates that block is new and full image should be taken.
+  * 3) Do modification of page images obtained in previous step.
+  * 4) GenericXLogFinish() - finish construction of generic xlog record.
+  *
+  * Please, note following points while constructing generic xlog records.
+  * - No direct modifications of page images are allowed! All modifications
+  *	 should be done in copies returned by GenericXLogRegister().  Literally
+  *	 code which makes generic xlog records should never call
+  *	 BufferGetPage() function.
+  * - On any step generic xlog record construction could be canceled by
+  *	 calling GenericXLogAbort().  All changes made in page images copies
+  *	 would be discarded.
+  * - Registrations of buffers (step 2) and modifications of page images
+  *	 (step 3) could be mixed in any sequence.  The only restriction is that
+  *	 you can modify page image only after registration of corresponding
+  *	 buffer.
+  * - After registration buffer also can be unregistered by calling
+  *	 GenericXLogUnregister(buffer).  In this case changes made in particular
+  *	 page image copy will be discarded.
+  * - Generic xlog assumes that pages are using standard layout.  I.e. all
+  *	 information between pd_lower and pd_upper will be discarded.
+  * - Maximum number of buffers simultaneously registered for generic xlog
+  *	 is MAX_GENERIC_XLOG_PAGES.  Error would be thrown if this limit
+  *	 exceeded.
+  * - Since you modify copies of page images, GenericXLogStart() doesn't
+  *	 start a critical section.  Thus, you can do memory allocation, error
+  *	 throwing etc between GenericXLogStart() and GenericXLogFinish().
+  *	 Actual critical section present inside GenericXLogFinish().
+  * - GenericXLogFinish() takes care about marking buffers dirty and setting
+  *	 their LSNs.  You don't need to do this explicitly.
+  * - For unlogged relations, everything work the same expect there is no
+  *	 WAL record produced.  Thus, you typically don't need to do any explicit
+  *	 checks for unlogged relations.
+  * - If registered buffer isn't new, generic xlog record contains delta
+  *	 between old and new page images.  This delta is produced by per byte
+  *	 comparison.  Current delta mechanist is not effective for data shift
+  *	 inside the page.  However, it could be improved in further versions.
+  * - Generic xlog redo function will acquire exclusive locks to buffers
+  *	 in the same order they were registered.  After redo of all changes
+  *	 locks would be released in the same order.  That could makes sense for
+  *	 concurrency.
+  *
+  * Internally delta between pages consists of set of fragments.  Each fragment
+  * represents changes made in given region of page.  Fragment is described
+  * as following.
+  *
+  * - offset of page region (OffsetNumber)
+  * - length of page region (OffsetNumber)
+  * - data - the data to place into described region ('length' number of bytes)
+  *
+  * Unchanged regions of page are uncovered by these fragments.  This is why
+  * delta could be more compact than full page image.  But if unchanged region
+  * of page is less than fragment header (offset and length) then it would
+  * increase size of delta instead of decreasing.  Thus, we break fragment only
+  * for unchanged regions greater than MATCH_THRESHOLD.
+  *
+  * The worst case for delta size is when we didn't find any unchanged region
+  * in the page. Then size of delta would be size of page plus size of fragment
+  * header.
+  */
+ #define FRAGMENT_HEADER_SIZE	(2 * sizeof(OffsetNumber))
+ #define MATCH_THRESHOLD			FRAGMENT_HEADER_SIZE
+ #define MAX_DELTA_SIZE			BLCKSZ + FRAGMENT_HEADER_SIZE
+ 
+ /* Struct of generic xlog data for single page */
+ typedef struct
+ {
+ 	Buffer	buffer;			/* registered buffer */
+ 	char	image[BLCKSZ];	/* copy of page image for modification */
+ 	char	data[MAX_DELTA_SIZE]; /* delta between page images */
+ 	int		dataLen;		/* space consumed in data field */
+ 	bool	fullImage;		/* are we taking full image of this page? */
+ } PageData;
+ 
+ /* Enum of generic xlog (gxlog) status */
+ enum GenericXlogStatus
+ {
+ 	GXLOG_NOT_STARTED,	/* gxlog is not started */
+ 	GXLOG_LOGGED,		/* gxlog is started for logged relation */
+ 	GXLOG_UNLOGGED		/* gxlog is started for unlogged relation */
+ };
+ 
+ static enum GenericXlogStatus	genericXlogStatus = GXLOG_NOT_STARTED;
+ static PageData					pages[MAX_GENERIC_XLOG_PAGES];
+ 
+ 
+ static void writeFragment(PageData *pageData, OffsetNumber offset,
+ 						  OffsetNumber len, Pointer data);
+ static void writeDelta(PageData *pageData);
+ static void applyPageRedo(Page page, Pointer data, Size dataSize);
+ 
+ /*
+  * Write next fragment into delta.
+  */
+ static void
+ writeFragment(PageData *pageData, OffsetNumber offset, OffsetNumber length,
+ 			  Pointer data)
+ {
+ 	Pointer			ptr = pageData->data + pageData->dataLen;
+ 
+ 	/* Check we have enough of space */
+ 	Assert(pageData->dataLen + sizeof(offset) +
+ 		   sizeof(length) + length <= sizeof(pageData->data));
+ 
+ 	/* Write fragment data */
+ 	memcpy(ptr, &offset, sizeof(offset));
+ 	ptr += sizeof(offset);
+ 	memcpy(ptr, &length, sizeof(length));
+ 	ptr += sizeof(length);
+ 	memcpy(ptr, data, length);
+ 	ptr += length;
+ 
+ 	pageData->dataLen = ptr - pageData->data;
+ }
+ 
+ /*
+  * Make delta for given page.
+  */
+ static void
+ writeDelta(PageData *pageData)
+ {
+ 	Page			page = BufferGetPage(pageData->buffer),
+ 					image = (Page) pageData->image;
+ 	int				i,
+ 					fragmentBegin = -1,
+ 					fragmentEnd = -1;
+ 	uint16			pageLower = ((PageHeader) page)->pd_lower,
+ 					pageUpper = ((PageHeader) page)->pd_upper,
+ 					imageLower = ((PageHeader) image)->pd_lower,
+ 					imageUpper = ((PageHeader) image)->pd_upper;
+ 
+ 	for (i = 0; i < BLCKSZ; i++)
+ 	{
+ 		bool	match;
+ 
+ 		/*
+ 		 * Check if bytes in old and new page images matches.  We don't rely
+ 		 * data in unallocated area between pd_lower and pd_upper.  Thus we
+ 		 * assume unallocated area to expand with unmatched bytes.  Bytes
+ 		 * inside unallocated area are assumed to always match.
+ 		 */
+ 		if (i < pageLower)
+ 		{
+ 			if (i < imageLower)
+ 				match = (page[i] == image[i]);
+ 			else
+ 				match = false;
+ 		}
+ 		else if (i >= pageUpper)
+ 		{
+ 			if (i >= imageUpper)
+ 				match = (page[i] == image[i]);
+ 			else
+ 				match = false;
+ 		}
+ 		else
+ 		{
+ 			match = true;
+ 		}
+ 
+ 		if (match)
+ 		{
+ 			if (fragmentBegin >= 0)
+ 			{
+ 				/* Matched byte is potential of fragment. */
+ 				if (fragmentEnd < 0)
+ 					fragmentEnd = i;
+ 
+ 				/*
+ 				 * Write next fragment if sequence of matched bytes is longer
+ 				 * than MATCH_THRESHOLD.
+ 				 */
+ 				if (i - fragmentEnd >= MATCH_THRESHOLD)
+ 				{
+ 					writeFragment(pageData, fragmentBegin,
+ 								  fragmentEnd - fragmentBegin,
+ 								  page + fragmentBegin);
+ 					fragmentBegin = -1;
+ 					fragmentEnd = -1;
+ 				}
+ 			}
+ 		}
+ 		else
+ 		{
+ 			/* On unmatched byte, start new fragment if it's not done yet */
+ 			if (fragmentBegin < 0)
+ 				fragmentBegin = i;
+ 			fragmentEnd = -1;
+ 		}
+ 	}
+ 
+ 	if (fragmentBegin >= 0)
+ 		writeFragment(pageData, fragmentBegin,
+ 					  BLCKSZ - fragmentBegin,
+ 					  page + fragmentBegin);
+ 
+ #ifdef WAL_DEBUG
+ 	/*
+ 	 * If xlog debug is enabled then check produced delta.  Result of delta
+ 	 * application to saved image should be the same as current page state.
+ 	 */
+ 	if (XLOG_DEBUG)
+ 	{
+ 		char	tmp[BLCKSZ];
+ 		memcpy(tmp, image, BLCKSZ);
+ 		applyPageRedo(tmp, pageData->data, pageData->dataLen);
+ 		elog(ERROR, "result of generic xlog apply doesn't match");
+ 	}
+ #endif
+ }
+ 
+ /*
+  * Start new generic xlog record.
+  */
+ void
+ GenericXLogStart(Relation relation)
+ {
+ 	int i;
+ 
+ 	if (genericXlogStatus != GXLOG_NOT_STARTED)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 				 errmsg("GenericXLogStart: generic xlog is already started")));
+ 
+ 	genericXlogStatus = RelationNeedsWAL(relation) ? GXLOG_LOGGED : GXLOG_UNLOGGED;
+ 
+ 	for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++)
+ 	{
+ 		pages[i].buffer = InvalidBuffer;
+ 	}
+ }
+ 
+ /*
+  * Register new buffer for generic xlog record.
+  */
+ Page
+ GenericXLogRegister(Buffer buffer, bool isNew)
+ {
+ 	int block_id;
+ 
+ 	if (genericXlogStatus == GXLOG_NOT_STARTED)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 				 errmsg("GenericXLogRegister: generic xlog isn't started")));
+ 
+ 	/* Place new buffer to unused slot in array */
+ 	for (block_id = 0; block_id < MAX_GENERIC_XLOG_PAGES; block_id++)
+ 	{
+ 		if (BufferIsInvalid(pages[block_id].buffer))
+ 		{
+ 			pages[block_id].buffer = buffer;
+ 			memcpy(pages[block_id].image, BufferGetPage(buffer), BLCKSZ);
+ 			pages[block_id].dataLen = 0;
+ 			pages[block_id].fullImage = isNew;
+ 			return (Page)pages[block_id].image;
+ 		}
+ 		else if (pages[block_id].buffer == buffer)
+ 		{
+ 			/* 
+ 			 * Buffer already registered.  Just return image which is already
+ 			 * prepared.
+ 			 */
+ 			return (Page)pages[block_id].image;
+ 		}
+ 	}
+ 
+ 	ereport(ERROR,
+ 			(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ 			 errmsg("GenericXLogRegister: maximum number of %d buffers is exceeded",
+ 					MAX_GENERIC_XLOG_PAGES)));
+ 
+ 	/* keep compiler quiet */
+ 	return NULL;
+ }
+ 
+ /*
+  * Unregister particular buffer for generic xlog record.
+  */
+ void
+ GenericXLogUnregister(Buffer buffer)
+ {
+ 	int block_id;
+ 
+ 	if (genericXlogStatus == GXLOG_NOT_STARTED)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 				 errmsg("GenericXLogUnregister: generic xlog isn't started")));
+ 
+ 	/* Find block in array to unregister */
+ 	for (block_id = 0; block_id < MAX_GENERIC_XLOG_PAGES; block_id++)
+ 	{
+ 		if (pages[block_id].buffer == buffer)
+ 		{
+ 			/*
+ 			 * Preserve order of pages in array because it could matter for
+ 			 * concurrency.
+ 			 */
+ 			memmove(&pages[block_id], &pages[block_id + 1],
+ 					(MAX_GENERIC_XLOG_PAGES - block_id - 1) * sizeof(PageData));
+ 			pages[MAX_GENERIC_XLOG_PAGES - 1].buffer = InvalidBuffer;
+ 			return;
+ 		}
+ 	}
+ 
+ 	ereport(ERROR,
+ 			(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 			 errmsg("GenericXLogUnregister: registered buffer not found")));
+ }
+ 
+ /*
+  * Put all changes in registered buffers to generic xlog record.
+  */
+ XLogRecPtr
+ GenericXLogFinish(void)
+ {
+ 	XLogRecPtr lsn = InvalidXLogRecPtr;
+ 	int i;
+ 
+ 	if (genericXlogStatus == GXLOG_LOGGED)
+ 	{
+ 		/* Logged relation: make xlog record in critical section. */
+ 		START_CRIT_SECTION();
+ 		XLogBeginInsert();
+ 
+ 		for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++)
+ 		{
+ 			char	tmp[BLCKSZ];
+ 
+ 			if (BufferIsInvalid(pages[i].buffer))
+ 				continue;
+ 
+ 			/* Swap current and saved page image. */
+ 			memcpy(tmp, pages[i].image, BLCKSZ);
+ 			memcpy(pages[i].image, BufferGetPage(pages[i].buffer), BLCKSZ);
+ 			memcpy(BufferGetPage(pages[i].buffer), tmp, BLCKSZ);
+ 
+ 			if (pages[i].fullImage)
+ 			{
+ 				/* Full page image doesn't require anything special from us */
+ 				XLogRegisterBuffer(i, pages[i].buffer, REGBUF_FORCE_IMAGE);
+ 			}
+ 			else
+ 			{
+ 				/*
+ 				 * In normal node calculate delta and write use it as data
+ 				 * associated with this page.
+ 				 */
+ 				XLogRegisterBuffer(i, pages[i].buffer, REGBUF_STANDARD);
+ 				writeDelta(&pages[i]);
+ 				XLogRegisterBufData(i, pages[i].data, pages[i].dataLen);
+ 			}
+ 		}
+ 
+ 		/* Insert xlog record */
+ 		lsn = XLogInsert(RM_GENERIC_ID, 0);
+ 
+ 		/* Set LSN and make buffers dirty */
+ 		for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++)
+ 		{
+ 			if (BufferIsInvalid(pages[i].buffer))
+ 				continue;
+ 			PageSetLSN(BufferGetPage(pages[i].buffer), lsn);
+ 			MarkBufferDirty(pages[i].buffer);
+ 		}
+ 		END_CRIT_SECTION();
+ 	}
+ 	else if (genericXlogStatus == GXLOG_UNLOGGED)
+ 	{
+ 		/* Unlogged relation: skip xlog-related stuff */
+ 		START_CRIT_SECTION();
+ 		for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++)
+ 		{
+ 			if (BufferIsInvalid(pages[i].buffer))
+ 				continue;
+ 			memcpy(BufferGetPage(pages[i].buffer), pages[i].image, BLCKSZ);
+ 			MarkBufferDirty(pages[i].buffer);
+ 		}
+ 		END_CRIT_SECTION();
+ 	}
+ 	else
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 				 errmsg("GenericXLogFinish: generic xlog isn't started")));
+ 	}
+ 
+ 	genericXlogStatus = GXLOG_NOT_STARTED;
+ 
+ 	return lsn;
+ }
+ 
+ /*
+  * Abort generic xlog record.
+  */
+ void
+ GenericXLogAbort(void)
+ {
+ 	if (genericXlogStatus == GXLOG_NOT_STARTED)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 				 errmsg("GenericXLogAbort: generic xlog isn't started")));
+ 
+ 	genericXlogStatus = GXLOG_NOT_STARTED;
+ }
+ 
+ /*
+  * Apply delta to given page image.
+  */
+ static void
+ applyPageRedo(Page page, Pointer data, Size dataSize)
+ {
+ 	Pointer ptr = data, end = data + dataSize;
+ 
+ 	while (ptr < end)
+ 	{
+ 		OffsetNumber	offset,
+ 						length;
+ 
+ 		memcpy(&offset, ptr, sizeof(offset));
+ 		ptr += sizeof(offset);
+ 		memcpy(&length, ptr, sizeof(length));
+ 		ptr += sizeof(length);
+ 
+ 		memcpy(page + offset, ptr, length);
+ 
+ 		ptr += length;
+ 	}
+ }
+ 
+ /*
+  * Redo function for generic xlog record.
+  */
+ void
+ generic_redo(XLogReaderState *record)
+ {
+ 	uint8		block_id;
+ 	Buffer		buffers[MAX_GENERIC_XLOG_PAGES] = {InvalidBuffer};
+ 	XLogRecPtr	lsn = record->EndRecPtr;
+ 
+ 	Assert(record->max_block_id < MAX_GENERIC_XLOG_PAGES);
+ 
+ 	/* Interate over blocks */
+ 	for (block_id = 0; block_id <= record->max_block_id; block_id++)
+ 	{
+ 		XLogRedoAction action;
+ 
+ 		if (!XLogRecHasBlockRef(record, block_id))
+ 			continue;
+ 
+ 		action = XLogReadBufferForRedo(record, block_id, &buffers[block_id]);
+ 
+ 		/* Apply redo to given block if needed */
+ 		if (action == BLK_NEEDS_REDO)
+ 		{
+ 			Pointer	blockData;
+ 			Size	blockDataSize;
+ 			Page	page;
+ 
+ 			page = BufferGetPage(buffers[block_id]);
+ 			blockData = XLogRecGetBlockData(record, block_id, &blockDataSize);
+ 			applyPageRedo(page, blockData, blockDataSize);
+ 
+ 			PageSetLSN(page, lsn);
+ 			MarkBufferDirty(buffers[block_id]);
+ 		}
+ 	}
+ 
+ 	/* Changes are done: unlock and release all buffers */
+ 	for (block_id = 0; block_id <= record->max_block_id; block_id++)
+ 	{
+ 		if (BufferIsValid(buffers[block_id]))
+ 			UnlockReleaseBuffer(buffers[block_id]);
+ 	}
+ }
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
new file mode 100644
index 7c4d773..7b38c16
*** a/src/backend/access/transam/rmgr.c
--- b/src/backend/access/transam/rmgr.c
***************
*** 11,16 ****
--- 11,17 ----
  #include "access/commit_ts.h"
  #include "access/gin.h"
  #include "access/gist_private.h"
+ #include "access/generic_xlog.h"
  #include "access/hash.h"
  #include "access/heapam_xlog.h"
  #include "access/brin_xlog.h"
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
new file mode 100644
index 13af485..262deb2
*** a/src/backend/replication/logical/decode.c
--- b/src/backend/replication/logical/decode.c
*************** LogicalDecodingProcessRecord(LogicalDeco
*** 143,148 ****
--- 143,149 ----
  		case RM_BRIN_ID:
  		case RM_COMMIT_TS_ID:
  		case RM_REPLORIGIN_ID:
+ 		case RM_GENERIC_ID:
  			/* just deal with xid, and done */
  			ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(record),
  									buf.origptr);
diff --git a/src/bin/pg_xlogdump/.gitignore b/src/bin/pg_xlogdump/.gitignore
new file mode 100644
index eebaf30..33a1acf
*** a/src/bin/pg_xlogdump/.gitignore
--- b/src/bin/pg_xlogdump/.gitignore
***************
*** 4,9 ****
--- 4,10 ----
  /clogdesc.c
  /committsdesc.c
  /dbasedesc.c
+ /genericdesc.c
  /gindesc.c
  /gistdesc.c
  /hashdesc.c
diff --git a/src/bin/pg_xlogdump/rmgrdesc.c b/src/bin/pg_xlogdump/rmgrdesc.c
new file mode 100644
index f9cd395..cff7e59
*** a/src/bin/pg_xlogdump/rmgrdesc.c
--- b/src/bin/pg_xlogdump/rmgrdesc.c
***************
*** 11,16 ****
--- 11,17 ----
  #include "access/brin_xlog.h"
  #include "access/clog.h"
  #include "access/commit_ts.h"
+ #include "access/generic_xlog.h"
  #include "access/gin.h"
  #include "access/gist_private.h"
  #include "access/hash.h"
diff --git a/src/include/access/generic_xlog.h b/src/include/access/generic_xlog.h
new file mode 100644
index ...49249e0
*** a/src/include/access/generic_xlog.h
--- b/src/include/access/generic_xlog.h
***************
*** 0 ****
--- 1,36 ----
+ /*-------------------------------------------------------------------------
+  *
+  * generic_xlog.h
+  *	  Generic xlog API definition.
+  *
+  *
+  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * src/include/access/generic_xlog.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef GENERIC_XLOG_H
+ #define GENERIC_XLOG_H
+ 
+ #include "access/xlog.h"
+ #include "access/xlog_internal.h"
+ #include "storage/bufpage.h"
+ #include "utils/rel.h"
+ 
+ #define MAX_GENERIC_XLOG_PAGES	  3
+ 
+ /* API for construction of generic xlog records */
+ extern void GenericXLogStart(Relation relation);
+ extern Page GenericXLogRegister(Buffer buffer, bool isNew);
+ extern void GenericXLogUnregister(Buffer buffer);
+ extern XLogRecPtr GenericXLogFinish(void);
+ extern void GenericXLogAbort(void);
+ 
+ /* functions defined for rmgr */
+ extern void generic_redo(XLogReaderState *record);
+ extern const char *generic_identify(uint8 info);
+ extern void generic_desc(StringInfo buf, XLogReaderState *record);
+ 
+ #endif   /* GENERIC_XLOG_H */
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
new file mode 100644
index fab912d..3cfe6f7
*** a/src/include/access/rmgrlist.h
--- b/src/include/access/rmgrlist.h
*************** PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo
*** 45,47 ****
--- 45,48 ----
  PG_RMGR(RM_BRIN_ID, "BRIN", brin_redo, brin_desc, brin_identify, NULL, NULL)
  PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL)
  PG_RMGR(RM_REPLORIGIN_ID, "ReplicationOrigin", replorigin_redo, replorigin_desc, replorigin_identify, NULL, NULL)
+ PG_RMGR(RM_GENERIC_ID, "Generic", generic_redo, generic_desc, generic_identify, NULL, NULL)