diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
new file mode 100644
index 30adece..9046f50
*** a/doc/src/sgml/filelist.sgml
--- b/doc/src/sgml/filelist.sgml
***************
*** 100,105 ****
--- 100,106 ----
  <!ENTITY sources    SYSTEM "sources.sgml">
  <!ENTITY storage    SYSTEM "storage.sgml">
  <!ENTITY tablesample-method SYSTEM "tablesample-method.sgml">
+ <!ENTITY generic-wal SYSTEM "generic-wal.sgml">
  
  <!-- contrib information -->
  <!ENTITY contrib         SYSTEM "contrib.sgml">
diff --git a/doc/src/sgml/generic-wal.sgml b/doc/src/sgml/generic-wal.sgml
new file mode 100644
index ...a00c03c
*** a/doc/src/sgml/generic-wal.sgml
--- b/doc/src/sgml/generic-wal.sgml
***************
*** 0 ****
--- 1,139 ----
+ <!-- doc/src/sgml/generic-wal.sgml -->
+ 
+ <chapter id="generic-wal">
+  <title>Generic WAL records</title>
+ 
+   <para>
+    Despite all built in access methods and WAL-logged modules have their own
+    types of WAL records, there is also generic WAL record type which describes
+    changes to pages in a generic way.  This is useful for extensions that
+    provide custom access methods, because they cannot register their own
+    WAL redo routines.
+   </para>
+ 
+   <para>
+    API for contructing generic WAL records is defined in
+    <filename>generic_xlog.h</> and implemented in <filename>generic_xlog.c</>.
+    Each generic WAL record must be constructed by following these steps:
+ 
+    <orderedlist>
+     <listitem>
+      <para>
+       <function>GenericXLogStart(relation)</> - start construction of a generic xlog
+       record for the given relation.
+      </para>
+     </listitem>
+ 
+     <listitem>
+      <para>
+       <function>GenericXLogRegister(buffer, isNew)</> - register one or more buffers
+       (one at a time) for the current generic xlog record.  This function
+       returns a copy of the page image, where modifications can be made.
+       The second argument indicates if the page is new (eventually, this
+       will result in a full page image being put into the xlog record).
+      </para>
+     </listitem>
+ 
+     <listitem>
+      <para>
+       Apply modifications to page images obtained in the previous step.
+      </para>
+     </listitem>
+ 
+     <listitem>
+      <para>
+       <function>GenericXLogFinish()</> - finish construction of a generic xlog record.
+      </para>
+     </listitem>
+    </orderedlist>
+   </para>
+ 
+   <para>
+    The xlog record construction can be canceled between any of the above
+    steps by calling <function>GenericXLogAbort()</>.  This will discard all
+    changes to the page image copies.
+   </para>
+ 
+   <para>
+    Please note the following points when constructing generic xlog records:
+    <itemizedlist>
+     <listitem>
+      <para>
+       No direct modifications of page images are allowed!  All modifications
+       must be done in copies acquired from <function>GenericXLogRegister()</>.
+       In other words, code which makes generic xlog records must never call
+       <function>BufferGetPage()</>.
+      </para>
+     </listitem>
+ 
+     <listitem>
+      <para>
+       Registrations of buffers (step 2) and modifications of page images
+       (step 3) can be mixed freely, i.e., both steps may be repeated in any
+       sequence.  The only restriction is that you can modify a page image
+       only after the registration of the corresponding buffer.
+      </para>
+     </listitem>
+ 
+     <listitem>
+      <para>
+       After registration, the buffer can also be unregistered by calling
+       <function>GenericXLogUnregister(buffer)</>.  In this case, the changes
+       made to that particular page image copy will be discarded.
+      </para>
+     </listitem>
+ 
+     <listitem>
+      <para>
+       Generic xlog assumes that pages are using standard layout.  I.e., all
+       information between pd_lower and pd_upper will be discarded.
+      </para>
+     </listitem>
+ 
+     <listitem>
+      <para>
+       The maximum number of buffers that can be simultaneously registered
+       for a generic xlog is <literal>MAX_GENERIC_XLOG_PAGES</>.  An error will
+       be thrown if this limit is exceeded.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Since you modify copies of page images, <function>GenericXLogStart()</>
+       does not start a critical section.  Thus, you can do memory allocation,
+       error throwing, etc. between <function>GenericXLogStart()</> and
+       <function>GenericXLogFinish()</>.  The actual critical section is present
+       inside <function>GenericXLogFinish()</>.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <function>GenericXLogFinish()</> takes care of marking buffers as dirty
+       and setting their LSNs.  You do not need to do this explicitly.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       For unlogged relations, everything works the same except there is no
+       WAL record produced.  Thus, you typically do not need to do any explicit
+       checks for unlogged relations.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       If a registered buffer is not new, the generic xlog record contains
+       a delta between the old and the new page images.  This delta is produced
+       using per byte comparison.  The current delta mechanism is not effective
+       for moving data within a page and may be improved in the future.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       The generic xlog redo function will acquire exclusive locks to buffers
+       in the same order as they were registered.  After redoing all changes,
+       the locks will be released in the same order.
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+ </chapter>
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
new file mode 100644
index 7e82cdc..0346d36
*** a/doc/src/sgml/postgres.sgml
--- b/doc/src/sgml/postgres.sgml
***************
*** 247,252 ****
--- 247,253 ----
    &custom-scan;
    &geqo;
    &indexam;
+   &generic-wal;
    &gist;
    &spgist;
    &gin;
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 ...caa9a03
*** 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 that 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 ...32c2648
*** a/src/backend/access/transam/generic_xlog.c
--- b/src/backend/access/transam/generic_xlog.c
***************
*** 0 ****
--- 1,457 ----
+ /*-------------------------------------------------------------------------
+  *
+  * 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"
+ 
+ /*-------------------------------------------------------------------------
+  * Internally, a delta between pages consists of set of fragments.  Each
+  * fragment represents changes made in a given region of a page.  A fragment
+  * is made up as follows:
+  *
+  * - offset of page region (OffsetNumber)
+  * - length of page region (OffsetNumber)
+  * - data - the data to place into the region ('length' number of bytes)
+  *
+  * Unchanged regions of a page are not represented in its delta.  As a
+  * result, a delta can be more compact than the full page image.  But having
+  * an unchanged region in the middle to two fragments that is smaller than
+  * the fragment header (offset and length) does not pay off in terms of the
+  * overall size of the delta. For this reason, we break fragments only if
+  * the unchanged region is bigger than MATCH_THRESHOLD.
+  *
+  * The worst case for delta sizes occurs when we did not find any unchanged
+  * region in the page.  The size of the delta will be the size of the page plus
+  * the size of the fragment header in that case.
+  *-------------------------------------------------------------------------
+  */
+ #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 a 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 if we have enough 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 match.  We do not care
+ 		 * about data in the unallocated area between pd_lower and pd_upper.
+ 		 * We assume the unallocated area to expand with unmatched bytes.
+ 		 * Bytes inside the 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 potentially part of a 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 is 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);
+ 		if (memcmp(tmp, page, pageLower)
+ 			|| memcmp(tmp + pageUpper, page + pageUpper, BLCKSZ - pageUpper))
+ 			elog(ERROR, "result of generic xlog apply does not 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 is not 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 is already registered.  Just return the 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 is not 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)
+ 			{
+ 				/* A full page image does not require anything special */
+ 				XLogRegisterBuffer(i, pages[i].buffer, REGBUF_FORCE_IMAGE);
+ 			}
+ 			else
+ 			{
+ 				/*
+ 				 * In normal mode, calculate delta and write 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 mark 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 is not 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 is not 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)
