*** a/doc/src/sgml/config.sgml --- b/doc/src/sgml/config.sgml *************** *** 1805,1817 **** include 'filename' ! full_page_writes (boolean) full_page_writes configuration parameter ! When this parameter is on, the PostgreSQL server writes the entire content of each disk page to WAL during the first modification of that page after a checkpoint. This is needed because --- 1805,1818 ---- ! full_page_writes (enum) full_page_writes configuration parameter ! When this parameter is on or compress, ! the PostgreSQL server writes the entire content of each disk page to WAL during the first modification of that page after a checkpoint. This is needed because *************** *** 1829,1834 **** include 'filename' --- 1830,1840 ---- + Valid values are on, compress, and off. + The default is on. + + + Turning this parameter off speeds normal operation, but might lead to either unrecoverable data corruption, or silent data corruption, after a system failure. The risks are similar to turning off *************** *** 1843,1851 **** include 'filename' This parameter can only be set in the postgresql.conf file or on the server command line. - The default is on. --- 1849,1861 ---- + Setting this parameter to compress compresses + the full page image to reduce the amount of WAL data. + + + This parameter can only be set in the postgresql.conf file or on the server command line. *** a/src/backend/access/rmgrdesc/xlogdesc.c --- b/src/backend/access/rmgrdesc/xlogdesc.c *************** *** 31,36 **** const struct config_enum_entry wal_level_options[] = { --- 31,51 ---- {NULL, 0, false} }; + static const char * + full_page_writes_str(FullPageWritesLevel level) + { + switch (level) + { + case FULL_PAGE_WRITES_ON: + return "true"; + case FULL_PAGE_WRITES_COMPRESS: + return "compress"; + case FULL_PAGE_WRITES_OFF: + return "false"; + } + return "unrecognized"; + } + void xlog_desc(StringInfo buf, uint8 xl_info, char *rec) { *************** *** 48,54 **** xlog_desc(StringInfo buf, uint8 xl_info, char *rec) (uint32) (checkpoint->redo >> 32), (uint32) checkpoint->redo, checkpoint->ThisTimeLineID, checkpoint->PrevTimeLineID, ! checkpoint->fullPageWrites ? "true" : "false", checkpoint->nextXidEpoch, checkpoint->nextXid, checkpoint->nextOid, checkpoint->nextMulti, --- 63,69 ---- (uint32) (checkpoint->redo >> 32), (uint32) checkpoint->redo, checkpoint->ThisTimeLineID, checkpoint->PrevTimeLineID, ! full_page_writes_str(checkpoint->fullPageWrites), checkpoint->nextXidEpoch, checkpoint->nextXid, checkpoint->nextOid, checkpoint->nextMulti, *************** *** 126,135 **** xlog_desc(StringInfo buf, uint8 xl_info, char *rec) } else if (info == XLOG_FPW_CHANGE) { ! bool fpw; ! memcpy(&fpw, rec, sizeof(bool)); ! appendStringInfo(buf, "full_page_writes: %s", fpw ? "true" : "false"); } else if (info == XLOG_END_OF_RECOVERY) { --- 141,150 ---- } else if (info == XLOG_FPW_CHANGE) { ! int fpw; ! memcpy(&fpw, rec, sizeof(int)); ! appendStringInfo(buf, "full_page_writes: %s", full_page_writes_str(fpw)); } else if (info == XLOG_END_OF_RECOVERY) { *** a/src/backend/access/transam/xlog.c --- b/src/backend/access/transam/xlog.c *************** *** 55,60 **** --- 55,61 ---- #include "storage/spin.h" #include "utils/builtins.h" #include "utils/guc.h" + #include "utils/pg_lzcompress.h" #include "utils/ps_status.h" #include "utils/relmapper.h" #include "utils/snapmgr.h" *************** *** 78,84 **** int XLogArchiveTimeout = 0; bool XLogArchiveMode = false; char *XLogArchiveCommand = NULL; bool EnableHotStandby = false; ! bool fullPageWrites = true; bool log_checkpoints = false; int sync_method = DEFAULT_SYNC_METHOD; int wal_level = WAL_LEVEL_MINIMAL; --- 79,85 ---- bool XLogArchiveMode = false; char *XLogArchiveCommand = NULL; bool EnableHotStandby = false; ! int fullPageWrites = FULL_PAGE_WRITES_ON; bool log_checkpoints = false; int sync_method = DEFAULT_SYNC_METHOD; int wal_level = WAL_LEVEL_MINIMAL; *************** *** 165,171 **** static TimeLineID receiveTLI = 0; * that the recovery starting checkpoint record indicates, and then updated * each time XLOG_FPW_CHANGE record is replayed. */ ! static bool lastFullPageWrites; /* * Local copy of SharedRecoveryInProgress variable. True actually means "not --- 166,172 ---- * that the recovery starting checkpoint record indicates, and then updated * each time XLOG_FPW_CHANGE record is replayed. */ ! static int lastFullPageWrites; /* * Local copy of SharedRecoveryInProgress variable. True actually means "not *************** *** 796,801 **** static bool ReserveXLogSwitch(XLogRecPtr *StartPos, XLogRecPtr *EndPos, --- 797,803 ---- XLogRecPtr *PrevPtr); static XLogRecPtr WaitXLogInsertionsToFinish(XLogRecPtr upto); static void WakeupWaiters(XLogRecPtr EndPos); + static char *CompressBackupBlock(char *page, uint32 orig_len, uint32 *len); static char *GetXLogBuffer(XLogRecPtr ptr); static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos); static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos); *************** *** 846,851 **** XLogInsert(RmgrId rmid, uint8 info, XLogRecData *rdata) --- 848,854 ---- static XLogRecord *rechdr; XLogRecPtr StartPos; XLogRecPtr EndPos; + int fpw; if (rechdr == NULL) { *************** *** 898,908 **** begin:; /* * Decide if we need to do full-page writes in this XLOG record: true if ! * full_page_writes is on or we have a PITR request for it. Since we ! * don't yet have an insertion slot, fullPageWrites and forcePageWrites ! * could change under us, but we'll recheck them once we have a slot. */ ! doPageWrites = Insert->fullPageWrites || Insert->forcePageWrites; len = 0; for (rdt = rdata;;) --- 901,913 ---- /* * Decide if we need to do full-page writes in this XLOG record: true if ! * full_page_writes is needed (i.e., on or compress) or we have a PITR ! * request for it. Since we don't yet have an insertion slot, ! * fullPageWrites and forcePageWrites could change under us, but we'll ! * recheck them once we have a slot. */ ! fpw = Insert->fullPageWrites; ! doPageWrites = FullPageWritesIsNeeded(fpw) || Insert->forcePageWrites; len = 0; for (rdt = rdata;;) *************** *** 1003,1008 **** begin:; --- 1008,1026 ---- rdt->next = &(dtbuf_rdt2[i]); rdt = rdt->next; + if (fpw <= FULL_PAGE_WRITES_COMPRESS) + { + rdt->data = CompressBackupBlock(page, BLCKSZ - bkpb->hole_length, &(rdt->len)); + if (rdt->data != NULL) + { + write_len += rdt->len; + bkpb->hole_length = BLCKSZ - rdt->len; + bkpb->flags = BKPBLOCK_COMPRESSED; + rdt->next = NULL; + continue; + } + } + if (bkpb->hole_length == 0) { rdt->data = page; *************** *** 1133,1144 **** begin:; } /* ! * Also check to see if fullPageWrites or forcePageWrites was just turned ! * on; if we weren't already doing full-page writes then go back and ! * recompute. (If it was just turned off, we could recompute the record ! * without full pages, but we choose not to bother.) */ ! if ((Insert->fullPageWrites || Insert->forcePageWrites) && !doPageWrites) { /* Oops, must redo it with full-page data. */ WALInsertSlotRelease(); --- 1151,1164 ---- } /* ! * Also check to see if fullPageWrites was just changed on or compress, ! * or if forcePageWrites was just turned on; if we weren't already doing ! * full-page writes then go back and recompute. (If it was just turned off, ! * we could recompute the record without full pages, but we choose not ! * to bother.) */ ! if ((FullPageWritesIsNeeded(Insert->fullPageWrites) || Insert->forcePageWrites) && ! !doPageWrites) { /* Oops, must redo it with full-page data. */ WALInsertSlotRelease(); *************** *** 2091,2096 **** WaitXLogInsertionsToFinish(XLogRecPtr upto) --- 2111,2158 ---- } /* + * Create a compressed version of a backup block + * + * If successful, return a compressed result and set 'len' to its length. + * Otherwise (ie, compressed result is actually bigger than original), + * return NULL. + */ + static char * + CompressBackupBlock(char *page, uint32 orig_len, uint32 *len) + { + struct varlena *buf; + + #define PGLZ_BLCKSZ PGLZ_MAX_OUTPUT(BLCKSZ) + + buf = (struct varlena *) palloc(PGLZ_BLCKSZ); + + /* + * We recheck the actual size even if pglz_compress() reports success, + * because it might be satisfied with having saved as little as one byte + * in the compressed data --- which could turn into a net loss once you + * consider header and alignment padding. Worst case, the compressed + * format might require three padding bytes (plus header, which is + * included in VARSIZE(buf)), whereas the uncompressed format would take + * only one header byte and no padding if the value is short enough. So + * we insist on a savings of more than 2 bytes to ensure we have a gain. + */ + if (pglz_compress(page, BLCKSZ, + (PGLZ_Header *) buf, PGLZ_strategy_default) && + VARSIZE(buf) < orig_len - 2) + { + /* successful compression */ + *len = VARHDRSZ + VARSIZE(buf); + return (char *) buf; + } + else + { + /* incompressible data */ + pfree(buf); + return NULL; + } + } + + /* * Get a pointer to the right location in the WAL buffer containing the * given XLogRecPtr. * *************** *** 2346,2351 **** XLogCheckBuffer(XLogRecData *rdata, bool holdsExclusiveLock, --- 2408,2414 ---- * The page needs to be backed up, so set up *bkpb */ BufferGetTag(rdata->buffer, &bkpb->node, &bkpb->fork, &bkpb->block); + bkpb->flags = BKPBLOCK_UNCOMPRESSED; if (rdata->buffer_std) { *************** *** 4307,4313 **** RestoreBackupBlockContents(XLogRecPtr lsn, BkpBlock bkpb, char *blk, page = (Page) BufferGetPage(buffer); ! if (bkpb.hole_length == 0) { memcpy((char *) page, blk, BLCKSZ); } --- 4370,4381 ---- page = (Page) BufferGetPage(buffer); ! if (bkpb.flags == BKPBLOCK_COMPRESSED) ! { ! /* If it's compressed, decompress it */ ! pglz_decompress((PGLZ_Header *) blk, (char *) page); ! } ! else if (bkpb.hole_length == 0) { memcpy((char *) page, blk, BLCKSZ); } *************** *** 8975,8984 **** UpdateFullPageWrites(void) * setting it to false, first write the WAL record and then set the global * flag. */ ! if (fullPageWrites) { WALInsertSlotAcquire(true); ! Insert->fullPageWrites = true; WALInsertSlotRelease(); } --- 9043,9052 ---- * setting it to false, first write the WAL record and then set the global * flag. */ ! if (FullPageWritesIsNeeded(fullPageWrites)) { WALInsertSlotAcquire(true); ! Insert->fullPageWrites = fullPageWrites; WALInsertSlotRelease(); } *************** *** 8991,9007 **** UpdateFullPageWrites(void) XLogRecData rdata; rdata.data = (char *) (&fullPageWrites); ! rdata.len = sizeof(bool); rdata.buffer = InvalidBuffer; rdata.next = NULL; XLogInsert(RM_XLOG_ID, XLOG_FPW_CHANGE, &rdata); } ! if (!fullPageWrites) { WALInsertSlotAcquire(true); ! Insert->fullPageWrites = false; WALInsertSlotRelease(); } END_CRIT_SECTION(); --- 9059,9075 ---- XLogRecData rdata; rdata.data = (char *) (&fullPageWrites); ! rdata.len = sizeof(int); rdata.buffer = InvalidBuffer; rdata.next = NULL; XLogInsert(RM_XLOG_ID, XLOG_FPW_CHANGE, &rdata); } ! if (!FullPageWritesIsNeeded(fullPageWrites)) { WALInsertSlotAcquire(true); ! Insert->fullPageWrites = fullPageWrites; WALInsertSlotRelease(); } END_CRIT_SECTION(); *************** *** 9353,9368 **** xlog_redo(XLogRecPtr lsn, XLogRecord *record) { /* use volatile pointer to prevent code rearrangement */ volatile XLogCtlData *xlogctl = XLogCtl; ! bool fpw; ! memcpy(&fpw, XLogRecGetData(record), sizeof(bool)); /* * Update the LSN of the last replayed XLOG_FPW_CHANGE record so that * do_pg_start_backup() and do_pg_stop_backup() can check whether * full_page_writes has been disabled during online backup. */ ! if (!fpw) { SpinLockAcquire(&xlogctl->info_lck); if (xlogctl->lastFpwDisableRecPtr < ReadRecPtr) --- 9421,9436 ---- { /* use volatile pointer to prevent code rearrangement */ volatile XLogCtlData *xlogctl = XLogCtl; ! int fpw; ! memcpy(&fpw, XLogRecGetData(record), sizeof(int)); /* * Update the LSN of the last replayed XLOG_FPW_CHANGE record so that * do_pg_start_backup() and do_pg_stop_backup() can check whether * full_page_writes has been disabled during online backup. */ ! if (!FullPageWritesIsNeeded(fpw)) { SpinLockAcquire(&xlogctl->info_lck); if (xlogctl->lastFpwDisableRecPtr < ReadRecPtr) *************** *** 9686,9692 **** do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p, do { ! bool checkpointfpw; /* * Force a CHECKPOINT. Aside from being necessary to prevent torn --- 9754,9760 ---- do { ! int checkpointfpw; /* * Force a CHECKPOINT. Aside from being necessary to prevent torn *************** *** 9737,9743 **** do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p, recptr = xlogctl->lastFpwDisableRecPtr; SpinLockRelease(&xlogctl->info_lck); ! if (!checkpointfpw || startpoint <= recptr) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("WAL generated with full_page_writes=off was replayed " --- 9805,9811 ---- recptr = xlogctl->lastFpwDisableRecPtr; SpinLockRelease(&xlogctl->info_lck); ! if (!FullPageWritesIsNeeded(checkpointfpw) || startpoint <= recptr) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("WAL generated with full_page_writes=off was replayed " *** a/src/backend/utils/misc/guc.c --- b/src/backend/utils/misc/guc.c *************** *** 381,386 **** static const struct config_enum_entry synchronous_commit_options[] = { --- 381,403 ---- }; /* + * Although only "on", "off", and "compress" are documented, we + * accept all the likely variants of "on" and "off". + */ + static const struct config_enum_entry full_page_writes_options[] = { + {"compress", FULL_PAGE_WRITES_COMPRESS, false}, + {"on", FULL_PAGE_WRITES_ON, false}, + {"off", FULL_PAGE_WRITES_OFF, false}, + {"true", FULL_PAGE_WRITES_ON, true}, + {"false", FULL_PAGE_WRITES_OFF, true}, + {"yes", FULL_PAGE_WRITES_ON, true}, + {"no", FULL_PAGE_WRITES_OFF, true}, + {"1", FULL_PAGE_WRITES_ON, true}, + {"0", FULL_PAGE_WRITES_OFF, true}, + {NULL, 0, false} + }; + + /* * Options for enum values stored in other modules */ extern const struct config_enum_entry wal_level_options[]; *************** *** 840,858 **** static struct config_bool ConfigureNamesBool[] = NULL, NULL, NULL }, { - {"full_page_writes", PGC_SIGHUP, WAL_SETTINGS, - gettext_noop("Writes full pages to WAL when first modified after a checkpoint."), - gettext_noop("A page write in process during an operating system crash might be " - "only partially written to disk. During recovery, the row changes " - "stored in WAL are not enough to recover. This option writes " - "pages when first modified after a checkpoint to WAL so full recovery " - "is possible.") - }, - &fullPageWrites, - true, - NULL, NULL, NULL - }, - { {"log_checkpoints", PGC_SIGHUP, LOGGING_WHAT, gettext_noop("Logs each checkpoint."), NULL --- 857,862 ---- *************** *** 3288,3293 **** static struct config_enum ConfigureNamesEnum[] = --- 3292,3311 ---- }, { + {"full_page_writes", PGC_SIGHUP, WAL_SETTINGS, + gettext_noop("Writes full pages to WAL when first modified after a checkpoint."), + gettext_noop("A page write in process during an operating system crash might be " + "only partially written to disk. During recovery, the row changes " + "stored in WAL are not enough to recover. This option writes " + "pages when first modified after a checkpoint to WAL so full recovery " + "is possible.") + }, + &fullPageWrites, + FULL_PAGE_WRITES_ON, full_page_writes_options, + NULL, NULL, NULL + }, + + { {"trace_recovery_messages", PGC_SIGHUP, DEVELOPER_OPTIONS, gettext_noop("Enables logging of recovery-related debugging information."), gettext_noop("Each level includes all the levels that follow it. The later" *** a/src/backend/utils/misc/postgresql.conf.sample --- b/src/backend/utils/misc/postgresql.conf.sample *************** *** 173,179 **** # fsync # fsync_writethrough # open_sync ! #full_page_writes = on # recover from partial page writes #wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers # (change requires restart) #wal_writer_delay = 200ms # 1-10000 milliseconds --- 173,180 ---- # fsync # fsync_writethrough # open_sync ! #full_page_writes = on # recover from partial page writes; ! # off, compress, or on #wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers # (change requires restart) #wal_writer_delay = 200ms # 1-10000 milliseconds *** a/src/bin/pg_controldata/pg_controldata.c --- b/src/bin/pg_controldata/pg_controldata.c *************** *** 218,224 **** main(int argc, char *argv[]) printf(_("Latest checkpoint's PrevTimeLineID: %u\n"), ControlFile.checkPointCopy.PrevTimeLineID); printf(_("Latest checkpoint's full_page_writes: %s\n"), ! ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")); printf(_("Latest checkpoint's NextXID: %u/%u\n"), ControlFile.checkPointCopy.nextXidEpoch, ControlFile.checkPointCopy.nextXid); --- 218,224 ---- printf(_("Latest checkpoint's PrevTimeLineID: %u\n"), ControlFile.checkPointCopy.PrevTimeLineID); printf(_("Latest checkpoint's full_page_writes: %s\n"), ! FullPageWritesStr(ControlFile.checkPointCopy.fullPageWrites)); printf(_("Latest checkpoint's NextXID: %u/%u\n"), ControlFile.checkPointCopy.nextXidEpoch, ControlFile.checkPointCopy.nextXid); *** a/src/bin/pg_resetxlog/pg_resetxlog.c --- b/src/bin/pg_resetxlog/pg_resetxlog.c *************** *** 496,502 **** GuessControlValues(void) ControlFile.checkPointCopy.redo = SizeOfXLogLongPHD; ControlFile.checkPointCopy.ThisTimeLineID = 1; ControlFile.checkPointCopy.PrevTimeLineID = 1; ! ControlFile.checkPointCopy.fullPageWrites = false; ControlFile.checkPointCopy.nextXidEpoch = 0; ControlFile.checkPointCopy.nextXid = FirstNormalTransactionId; ControlFile.checkPointCopy.nextOid = FirstBootstrapObjectId; --- 496,502 ---- ControlFile.checkPointCopy.redo = SizeOfXLogLongPHD; ControlFile.checkPointCopy.ThisTimeLineID = 1; ControlFile.checkPointCopy.PrevTimeLineID = 1; ! ControlFile.checkPointCopy.fullPageWrites = FULL_PAGE_WRITES_OFF; ControlFile.checkPointCopy.nextXidEpoch = 0; ControlFile.checkPointCopy.nextXid = FirstNormalTransactionId; ControlFile.checkPointCopy.nextOid = FirstBootstrapObjectId; *************** *** 583,589 **** PrintControlValues(bool guessed) printf(_("Latest checkpoint's TimeLineID: %u\n"), ControlFile.checkPointCopy.ThisTimeLineID); printf(_("Latest checkpoint's full_page_writes: %s\n"), ! ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")); printf(_("Latest checkpoint's NextXID: %u/%u\n"), ControlFile.checkPointCopy.nextXidEpoch, ControlFile.checkPointCopy.nextXid); --- 583,589 ---- printf(_("Latest checkpoint's TimeLineID: %u\n"), ControlFile.checkPointCopy.ThisTimeLineID); printf(_("Latest checkpoint's full_page_writes: %s\n"), ! FullPageWritesStr(ControlFile.checkPointCopy.fullPageWrites)); printf(_("Latest checkpoint's NextXID: %u/%u\n"), ControlFile.checkPointCopy.nextXidEpoch, ControlFile.checkPointCopy.nextXid); *** a/src/include/access/xlog.h --- b/src/include/access/xlog.h *************** *** 188,194 **** extern int XLogArchiveTimeout; extern bool XLogArchiveMode; extern char *XLogArchiveCommand; extern bool EnableHotStandby; - extern bool fullPageWrites; extern bool log_checkpoints; extern int num_xloginsert_slots; --- 188,193 ---- *************** *** 201,206 **** typedef enum WalLevel --- 200,217 ---- } WalLevel; extern int wal_level; + typedef enum FullPageWritesLevel + { + FULL_PAGE_WRITES_OFF = 0, + FULL_PAGE_WRITES_COMPRESS, + FULL_PAGE_WRITES_ON + } FullPageWritesLevel; + extern int fullPageWrites; + #define FullPageWritesIsNeeded(fpw) (fpw >= FULL_PAGE_WRITES_COMPRESS) + #define FullPageWritesStr(fpw) \ + (fpw == FULL_PAGE_WRITES_ON ? _("on") : \ + (fpw == FULL_PAGE_WRITES_OFF ? _("off") : _("compress"))) + #define XLogArchivingActive() (XLogArchiveMode && wal_level >= WAL_LEVEL_ARCHIVE) #define XLogArchiveCommandSet() (XLogArchiveCommand[0] != '\0') *** a/src/include/access/xlog_internal.h --- b/src/include/access/xlog_internal.h *************** *** 46,57 **** typedef struct BkpBlock RelFileNode node; /* relation containing block */ ForkNumber fork; /* fork within the relation */ BlockNumber block; /* block number */ ! uint16 hole_offset; /* number of bytes before "hole" */ ! uint16 hole_length; /* number of bytes in "hole" */ /* ACTUAL BLOCK DATA FOLLOWS AT END OF STRUCT */ } BkpBlock; /* * Each page of XLOG file has a header like this: */ --- 46,61 ---- RelFileNode node; /* relation containing block */ ForkNumber fork; /* fork within the relation */ BlockNumber block; /* block number */ ! unsigned hole_offset:15, /* number of bytes before "hole" */ ! flags:2, /* state of a backup block, see below */ ! hole_length:15; /* number of bytes in "hole" */ /* ACTUAL BLOCK DATA FOLLOWS AT END OF STRUCT */ } BkpBlock; + #define BKPBLOCK_UNCOMPRESSED 0 /* uncompressed */ + #define BKPBLOCK_COMPRESSED 1 /* comperssed */ + /* * Each page of XLOG file has a header like this: */ *** a/src/include/catalog/pg_control.h --- b/src/include/catalog/pg_control.h *************** *** 35,41 **** typedef struct CheckPoint TimeLineID ThisTimeLineID; /* current TLI */ TimeLineID PrevTimeLineID; /* previous TLI, if this record begins a new * timeline (equals ThisTimeLineID otherwise) */ ! bool fullPageWrites; /* current full_page_writes */ uint32 nextXidEpoch; /* higher-order bits of nextXid */ TransactionId nextXid; /* next free XID */ Oid nextOid; /* next free OID */ --- 35,41 ---- TimeLineID ThisTimeLineID; /* current TLI */ TimeLineID PrevTimeLineID; /* previous TLI, if this record begins a new * timeline (equals ThisTimeLineID otherwise) */ ! int fullPageWrites; /* current full_page_writes */ uint32 nextXidEpoch; /* higher-order bits of nextXid */ TransactionId nextXid; /* next free XID */ Oid nextOid; /* next free OID */