fix-wal-level-minimal-michael-horiguchi-3.patch
text/x-patch
Filename: fix-wal-level-minimal-michael-horiguchi-3.patch
Type: text/x-patch
Part: 0
Message:
Re: WAL logging problem in 9.4.3?
Patch
Format: context
| File | + | − |
|---|---|---|
| src/backend/access/heap/heapam.c | 23 | 0 |
| src/backend/access/heap/pruneheap.c | 2 | 0 |
| src/backend/access/heap/rewriteheap.c | 1 | 0 |
| src/backend/access/heap/visibilitymap.c | 2 | 0 |
| src/backend/access/transam/xact.c | 7 | 0 |
| src/backend/catalog/storage.c | 297 | 0 |
| src/backend/commands/copy.c | 6 | 0 |
| src/backend/commands/createas.c | 4 | 0 |
| src/backend/commands/matview.c | 2 | 0 |
| src/backend/commands/tablecmds.c | 2 | 0 |
| src/backend/commands/vacuumlazy.c | 3 | 0 |
| src/backend/storage/buffer/bufmgr.c | 31 | 0 |
| src/backend/utils/cache/relcache.c | 13 | 0 |
| src/include/access/heapam.h | 4 | 4 |
| src/include/catalog/storage.h | 4 | 0 |
| src/include/storage/bufmgr.h | 2 | 0 |
| src/include/utils/rel.h | 8 | 0 |
*** a/src/backend/access/heap/heapam.c
--- b/src/backend/access/heap/heapam.c
***************
*** 34,39 ****
--- 34,61 ----
* the POSTGRES heap access method used for all POSTGRES
* relations.
*
+ * WAL CONSIDERATIONS
+ * All heap operations are normally WAL-logged. but there are a few
+ * exceptions. Temporary and unlogged relations never need to be
+ * WAL-logged, but we can also skip WAL-logging for a table that was
+ * created in the same transaction, if we don't need WAL for PITR or
+ * WAL archival purposes (i.e. if wal_level=minimal), and we fsync()
+ * the file to disk at COMMIT instead.
+ *
+ * The same-relation optimization is not employed automatically on all
+ * updates to a table that was created in the same transacton, because
+ * for a small number of changes, it's cheaper to just create the WAL
+ * records than fsyncing() the whole relation at COMMIT. It is only
+ * worthwhile for (presumably) large operations like COPY, CLUSTER,
+ * or VACUUM FULL. Use heap_register_sync() to initiate such an
+ * operation; it will cause any subsequent updates to the table to skip
+ * WAL-logging, if possible, and cause the heap to be synced to disk at
+ * COMMIT.
+ *
+ * To make that work, all modifications to heap must use
+ * HeapNeedsWAL() to check if WAL-logging is needed in this transaction
+ * for the given block.
+ *
*-------------------------------------------------------------------------
*/
#include "postgres.h"
***************
*** 56,61 ****
--- 78,84 ----
#include "access/xlogutils.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
+ #include "catalog/storage.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "port/atomics.h"
***************
*** 2370,2381 **** ReleaseBulkInsertStatePin(BulkInsertState bistate)
* The new tuple is stamped with current transaction ID and the specified
* command ID.
*
- * If the HEAP_INSERT_SKIP_WAL option is specified, the new tuple is not
- * logged in WAL, even for a non-temp relation. Safe usage of this behavior
- * requires that we arrange that all new tuples go into new pages not
- * containing any tuples from other transactions, and that the relation gets
- * fsync'd before commit. (See also heap_sync() comments)
- *
* The HEAP_INSERT_SKIP_FSM option is passed directly to
* RelationGetBufferForTuple, which see for more info.
*
--- 2393,2398 ----
*** a/src/backend/access/heap/pruneheap.c
--- b/src/backend/access/heap/pruneheap.c
***************
*** 20,25 ****
--- 20,26 ----
#include "access/htup_details.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+ #include "catalog/storage.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
***************
*** 259,265 **** heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
/*
* Emit a WAL HEAP_CLEAN record showing what we did
*/
! if (RelationNeedsWAL(relation))
{
XLogRecPtr recptr;
--- 260,266 ----
/*
* Emit a WAL HEAP_CLEAN record showing what we did
*/
! if (BufferNeedsWAL(relation, buffer))
{
XLogRecPtr recptr;
*** a/src/backend/access/heap/rewriteheap.c
--- b/src/backend/access/heap/rewriteheap.c
***************
*** 649,657 **** raw_heap_insert(RewriteState state, HeapTuple tup)
}
else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD)
heaptup = toast_insert_or_update(state->rs_new_rel, tup, NULL,
! HEAP_INSERT_SKIP_FSM |
! (state->rs_use_wal ?
! 0 : HEAP_INSERT_SKIP_WAL));
else
heaptup = tup;
--- 649,655 ----
}
else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD)
heaptup = toast_insert_or_update(state->rs_new_rel, tup, NULL,
! HEAP_INSERT_SKIP_FSM);
else
heaptup = tup;
*** a/src/backend/access/heap/visibilitymap.c
--- b/src/backend/access/heap/visibilitymap.c
***************
*** 88,93 ****
--- 88,94 ----
#include "access/heapam_xlog.h"
#include "access/visibilitymap.h"
#include "access/xlog.h"
+ #include "catalog/storage.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
***************
*** 307,313 **** visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf,
map[mapByte] |= (flags << mapOffset);
MarkBufferDirty(vmBuf);
! if (RelationNeedsWAL(rel))
{
if (XLogRecPtrIsInvalid(recptr))
{
--- 308,314 ----
map[mapByte] |= (flags << mapOffset);
MarkBufferDirty(vmBuf);
! if (BufferNeedsWAL(rel, heapBuf))
{
if (XLogRecPtrIsInvalid(recptr))
{
*** a/src/backend/access/transam/xact.c
--- b/src/backend/access/transam/xact.c
***************
*** 2007,2012 **** CommitTransaction(void)
--- 2007,2015 ----
/* 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
***************
*** 2235,2240 **** PrepareTransaction(void)
--- 2238,2246 ----
/* 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
***************
*** 2548,2553 **** AbortTransaction(void)
--- 2554,2560 ----
AtAbort_Notify();
AtEOXact_RelationMap(false);
AtAbort_Twophase();
+ smgrDoPendingSyncs(false); /* abandone pending syncs */
/*
* Advertise the fact that we aborted in pg_xact (assuming that we got as
*** a/src/backend/catalog/storage.c
--- b/src/backend/catalog/storage.c
***************
*** 29,34 ****
--- 29,35 ----
#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"
***************
*** 64,69 **** typedef struct PendingRelDelete
--- 65,113 ----
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
+ * 'sync_above' is set to the current size of the relation. Any operations
+ * on blocks < sync_above 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 PendingRelSync
+ {
+ RelFileNode relnode; /* relation created in same xact */
+ BlockNumber sync_above; /* WAL-logging skipped for blocks >=
+ * sync_above */
+ BlockNumber truncated_to; /* truncation WAL record was written */
+ } PendingRelSync;
+
+ /* Relations that need to be fsync'd at commit */
+ static HTAB *pendingSyncs = NULL;
+
+ static void createPendingSyncsHash(void);
+
+ /*
* RelationCreateStorage
* Create physical storage for a relation.
*
***************
*** 226,231 **** RelationPreserveStorage(RelFileNode rnode, bool atCommit)
--- 270,277 ----
void
RelationTruncate(Relation rel, BlockNumber nblocks)
{
+ PendingRelSync *pending = NULL;
+ bool found;
bool fsm;
bool vm;
***************
*** 260,296 **** RelationTruncate(Relation rel, BlockNumber nblocks)
*/
if (RelationNeedsWAL(rel))
{
! /*
! * Make an XLOG entry reporting the file truncation.
! */
! XLogRecPtr lsn;
! xl_smgr_truncate xlrec;
!
! xlrec.blkno = nblocks;
! xlrec.rnode = rel->rd_node;
! xlrec.flags = SMGR_TRUNCATE_ALL;
!
! 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);
}
/* Do the real work */
smgrtruncate(rel->rd_smgr, MAIN_FORKNUM, nblocks);
}
/*
* smgrDoPendingDeletes() -- Take care of relation deletes at end of xact.
*
--- 306,386 ----
*/
if (RelationNeedsWAL(rel))
{
! /* no_pending_sync is ignored since new entry is created here */
! if (!rel->pending_sync)
! {
! if (!pendingSyncs)
! createPendingSyncsHash();
! elog(DEBUG2, "RelationTruncate: accessing hash");
! pending = (PendingRelSync *) hash_search(pendingSyncs,
! (void *) &rel->rd_node,
! HASH_ENTER, &found);
! if (!found)
! {
! pending->sync_above = InvalidBlockNumber;
! pending->truncated_to = InvalidBlockNumber;
! }
!
! rel->no_pending_sync= false;
! rel->pending_sync = pending;
! }
!
! if (rel->pending_sync->sync_above == InvalidBlockNumber ||
! rel->pending_sync->sync_above < nblocks)
! {
! /*
! * Make an XLOG entry reporting the file truncation.
! */
! XLogRecPtr lsn;
! xl_smgr_truncate xlrec;
!
! xlrec.blkno = nblocks;
! xlrec.rnode = rel->rd_node;
!
! XLogBeginInsert();
! XLogRegisterData((char *) &xlrec, sizeof(xlrec));
!
! lsn = XLogInsert(RM_SMGR_ID,
! XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
!
! elog(DEBUG2, "WAL-logged truncation of rel %u/%u/%u to %u blocks",
! rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
! nblocks);
!
! /*
! * 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);
!
! rel->pending_sync->truncated_to = nblocks;
! }
}
/* Do the real work */
smgrtruncate(rel->rd_smgr, MAIN_FORKNUM, nblocks);
}
+ /* create the hash table to track pending at-commit fsyncs */
+ static void
+ createPendingSyncsHash(void)
+ {
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(RelFileNode);
+ ctl.entrysize = sizeof(PendingRelSync);
+ ctl.hash = tag_hash;
+ pendingSyncs = hash_create("pending relation sync table", 5,
+ &ctl, HASH_ELEM | HASH_FUNCTION);
+ }
+
/*
* smgrDoPendingDeletes() -- Take care of relation deletes at end of xact.
*
***************
*** 369,374 **** smgrDoPendingDeletes(bool isCommit)
--- 459,482 ----
}
/*
+ * RelationRemovePendingSync() -- remove pendingSync entry for a relation
+ */
+ void
+ RelationRemovePendingSync(Relation rel)
+ {
+ bool found;
+
+ rel->pending_sync = NULL;
+ rel->no_pending_sync = true;
+ if (pendingSyncs)
+ {
+ elog(DEBUG2, "RelationRemovePendingSync: accessing hash");
+ hash_search(pendingSyncs, (void *) &rel->rd_node, HASH_REMOVE, &found);
+ }
+ }
+
+
+ /*
* smgrGetPendingDeletes() -- Get a list of non-temp relations to be deleted.
*
* The return value is the number of relations scheduled for termination.
***************
*** 419,424 **** smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr)
--- 527,696 ----
return nrels;
}
+
+ /*
+ * Remember that the given relation needs to be sync'd at commit, because we
+ * are going to skip WAL-logging subsequent actions to it.
+ */
+ void
+ RecordPendingSync(Relation rel)
+ {
+ bool found = true;
+ BlockNumber nblocks;
+
+ Assert(RelationNeedsWAL(rel));
+
+ /* ignore no_pending_sync since new entry is created here */
+ if (!rel->pending_sync)
+ {
+ if (!pendingSyncs)
+ createPendingSyncsHash();
+
+ /* Look up or create an entry */
+ rel->no_pending_sync = false;
+ elog(DEBUG2, "RecordPendingSync: accessing hash");
+ rel->pending_sync =
+ (PendingRelSync *) hash_search(pendingSyncs,
+ (void *) &rel->rd_node,
+ HASH_ENTER, &found);
+ }
+
+ nblocks = RelationGetNumberOfBlocks(rel);
+ if (!found)
+ {
+ rel->pending_sync->truncated_to = InvalidBlockNumber;
+ rel->pending_sync->sync_above = nblocks;
+
+ elog(DEBUG2,
+ "registering new pending sync for rel %u/%u/%u at block %u",
+ rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
+ nblocks);
+
+ }
+ else if (rel->pending_sync->sync_above == InvalidBlockNumber)
+ {
+ elog(DEBUG2, "registering pending sync for rel %u/%u/%u at block %u",
+ rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
+ nblocks);
+ rel->pending_sync->sync_above = nblocks;
+ }
+ else
+ elog(DEBUG2,
+ "pending sync for rel %u/%u/%u was already registered at block %u (new %u)",
+ rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
+ rel->pending_sync->sync_above, 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;
+
+ if (!RelationNeedsWAL(rel))
+ return false;
+
+ elog(DEBUG2, "BufferNeedsWAL(r %d, b %d): hash = %p, ent=%p, neg = %d", rel->rd_id, BufferGetBlockNumber(buf), pendingSyncs, rel->pending_sync, rel->no_pending_sync);
+ /* no further work if we know that we don't have pending sync */
+ if (!pendingSyncs || rel->no_pending_sync)
+ return true;
+
+ /* do the real work */
+ if (!rel->pending_sync)
+ {
+ bool found = false;
+
+ /*
+ * Hold the entry in rel. This relies on the fact that hash entry
+ * never moves.
+ */
+ rel->pending_sync =
+ (PendingRelSync *) hash_search(pendingSyncs,
+ (void *) &rel->rd_node,
+ HASH_FIND, &found);
+ elog(DEBUG2, "BufferNeedsWAL: accessing hash : %s", found ? "found" : "not found");
+ if (!found)
+ {
+ /* we don't have no one. don't access the hash no longer */
+ rel->no_pending_sync = true;
+ return true;
+ }
+ }
+
+ blkno = BufferGetBlockNumber(buf);
+ if (rel->pending_sync->sync_above == InvalidBlockNumber ||
+ rel->pending_sync->sync_above > blkno)
+ {
+ elog(DEBUG2, "not skipping WAL-logging for rel %u/%u/%u block %u, because sync_above is %u",
+ rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
+ blkno, rel->pending_sync->sync_above);
+ return true;
+ }
+
+ /*
+ * We have emitted a truncation record for this block.
+ */
+ if (rel->pending_sync->truncated_to != InvalidBlockNumber &&
+ rel->pending_sync->truncated_to <= blkno)
+ {
+ elog(DEBUG2, "not skipping WAL-logging for rel %u/%u/%u block %u, because it was truncated earlier in the same xact",
+ rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
+ blkno);
+ return true;
+ }
+
+ elog(DEBUG2, "skipping WAL-logging for rel %u/%u/%u block %u",
+ rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
+ blkno);
+
+ return false;
+ }
+
+ /*
+ * Sync to disk any relations that we skipped WAL-logging for earlier.
+ */
+ void
+ smgrDoPendingSyncs(bool isCommit)
+ {
+ if (!pendingSyncs)
+ return;
+
+ if (isCommit)
+ {
+ HASH_SEQ_STATUS status;
+ PendingRelSync *pending;
+
+ hash_seq_init(&status, pendingSyncs);
+
+ while ((pending = hash_seq_search(&status)) != NULL)
+ {
+ if (pending->sync_above != InvalidBlockNumber)
+ {
+ FlushRelationBuffersWithoutRelCache(pending->relnode, false);
+ smgrimmedsync(smgropen(pending->relnode, InvalidBackendId), MAIN_FORKNUM);
+
+ elog(DEBUG2, "syncing rel %u/%u/%u", pending->relnode.spcNode,
+ pending->relnode.dbNode, pending->relnode.relNode);
+ }
+ }
+ }
+
+ hash_destroy(pendingSyncs);
+ pendingSyncs = NULL;
+ }
+
/*
* PostPrepare_smgr -- Clean up after a successful PREPARE
*
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2347,2354 **** CopyFrom(CopyState cstate)
* - data is being written to relfilenode created in this transaction
* then we can skip writing WAL. It's safe because if the transaction
* doesn't commit, we'll discard the table (or the new relfilenode file).
! * If it does commit, we'll have done the heap_sync at the bottom of this
! * routine first.
*
* As mentioned in comments in utils/rel.h, the in-same-transaction test
* is not always set correctly, since in rare cases rd_newRelfilenodeSubid
--- 2347,2353 ----
* - data is being written to relfilenode created in this transaction
* then we can skip writing WAL. It's safe because if the transaction
* doesn't commit, we'll discard the table (or the new relfilenode file).
! * If it does commit, commit will do heap_sync().
*
* As mentioned in comments in utils/rel.h, the in-same-transaction test
* is not always set correctly, since in rare cases rd_newRelfilenodeSubid
***************
*** 2380,2386 **** CopyFrom(CopyState cstate)
{
hi_options |= HEAP_INSERT_SKIP_FSM;
if (!XLogIsNeeded())
! hi_options |= HEAP_INSERT_SKIP_WAL;
}
/*
--- 2379,2385 ----
{
hi_options |= HEAP_INSERT_SKIP_FSM;
if (!XLogIsNeeded())
! heap_register_sync(cstate->rel);
}
/*
***************
*** 2862,2872 **** CopyFrom(CopyState cstate)
FreeExecutorState(estate);
/*
! * If we skipped writing WAL, then we need to sync the heap (but not
! * indexes since those use WAL anyway)
*/
- if (hi_options & HEAP_INSERT_SKIP_WAL)
- heap_sync(cstate->rel);
return processed;
}
--- 2861,2871 ----
FreeExecutorState(estate);
/*
! * If we skipped writing WAL, then we will sync the heap at the end of
! * the transaction. (We used to do it here, but it was later found out
! * that to be safe, we must also avoid WAL-logging any subsequent
! * actions on the pages we skipped WAL for). Indexes always use WAL.
*/
return processed;
}
*** a/src/backend/commands/createas.c
--- b/src/backend/commands/createas.c
***************
*** 567,574 **** intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
* We can skip WAL-logging the insertions, unless PITR or streaming
* replication is in use. We can skip the FSM in any case.
*/
! myState->hi_options = HEAP_INSERT_SKIP_FSM |
! (XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL);
myState->bistate = GetBulkInsertState();
/* Not using WAL requires smgr_targblock be initially invalid */
--- 567,575 ----
* We can skip WAL-logging the insertions, unless PITR or streaming
* replication is in use. We can skip the FSM in any case.
*/
! if (!XLogIsNeeded())
! heap_register_sync(intoRelationDesc);
! myState->hi_options = HEAP_INSERT_SKIP_FSM;
myState->bistate = GetBulkInsertState();
/* Not using WAL requires smgr_targblock be initially invalid */
***************
*** 617,625 **** intorel_shutdown(DestReceiver *self)
FreeBulkInsertState(myState->bistate);
! /* If we skipped using WAL, must heap_sync before commit */
! if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
! heap_sync(myState->rel);
/* close rel, but keep lock until commit */
heap_close(myState->rel, NoLock);
--- 618,624 ----
FreeBulkInsertState(myState->bistate);
! /* If we skipped using WAL, we will sync the relation at commit */
/* close rel, but keep lock until commit */
heap_close(myState->rel, NoLock);
*** a/src/backend/commands/matview.c
--- b/src/backend/commands/matview.c
***************
*** 477,483 **** transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
*/
myState->hi_options = HEAP_INSERT_SKIP_FSM | HEAP_INSERT_FROZEN;
if (!XLogIsNeeded())
! myState->hi_options |= HEAP_INSERT_SKIP_WAL;
myState->bistate = GetBulkInsertState();
/* Not using WAL requires smgr_targblock be initially invalid */
--- 477,483 ----
*/
myState->hi_options = HEAP_INSERT_SKIP_FSM | HEAP_INSERT_FROZEN;
if (!XLogIsNeeded())
! heap_register_sync(transientrel);
myState->bistate = GetBulkInsertState();
/* Not using WAL requires smgr_targblock be initially invalid */
***************
*** 520,528 **** transientrel_shutdown(DestReceiver *self)
FreeBulkInsertState(myState->bistate);
! /* If we skipped using WAL, must heap_sync before commit */
! if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
! heap_sync(myState->transientrel);
/* close transientrel, but keep lock until commit */
heap_close(myState->transientrel, NoLock);
--- 520,526 ----
FreeBulkInsertState(myState->bistate);
! /* If we skipped using WAL, we will sync the relation at commit */
/* close transientrel, but keep lock until commit */
heap_close(myState->transientrel, NoLock);
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 4357,4364 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
bistate = GetBulkInsertState();
hi_options = HEAP_INSERT_SKIP_FSM;
if (!XLogIsNeeded())
! hi_options |= HEAP_INSERT_SKIP_WAL;
}
else
{
--- 4357,4365 ----
bistate = GetBulkInsertState();
hi_options = HEAP_INSERT_SKIP_FSM;
+
if (!XLogIsNeeded())
! heap_register_sync(newrel);
}
else
{
***************
*** 4624,4631 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
FreeBulkInsertState(bistate);
/* If we skipped writing WAL, then we need to sync the heap. */
- if (hi_options & HEAP_INSERT_SKIP_WAL)
- heap_sync(newrel);
heap_close(newrel, NoLock);
}
--- 4625,4630 ----
*** a/src/backend/commands/vacuumlazy.c
--- b/src/backend/commands/vacuumlazy.c
***************
*** 891,897 **** lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
* page has been previously WAL-logged, and if not, do that
* now.
*/
! if (RelationNeedsWAL(onerel) &&
PageGetLSN(page) == InvalidXLogRecPtr)
log_newpage_buffer(buf, true);
--- 891,897 ----
* page has been previously WAL-logged, and if not, do that
* now.
*/
! if (BufferNeedsWAL(onerel, buf) &&
PageGetLSN(page) == InvalidXLogRecPtr)
log_newpage_buffer(buf, true);
***************
*** 1118,1124 **** lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
}
/* Now WAL-log freezing if necessary */
! if (RelationNeedsWAL(onerel))
{
XLogRecPtr recptr;
--- 1118,1124 ----
}
/* Now WAL-log freezing if necessary */
! if (BufferNeedsWAL(onerel, buf))
{
XLogRecPtr recptr;
***************
*** 1476,1482 **** lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
MarkBufferDirty(buffer);
/* XLOG stuff */
! if (RelationNeedsWAL(onerel))
{
XLogRecPtr recptr;
--- 1476,1482 ----
MarkBufferDirty(buffer);
/* XLOG stuff */
! if (BufferNeedsWAL(onerel, buffer))
{
XLogRecPtr recptr;
*** a/src/backend/storage/buffer/bufmgr.c
--- b/src/backend/storage/buffer/bufmgr.c
***************
*** 451,456 **** static BufferDesc *BufferAlloc(SMgrRelation smgr,
--- 451,457 ----
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);
***************
*** 3147,3166 **** 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))
{
for (i = 0; i < NLocBuffer; i++)
{
uint32 buf_state;
bufHdr = GetLocalBufferDescriptor(i);
! if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
(BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
--- 3148,3188 ----
void
FlushRelationBuffers(Relation rel)
{
/* Open rel at the smgr level if not already done */
RelationOpenSmgr(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, rnode) &&
((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
(BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
***************
*** 3177,3183 **** FlushRelationBuffers(Relation rel)
PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
! smgrwrite(rel->rd_smgr,
bufHdr->tag.forkNum,
bufHdr->tag.blockNum,
localpage,
--- 3199,3205 ----
PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
! smgrwrite(smgr,
bufHdr->tag.forkNum,
bufHdr->tag.blockNum,
localpage,
***************
*** 3207,3224 **** FlushRelationBuffers(Relation rel)
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
! if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
! if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
{
PinBuffer_Locked(bufHdr);
LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_SHARED);
! FlushBuffer(bufHdr, rel->rd_smgr);
LWLockRelease(BufferDescriptorGetContentLock(bufHdr));
UnpinBuffer(bufHdr, true);
}
--- 3229,3246 ----
* As in DropRelFileNodeBuffers, an unlocked precheck should be safe
* and saves some cycles.
*/
! if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode))
continue;
ReservePrivateRefCountEntry();
buf_state = LockBufHdr(bufHdr);
! 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, smgr);
LWLockRelease(BufferDescriptorGetContentLock(bufHdr));
UnpinBuffer(bufHdr, true);
}
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 72,77 ****
--- 72,78 ----
#include "optimizer/var.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"
***************
*** 418,423 **** AllocateRelationDesc(Form_pg_class relp)
--- 419,428 ----
/* which we mark as a reference-counted tupdesc */
relation->rd_att->tdrefcount = 1;
+ /* We don't know if pending sync for this relation exists so far */
+ relation->pending_sync = NULL;
+ relation->no_pending_sync = false;
+
MemoryContextSwitchTo(oldcxt);
return relation;
***************
*** 2040,2045 **** formrdesc(const char *relationName, Oid relationReltype,
--- 2045,2054 ----
relation->rd_rel->relhasindex = true;
}
+ /* We don't know if pending sync for this relation exists so far */
+ relation->pending_sync = NULL;
+ relation->no_pending_sync = false;
+
/*
* add new reldesc to relcache
*/
***************
*** 3364,3369 **** RelationBuildLocalRelation(const char *relname,
--- 3373,3382 ----
else
rel->rd_rel->relfilenode = relfilenode;
+ /* newly built relation has no pending sync */
+ rel->no_pending_sync = true;
+ rel->pending_sync = NULL;
+
RelationInitLockInfo(rel); /* see lmgr.c */
RelationInitPhysicalAddr(rel);
*** a/src/include/access/heapam.h
--- b/src/include/access/heapam.h
***************
*** 25,34 ****
/* "options" flag bits for heap_insert */
! #define HEAP_INSERT_SKIP_WAL 0x0001
! #define HEAP_INSERT_SKIP_FSM 0x0002
! #define HEAP_INSERT_FROZEN 0x0004
! #define HEAP_INSERT_SPECULATIVE 0x0008
typedef struct BulkInsertStateData *BulkInsertState;
--- 25,33 ----
/* "options" flag bits for heap_insert */
! #define HEAP_INSERT_SKIP_FSM 0x0001
! #define HEAP_INSERT_FROZEN 0x0002
! #define HEAP_INSERT_SPECULATIVE 0x0004
typedef struct BulkInsertStateData *BulkInsertState;
***************
*** 179,184 **** extern void simple_heap_delete(Relation relation, ItemPointer tid);
--- 178,184 ----
extern void simple_heap_update(Relation relation, ItemPointer otid,
HeapTuple tup);
+ extern void heap_register_sync(Relation relation);
extern void heap_sync(Relation relation);
extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
*** a/src/include/catalog/storage.h
--- b/src/include/catalog/storage.h
***************
*** 22,34 **** 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);
!
/*
* These functions used to be in storage/smgr/smgr.c, which explains the
* naming
*/
extern void smgrDoPendingDeletes(bool isCommit);
extern int smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr);
extern void AtSubCommit_smgr(void);
extern void AtSubAbort_smgr(void);
extern void PostPrepare_smgr(void);
--- 22,37 ----
extern void RelationDropStorage(Relation rel);
extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
extern void RelationTruncate(Relation rel, BlockNumber nblocks);
! extern void RelationRemovePendingSync(Relation rel);
/*
* These functions used to be in storage/smgr/smgr.c, which explains the
* naming
*/
extern void smgrDoPendingDeletes(bool isCommit);
extern int smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr);
+ extern void smgrDoPendingSyncs(bool isCommit);
+ extern void RecordPendingSync(Relation rel);
+ bool BufferNeedsWAL(Relation rel, Buffer buf);
extern void AtSubCommit_smgr(void);
extern void AtSubAbort_smgr(void);
extern void PostPrepare_smgr(void);
*** a/src/include/storage/bufmgr.h
--- b/src/include/storage/bufmgr.h
***************
*** 190,195 **** extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation,
--- 190,197 ----
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);
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
***************
*** 216,221 **** typedef struct RelationData
--- 216,229 ----
/* use "struct" here to avoid needing to include pgstat.h: */
struct PgStat_TableStatus *pgstat_info; /* statistics collection area */
+
+ /*
+ * no_pending_sync is true if this relation is known not to have pending
+ * syncs. Elsewise searching for registered sync is required if
+ * pending_sync is NULL.
+ */
+ bool no_pending_sync;
+ struct PendingRelSync *pending_sync;
} RelationData;