Custom WAL Resource Managers
This chapter explains the interface between the core QHB system and custom WAL resource managers, which enable extensions to integrate directly with the WAL.
An extension, especially a Table Access Method or Index Access Method, may need to use WAL for recovery, replication, and/or Logical Decoding. Custom resource managers are a more flexible alternative to Generic WAL (which does not support logical decoding), but more complex for an extension to implement.
To create a new custom WAL resource manager, first define an RmgrData structure with implementations for the resource manager methods.
/*
* Method table for resource managers.
*
* This struct must be kept in sync with the PG_RMGR definition in
* rmgr.c.
*
* rm_identify must return a name for the record based on xl_info (without
* reference to the rmid). For example, XLOG_BTREE_VACUUM would be named
* "VACUUM". rm_desc can then be called to obtain additional detail for the
* record, if available (e.g. the last block).
*
* rm_mask takes as input a page modified by the resource manager and masks
* out bits that shouldn't be flagged by wal_consistency_checking.
*
* RmgrTable[] is indexed by RmgrId values (see rmgrlist.h). If rm_name is
* NULL, the corresponding RmgrTable entry is considered invalid.
*/
typedef struct RmgrData
{
const char *rm_name;
void (*rm_redo) (XLogReaderState *record);
void (*rm_desc) (StringInfo buf, XLogReaderState *record);
const char *(*rm_identify) (uint8 info);
void (*rm_startup) (void);
void (*rm_cleanup) (void);
void (*rm_mask) (char *pagedata, BlockNumber blkno);
void (*rm_decode) (struct LogicalDecodingContext *ctx,
struct XLogRecordBuffer *buf);
} RmgrData;
The working example below demonstrates usage of custom WAL resource managers.
#include "qhb.h"
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xloginsert.h"
#include "fmgr.h"
#include "utils/pg_lsn.h"
#include "varatt.h"
PG_MODULE_MAGIC;
/*
* test_custom_rmgrs WAL record message.
*/
typedef struct xl_testcustomrmgrs_message
{
Size message_size; /* size of the message */
char message[FLEXIBLE_ARRAY_MEMBER]; /* payload */
} xl_testcustomrmgrs_message;
#define SizeOfTestCustomRmgrsMessage (offsetof(xl_testcustomrmgrs_message, message))
#define XLOG_TEST_CUSTOM_RMGRS_MESSAGE 0x00
/*
* While developing or testing, use RM_EXPERIMENTAL_ID for rmid. For a real
* extension, reserve a new resource manager ID to avoid conflicting with
* other extensions; see:
* https://wiki.postgresql.org/wiki/CustomWALResourceManagers
*/
#define RM_TESTCUSTOMRMGRS_ID RM_EXPERIMENTAL_ID
#define TESTCUSTOMRMGRS_NAME "test_custom_rmgrs"
/* RMGR API, see xlog_internal.h */
void testcustomrmgrs_redo(XLogReaderState *record);
void testcustomrmgrs_desc(StringInfo buf, XLogReaderState *record);
const char *testcustomrmgrs_identify(uint8 info);
static const RmgrData testcustomrmgrs_rmgr = {
.rm_name = TESTCUSTOMRMGRS_NAME,
.rm_redo = testcustomrmgrs_redo,
.rm_desc = testcustomrmgrs_desc,
.rm_identify = testcustomrmgrs_identify
};
/*
* Module load callback
*/
void
_PG_init(void)
{
/*
* In order to create our own custom resource manager, we have to be
* loaded via shared_preload_libraries. Otherwise, registration will fail.
*/
RegisterCustomRmgr(RM_TESTCUSTOMRMGRS_ID, &testcustomrmgrs_rmgr);
}
/* RMGR API implementation */
/*
* Redo is just a noop for this module, because we aren't testing recovery of
* any real structure.
*/
void
testcustomrmgrs_redo(XLogReaderState *record)
{
uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
if (info != XLOG_TEST_CUSTOM_RMGRS_MESSAGE)
elog(PANIC, "testcustomrmgrs_redo: unknown op code %u", info);
}
void
testcustomrmgrs_desc(StringInfo buf, XLogReaderState *record)
{
char *rec = XLogRecGetData(record);
uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
if (info == XLOG_TEST_CUSTOM_RMGRS_MESSAGE)
{
xl_testcustomrmgrs_message *xlrec = (xl_testcustomrmgrs_message *) rec;
appendStringInfo(buf, "payload (%zu bytes): ", xlrec->message_size);
appendBinaryStringInfo(buf, xlrec->message, xlrec->message_size);
}
}
const char *
testcustomrmgrs_identify(uint8 info)
{
if ((info & ~XLR_INFO_MASK) == XLOG_TEST_CUSTOM_RMGRS_MESSAGE)
return "TEST_CUSTOM_RMGRS_MESSAGE";
return NULL;
}
/*
* SQL function for writing a simple message into WAL with the help of custom
* WAL resource manager.
*/
PG_FUNCTION_INFO_V1(test_custom_rmgrs_insert_wal_record);
Datum
test_custom_rmgrs_insert_wal_record(PG_FUNCTION_ARGS)
{
text *arg = PG_GETARG_TEXT_PP(0);
char *payload = VARDATA_ANY(arg);
Size len = VARSIZE_ANY_EXHDR(arg);
XLogRecPtr lsn;
xl_testcustomrmgrs_message xlrec;
xlrec.message_size = len;
XLogBeginInsert();
XLogRegisterData(&xlrec, SizeOfTestCustomRmgrsMessage);
XLogRegisterData(payload, len);
/* Let's mark this record as unimportant, just in case. */
XLogSetRecordFlags(XLOG_MARK_UNIMPORTANT);
lsn = XLogInsert(RM_TESTCUSTOMRMGRS_ID, XLOG_TEST_CUSTOM_RMGRS_MESSAGE);
PG_RETURN_LSN(lsn);
}
Then, register your new resource manager.
/*
* Register a new custom WAL resource manager.
*
* Resource manager IDs must be globally unique across all extensions. Refer
* to https://wiki.postgresql.org/wiki/CustomWALResourceManagers to reserve a
* unique RmgrId for your extension, to avoid conflicts with other extension
* developers. During development, use RM_EXPERIMENTAL_ID to avoid needlessly
* reserving a new ID.
*/
extern void RegisterCustomRmgr(RmgrId rmid, const RmgrData *rmgr);
RegisterCustomRmgr must be called from the extension module's _PG_init function. While developing a new extension, use RM_EXPERIMENTAL_ID for rmid. When you are ready to release the extension to users, reserve a new resource manager ID at the Custom WAL Resource Manager page.
Place the extension module implementing the custom resource manager in shared_preload_libraries so that it will be loaded early during QHB startup.
Note
The extension must remain in shared_preload_libraries as long as any custom WAL records may exist in the system. Otherwise QHB will not be able to apply or decode the custom WAL records, which may prevent the server from starting.