From: Kyotaro Horiguchi Date: Thu, 11 Oct 2018 16:00:44 +0900 Subject: [PATCH 4/7] Add infrastructure to WAL-logging skip feature We used to optimize WAL-logging for truncation of in-transaction crated tables in minimal mode by just signaling by HEAP_INSERT_SKIP_WAL option on heap operations. This mechanism can emit WAL records that results in corrupt state for certain series of in-transaction operations. This patch provides infrastructure to track pending at-commit fsyncs for a relation and in-transaction truncations. heap_register_sync() should be used to start tracking before batch operations like COPY and CLUSTER, and use BufferNeedsWAL() instead of RelationNeedsWAL() at the places related to WAL-logging about heap-modifying operations. --- src/backend/access/heap/heapam.c | 31 ++++ src/backend/access/transam/xact.c | 11 ++ src/backend/catalog/storage.c | 344 +++++++++++++++++++++++++++++++++--- src/backend/commands/tablecmds.c | 3 +- src/backend/storage/buffer/bufmgr.c | 40 ++++- src/backend/utils/cache/relcache.c | 3 + src/include/access/heapam.h | 1 + src/include/catalog/storage.h | 5 + src/include/storage/bufmgr.h | 2 + src/include/utils/rel.h | 8 + 10 files changed, 417 insertions(+), 31 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index fe5d939c45..024620ddc1 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -51,6 +51,7 @@ #include "access/xloginsert.h" #include "access/xlogutils.h" #include "catalog/catalog.h" +#include "catalog/storage.h" #include "miscadmin.h" #include "pgstat.h" #include "port/atomics.h" @@ -8829,3 +8830,33 @@ heap_mask(char *pagedata, BlockNumber blkno) } } } + +/* + * heap_register_sync - register a heap to be synced to disk at commit + * + * This can be used to skip WAL-logging changes on a relation file that has + * been created in the same transaction. This makes note of the current size of + * the relation, and ensures that when the relation is extended, any changes + * to the new blocks in the heap, in the same transaction, will not be + * WAL-logged. Instead, the heap contents are flushed to disk at commit, + * like heap_sync() does. + * + * This does the same for the TOAST heap, if any. Indexes are not affected. + */ +void +heap_register_sync(Relation rel) +{ + /* non-WAL-logged tables never need fsync */ + if (!RelationNeedsWAL(rel)) + return; + + RecordWALSkipping(rel); + if (OidIsValid(rel->rd_rel->reltoastrelid)) + { + Relation toastrel; + + toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock); + RecordWALSkipping(toastrel); + heap_close(toastrel, AccessShareLock); + } +} diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index c3214d4f4d..32a6a877f3 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -2022,6 +2022,9 @@ CommitTransaction(void) /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); + /* Flush updates to relations that we didn't WAL-logged */ + smgrDoPendingSyncs(true); + /* * Mark serializable transaction as complete for predicate locking * purposes. This should be done as late as we can put it and still allow @@ -2254,6 +2257,9 @@ PrepareTransaction(void) /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); + /* Flush updates to relations that we didn't WAL-logged */ + smgrDoPendingSyncs(true); + /* * Mark serializable transaction as complete for predicate locking * purposes. This should be done as late as we can put it and still allow @@ -2579,6 +2585,7 @@ AbortTransaction(void) AtAbort_Notify(); AtEOXact_RelationMap(false, is_parallel_worker); AtAbort_Twophase(); + smgrDoPendingSyncs(false); /* abandone pending syncs */ /* * Advertise the fact that we aborted in pg_xact (assuming that we got as @@ -4097,6 +4104,8 @@ ReleaseSavepoint(const char *name) (errcode(ERRCODE_S_E_INVALID_SPECIFICATION), errmsg("savepoint \"%s\" does not exist within current savepoint level", name))); + smgrProcessWALRequirementInval(s->subTransactionId, true); + /* * Mark "commit pending" all subtransactions up to the target * subtransaction. The actual commits will happen when control gets to @@ -4206,6 +4215,8 @@ RollbackToSavepoint(const char *name) (errcode(ERRCODE_S_E_INVALID_SPECIFICATION), errmsg("savepoint \"%s\" does not exist within current savepoint level", name))); + smgrProcessWALRequirementInval(s->subTransactionId, false); + /* * Mark "abort pending" all subtransactions up to the target * subtransaction. The actual aborts will happen when control gets to diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index 0302507e6f..a0cf8d3e27 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -28,6 +28,7 @@ #include "catalog/storage_xlog.h" #include "storage/freespace.h" #include "storage/smgr.h" +#include "utils/hsearch.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -62,6 +63,54 @@ typedef struct PendingRelDelete static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */ +/* + * We also track relation files (RelFileNode values) that have been created + * in the same transaction, and that have been modified without WAL-logging + * the action (an optimization possible with wal_level=minimal). When we are + * about to skip WAL-logging, a PendingRelSync entry is created, and + * 'skip_wal_min_blk' is set to the current size of the relation. Any operations + * on blocks < skip_wal_min_blk need to be WAL-logged as usual, but for + * operations on higher blocks, WAL-logging is skipped. + + * + * NB: after WAL-logging has been skipped for a block, we must not WAL-log + * any subsequent actions on the same block either. Replaying the WAL record + * of the subsequent action might fail otherwise, as the "before" state of + * the block might not match, as the earlier actions were not WAL-logged. + * Likewise, after we have WAL-logged an operation for a block, we must + * WAL-log any subsequent operations on the same page as well. Replaying + * a possible full-page-image from the earlier WAL record would otherwise + * revert the page to the old state, even if we sync the relation at end + * of transaction. + * + * If a relation is truncated (without creating a new relfilenode), and we + * emit a WAL record of the truncation, we can't skip WAL-logging for any + * of the truncated blocks anymore, as replaying the truncation record will + * destroy all the data inserted after that. But if we have already decided + * to skip WAL-logging changes to a relation, and the relation is truncated, + * we don't need to WAL-log the truncation either. + * + * This mechanism is currently only used by heaps. Indexes are always + * WAL-logged. Also, this only applies for wal_level=minimal; with higher + * WAL levels we need the WAL for PITR/replication anyway. + */ +typedef struct RelWalRequirement +{ + RelFileNode relnode; /* relation created in same xact */ + BlockNumber skip_wal_min_blk;/* WAL-logging skipped for blocks >= + * skip_wal_min_blk */ + BlockNumber wal_log_min_blk; /* The minimum blk number that requires + * WAL-logging even if skipped by the above*/ + SubTransactionId invalidate_sxid; /* subxid where this entry is + * invalidated */ +} RelWalRequirement; + +/* Relations that need to be fsync'd at commit */ +static HTAB *relWalRequirements = NULL; +static int walreq_pending_invals = 0; + +static RelWalRequirement *getWalRequirementEntry(Relation rel, bool create); + /* * RelationCreateStorage * Create physical storage for a relation. @@ -259,37 +308,114 @@ RelationTruncate(Relation rel, BlockNumber nblocks) */ if (RelationNeedsWAL(rel)) { - /* - * Make an XLOG entry reporting the file truncation. - */ - XLogRecPtr lsn; - xl_smgr_truncate xlrec; + RelWalRequirement *walreq; - xlrec.blkno = nblocks; - xlrec.rnode = rel->rd_node; - xlrec.flags = SMGR_TRUNCATE_ALL; + /* get pending sync entry, create if not yet */ + walreq = getWalRequirementEntry(rel, true); - XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + if (walreq->skip_wal_min_blk == InvalidBlockNumber || + walreq->skip_wal_min_blk < nblocks) + { + /* + * This is the first time truncation of this relation in this + * transaction or truncation that leaves pages that need at-commit + * fsync. Make an XLOG entry reporting the file truncation. + */ + XLogRecPtr lsn; + xl_smgr_truncate xlrec; - lsn = XLogInsert(RM_SMGR_ID, - XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE); + xlrec.blkno = nblocks; + xlrec.rnode = rel->rd_node; + xlrec.flags = SMGR_TRUNCATE_ALL; - /* - * Flush, because otherwise the truncation of the main relation might - * hit the disk before the WAL record, and the truncation of the FSM - * or visibility map. If we crashed during that window, we'd be left - * with a truncated heap, but the FSM or visibility map would still - * contain entries for the non-existent heap pages. - */ - if (fsm || vm) - XLogFlush(lsn); + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + + lsn = XLogInsert(RM_SMGR_ID, + XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE); + + /* + * Flush, because otherwise the truncation of the main relation + * might hit the disk before the WAL record, and the truncation of + * the FSM or visibility map. If we crashed during that window, + * we'd be left with a truncated heap, but the FSM or visibility + * map would still contain entries for the non-existent heap + * pages. + */ + if (fsm || vm) + XLogFlush(lsn); + + /* no longer skip WAL-logging for the blocks */ + rel->rd_walrequirement->wal_log_min_blk = nblocks; + } } /* Do the real work */ smgrtruncate(rel->rd_smgr, MAIN_FORKNUM, nblocks); } +/* + * getWalRequirementEntry: get WAL requirement entry. + * + * Returns WAL requirement entry for the relation. The entry tracks + * WAL-skipping blocks for the relation. The WAL-skipped blocks need fsync at + * commit time. Creates one if needed when create is true. + */ +static RelWalRequirement * +getWalRequirementEntry(Relation rel, bool create) +{ + RelWalRequirement *walreq_entry = NULL; + bool found; + + if (rel->rd_walrequirement) + return rel->rd_walrequirement; + + /* we know we don't have pending sync entry */ + if (!create && rel->rd_nowalrequirement) + return NULL; + + if (!relWalRequirements) + { + /* First time through: initialize the hash table */ + HASHCTL ctl; + + if (!create) + return NULL; + + MemSet(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(RelFileNode); + ctl.entrysize = sizeof(RelWalRequirement); + ctl.hash = tag_hash; + relWalRequirements = hash_create("pending relation sync table", 5, + &ctl, HASH_ELEM | HASH_FUNCTION); + } + + walreq_entry = (RelWalRequirement *) + hash_search(relWalRequirements, (void *) &rel->rd_node, + create ? HASH_ENTER: HASH_FIND, &found); + + if (!walreq_entry) + { + /* prevent further hash lookup */ + rel->rd_nowalrequirement = true; + return NULL; + } + + /* new entry created */ + if (!found) + { + walreq_entry->wal_log_min_blk = InvalidBlockNumber; + walreq_entry->skip_wal_min_blk = InvalidBlockNumber; + walreq_entry->invalidate_sxid = InvalidSubTransactionId; + } + + /* hold shortcut in Relation */ + rel->rd_nowalrequirement = false; + rel->rd_walrequirement = walreq_entry; + + return walreq_entry; +} + /* * smgrDoPendingDeletes() -- Take care of relation deletes at end of xact. * @@ -367,6 +493,34 @@ smgrDoPendingDeletes(bool isCommit) } } +/* + * RelationInvalidateWALRequirements() -- invalidate wal requirement entry + */ +void +RelationInvalidateWALRequirements(Relation rel) +{ + RelWalRequirement *walreq; + + /* we know we don't have one */ + if (rel->rd_nowalrequirement) + return; + + walreq = getWalRequirementEntry(rel, false); + + if (!walreq) + return; + + /* + * The state is reset at subtransaction commit/abort. No invalidation + * request must not come for the same relation in the same subtransaction. + */ + Assert(walreq->invalidate_sxid == InvalidSubTransactionId); + + walreq_pending_invals++; + walreq->invalidate_sxid = GetCurrentSubTransactionId(); +} + + /* * smgrGetPendingDeletes() -- Get a list of non-temp relations to be deleted. * @@ -418,6 +572,154 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) return nrels; } + +/* + * Remember that the given relation doesn't need WAL-logging for the blocks + * after the current block size and the blocks are going to be sync'd at + * commit. + */ +void +RecordWALSkipping(Relation rel) +{ + BlockNumber nblocks; + RelWalRequirement *walreq; + + Assert(RelationNeedsWAL(rel)); + + /* get pending sync entry, create if not yet */ + walreq = getWalRequirementEntry(rel, true); + + nblocks = RelationGetNumberOfBlocks(rel); + + /* + * Record only the first registration. + */ + if (walreq->skip_wal_min_blk != InvalidBlockNumber) + return; + + walreq->skip_wal_min_blk = nblocks; +} + +/* + * Do changes to given heap page need to be WAL-logged? + * + * This takes into account any previous RecordPendingSync() requests. + * + * Note that it is required to check this before creating any WAL records for + * heap pages - it is not merely an optimization! WAL-logging a record, when + * we have already skipped a previous WAL record for the same page could lead + * to failure at WAL replay, as the "before" state expected by the record + * might not match what's on disk. Also, if the heap was truncated earlier, we + * must WAL-log any changes to the once-truncated blocks, because replaying + * the truncation record will destroy them. + */ +bool +BufferNeedsWAL(Relation rel, Buffer buf) +{ + BlockNumber blkno = InvalidBlockNumber; + RelWalRequirement *walreq; + + if (!RelationNeedsWAL(rel)) + return false; + + /* fetch exising pending sync entry */ + walreq = getWalRequirementEntry(rel, false); + + /* + * no point in doing further work if we know that we don't have special + * WAL requirement + */ + if (!walreq) + return true; + + Assert(BufferIsValid(buf)); + + blkno = BufferGetBlockNumber(buf); + + /* + * We don't skip WAL-logging for pages that once done. + */ + if (walreq->skip_wal_min_blk == InvalidBlockNumber || + walreq->skip_wal_min_blk > blkno) + return true; + + /* + * we don't skip WAL-logging for blocks that have got WAL-logged + * truncation + */ + if (walreq->wal_log_min_blk != InvalidBlockNumber && + walreq->wal_log_min_blk <= blkno) + return true; + + return false; +} + +/* + * Sync to disk any relations that we have skipped WAL-logging earlier. + */ +void +smgrDoPendingSyncs(bool isCommit) +{ + if (!relWalRequirements) + return; + + if (isCommit) + { + HASH_SEQ_STATUS status; + RelWalRequirement *walreq; + + hash_seq_init(&status, relWalRequirements); + + while ((walreq = hash_seq_search(&status)) != NULL) + { + if (walreq->skip_wal_min_blk != InvalidBlockNumber) + { + FlushRelationBuffersWithoutRelCache(walreq->relnode, false); + smgrimmedsync(smgropen(walreq->relnode, InvalidBackendId), + MAIN_FORKNUM); + } + } + } + + hash_destroy(relWalRequirements); + relWalRequirements = NULL; +} + +/* + * Process pending invalidation of WAL requirements happened in the + * subtransaction + */ +void +smgrProcessWALRequirementInval(SubTransactionId sxid, bool isCommit) +{ + HASH_SEQ_STATUS status; + RelWalRequirement *walreq; + + if (!relWalRequirements || walreq_pending_invals == 0) + return; + + /* + * It may take some time when there're many relWalRequirements entries. We + * expect that we don't have relWalRequirements in almost all cases. + */ + hash_seq_init(&status, relWalRequirements); + + while ((walreq = hash_seq_search(&status)) != NULL) + { + if (walreq->invalidate_sxid == sxid) + { + Assert(walreq_pending_invals > 0); + walreq->invalidate_sxid = InvalidSubTransactionId; + walreq_pending_invals--; + if (isCommit) + { + walreq->skip_wal_min_blk = InvalidBlockNumber; + walreq->wal_log_min_blk = InvalidBlockNumber; + } + } + } +} + /* * PostPrepare_smgr -- Clean up after a successful PREPARE * diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 3183b2aaa1..45bb0b5614 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -11587,11 +11587,12 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode) /* * Create and copy all forks of the relation, and schedule unlinking of - * old physical files. + * old physical files. Pending syncs for the old node is no longer needed. * * NOTE: any conflict in relfilenode value will be caught in * RelationCreateStorage(). */ + RelationInvalidateWALRequirements(rel); RelationCreateStorage(newrnode, rel->rd_rel->relpersistence); /* copy main fork */ diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 273e2f385f..a9741f138c 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -451,6 +451,7 @@ static BufferDesc *BufferAlloc(SMgrRelation smgr, BufferAccessStrategy strategy, bool *foundPtr); static void FlushBuffer(BufferDesc *buf, SMgrRelation reln); +static void FlushRelationBuffers_common(SMgrRelation smgr, bool islocal); static void AtProcExit_Buffers(int code, Datum arg); static void CheckForBufferLeaks(void); static int rnode_comparator(const void *p1, const void *p2); @@ -3153,20 +3154,41 @@ PrintPinnedBufs(void) void FlushRelationBuffers(Relation rel) { - int i; - BufferDesc *bufHdr; - /* Open rel at the smgr level if not already done */ RelationOpenSmgr(rel); - if (RelationUsesLocalBuffers(rel)) + FlushRelationBuffers_common(rel->rd_smgr, RelationUsesLocalBuffers(rel)); +} + +/* + * Like FlushRelationBuffers(), but the relation is specified by a + * RelFileNode + */ +void +FlushRelationBuffersWithoutRelCache(RelFileNode rnode, bool islocal) +{ + FlushRelationBuffers_common(smgropen(rnode, InvalidBackendId), islocal); +} + +/* + * Code shared between functions FlushRelationBuffers() and + * FlushRelationBuffersWithoutRelCache(). + */ +static void +FlushRelationBuffers_common(SMgrRelation smgr, bool islocal) +{ + RelFileNode rnode = smgr->smgr_rnode.node; + int i; + BufferDesc *bufHdr; + + if (islocal) { for (i = 0; i < NLocBuffer; i++) { uint32 buf_state; bufHdr = GetLocalBufferDescriptor(i); - if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) && + if (RelFileNodeEquals(bufHdr->tag.rnode, rnode) && ((buf_state = pg_atomic_read_u32(&bufHdr->state)) & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY)) { @@ -3183,7 +3205,7 @@ FlushRelationBuffers(Relation rel) PageSetChecksumInplace(localpage, bufHdr->tag.blockNum); - smgrwrite(rel->rd_smgr, + smgrwrite(smgr, bufHdr->tag.forkNum, bufHdr->tag.blockNum, localpage, @@ -3213,18 +3235,18 @@ FlushRelationBuffers(Relation rel) * As in DropRelFileNodeBuffers, an unlocked precheck should be safe * and saves some cycles. */ - if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node)) + if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode)) continue; ReservePrivateRefCountEntry(); buf_state = LockBufHdr(bufHdr); - if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) && + if (RelFileNodeEquals(bufHdr->tag.rnode, rnode) && (buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY)) { PinBuffer_Locked(bufHdr); LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_SHARED); - FlushBuffer(bufHdr, rel->rd_smgr); + FlushBuffer(bufHdr, smgr); LWLockRelease(BufferDescriptorGetContentLock(bufHdr)); UnpinBuffer(bufHdr, true); } diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 84609e0725..95e834d45e 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -75,6 +75,7 @@ #include "partitioning/partdesc.h" #include "rewrite/rewriteDefine.h" #include "rewrite/rowsecurity.h" +#include "storage/bufmgr.h" #include "storage/lmgr.h" #include "storage/smgr.h" #include "utils/array.h" @@ -5625,6 +5626,8 @@ load_relcache_init_file(bool shared) rel->rd_newRelfilenodeSubid = InvalidSubTransactionId; rel->rd_amcache = NULL; MemSet(&rel->pgstat_info, 0, sizeof(rel->pgstat_info)); + rel->rd_nowalrequirement = false; + rel->rd_walrequirement = NULL; /* * Recompute lock and physical addressing info. This is needed in diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 945ca50616..509394bb35 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -174,6 +174,7 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid); extern void simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup); +extern void heap_register_sync(Relation relation); extern void heap_sync(Relation relation); /* in heap/pruneheap.c */ diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h index 9f638be924..76178b87f2 100644 --- a/src/include/catalog/storage.h +++ b/src/include/catalog/storage.h @@ -22,6 +22,7 @@ extern void RelationCreateStorage(RelFileNode rnode, char relpersistence); extern void RelationDropStorage(Relation rel); extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit); extern void RelationTruncate(Relation rel, BlockNumber nblocks); +extern void RelationInvalidateWALRequirements(Relation rel); /* * These functions used to be in storage/smgr/smgr.c, which explains the @@ -29,6 +30,10 @@ extern void RelationTruncate(Relation rel, BlockNumber nblocks); */ extern void smgrDoPendingDeletes(bool isCommit); extern int smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr); +extern void smgrDoPendingSyncs(bool isCommit); +extern void smgrProcessWALRequirementInval(SubTransactionId sxid, bool isCommit); +extern void RecordWALSkipping(Relation rel); +bool BufferNeedsWAL(Relation rel, Buffer buf); extern void AtSubCommit_smgr(void); extern void AtSubAbort_smgr(void); extern void PostPrepare_smgr(void); diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h index c5826f691d..8a9ea041dd 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -189,6 +189,8 @@ extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum); extern void FlushOneBuffer(Buffer buffer); extern void FlushRelationBuffers(Relation rel); +extern void FlushRelationBuffersWithoutRelCache(RelFileNode rnode, + bool islocal); extern void FlushDatabaseBuffers(Oid dbid); extern void DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum, BlockNumber firstDelBlock); diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 54028515a7..30f0d5bd83 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -198,6 +198,14 @@ typedef struct RelationData /* use "struct" here to avoid needing to include pgstat.h: */ struct PgStat_TableStatus *pgstat_info; /* statistics collection area */ + + /* + * rd_nowalrequirement is true if this relation is known not to have + * special WAL requirements. Otherwise we need to ask smgr for an entry + * if rd_walrequirement is NULL. + */ + bool rd_nowalrequirement; + struct RelWalRequirement *rd_walrequirement; } RelationData; -- 2.16.3 ----Next_Part(Mon_Mar_25_21_32_04_2019_838)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v8-0005-Fix-WAL-skipping-feature.patch"