agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH 4/4] Fix WAL skipping feature. 19+ messages / 4 participants [nested] [flat]
* [PATCH 5/8] Fix WAL skipping feature. @ 2018-10-11 07:18 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2018-10-11 07:18 UTC (permalink / raw) This patch replaces WAL-skipping means from HEAP_INSERT_SKIP_WAL to pending-sync tracking infrastructure. --- src/backend/access/heap/heapam.c | 109 ++++++++++++++++++++++++-------- src/backend/access/heap/pruneheap.c | 3 +- src/backend/access/heap/rewriteheap.c | 3 - src/backend/access/heap/vacuumlazy.c | 6 +- src/backend/access/heap/visibilitymap.c | 3 +- src/backend/commands/copy.c | 13 ++-- src/backend/commands/createas.c | 9 ++- src/backend/commands/matview.c | 6 +- src/backend/commands/tablecmds.c | 6 +- src/include/access/heapam.h | 3 +- src/include/access/tableam.h | 11 +--- 11 files changed, 106 insertions(+), 66 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 5a8627507f..00416c4a99 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -28,6 +28,27 @@ * 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 transaction, because for + * a small number of changes, it's cheaper to just create the WAL records + * than fsync()ing 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 + * BufferNeedsWAL() to check if WAL-logging is needed in this transaction + * for the given block. + * *------------------------------------------------------------------------- */ #include "postgres.h" @@ -1934,7 +1955,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; @@ -2044,7 +2065,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, int ndone; PGAlignedBlock scratch; Page page; - bool needwal; Size saveFreeSpace; bool need_tuple_data = RelationIsLogicallyLogged(relation); bool need_cids = RelationIsAccessibleInLogicalDecoding(relation); @@ -2052,7 +2072,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); @@ -2094,6 +2113,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, Buffer vmbuffer = InvalidBuffer; bool all_visible_cleared = false; int nthispage; + bool needwal; CHECK_FOR_INTERRUPTS(); @@ -2105,6 +2125,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, InvalidBuffer, options, bistate, &vmbuffer, NULL); page = BufferGetPage(buffer); + needwal = BufferNeedsWAL(relation, buffer); /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); @@ -2657,7 +2678,7 @@ l1: * NB: heap_abort_speculative() uses the same xlog record and replay * routines. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; @@ -2791,6 +2812,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, vmbuffer = InvalidBuffer, vmbuffer_new = InvalidBuffer; bool need_toast; + bool oldbuf_needs_wal, + newbuf_needs_wal; Size newtupsize, pagefree; bool have_tuple_lock = false; @@ -3342,7 +3365,7 @@ l2: MarkBufferDirty(buffer); - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -3556,8 +3579,20 @@ l2: MarkBufferDirty(newbuf); MarkBufferDirty(buffer); - /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + /* + * XLOG stuff + * + * Emit heap-update log. When wal_level = minimal, we may emit insert or + * delete record according to wal-optimization. + */ + oldbuf_needs_wal = BufferNeedsWAL(relation, buffer); + + if (newbuf == buffer) + newbuf_needs_wal = oldbuf_needs_wal; + else + newbuf_needs_wal = BufferNeedsWAL(relation, newbuf); + + if (oldbuf_needs_wal || newbuf_needs_wal) { XLogRecPtr recptr; @@ -3567,15 +3602,26 @@ l2: */ if (RelationIsAccessibleInLogicalDecoding(relation)) { - log_heap_new_cid(relation, &oldtup); - log_heap_new_cid(relation, heaptup); + if (oldbuf_needs_wal) + log_heap_new_cid(relation, &oldtup); + if (newbuf_needs_wal) + log_heap_new_cid(relation, heaptup); } - recptr = log_heap_update(relation, buffer, - newbuf, &oldtup, heaptup, - old_key_tuple, - all_visible_cleared, - all_visible_cleared_new); + if (oldbuf_needs_wal && newbuf_needs_wal) + recptr = log_heap_update(relation, buffer, newbuf, + &oldtup, heaptup, + old_key_tuple, + all_visible_cleared, + all_visible_cleared_new); + else if (oldbuf_needs_wal) + recptr = log_heap_delete(relation, buffer, &oldtup, old_key_tuple, + xmax_old_tuple, false, + all_visible_cleared); + else + recptr = log_heap_insert(relation, buffer, newtup, + 0, all_visible_cleared_new); + if (newbuf != buffer) { PageSetLSN(BufferGetPage(newbuf), recptr); @@ -4453,7 +4499,7 @@ failed: * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG * entries for everything anyway.) */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, *buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -5205,7 +5251,7 @@ l4: MarkBufferDirty(buf); /* XLOG stuff */ - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, buf)) { xl_heap_lock_updated xlrec; XLogRecPtr recptr; @@ -5365,7 +5411,7 @@ heap_finish_speculative(Relation relation, ItemPointer tid) htup->t_ctid = *tid; /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_confirm xlrec; XLogRecPtr recptr; @@ -5497,7 +5543,7 @@ heap_abort_speculative(Relation relation, ItemPointer tid) * The WAL records generated here match heap_delete(). The same recovery * routines are used. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -5606,7 +5652,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple) MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_inplace xlrec; XLogRecPtr recptr; @@ -6802,8 +6848,8 @@ log_heap_clean(Relation reln, Buffer buffer, xl_heap_clean xlrec; XLogRecPtr recptr; - /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + /* Caller should not call me on non-WAL-logged buffers */ + Assert(BufferNeedsWAL(reln, buffer)); xlrec.latestRemovedXid = latestRemovedXid; xlrec.nredirected = nredirected; @@ -6850,8 +6896,8 @@ log_heap_freeze(Relation reln, Buffer buffer, TransactionId cutoff_xid, xl_heap_freeze_page xlrec; XLogRecPtr recptr; - /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + /* Caller should not call me on non-WAL-logged buffers */ + Assert(BufferNeedsWAL(reln, buffer)); /* nor when there are no tuples to freeze */ Assert(ntuples > 0); @@ -7077,8 +7123,8 @@ log_heap_update(Relation reln, Buffer oldbuf, bool init; int bufflags; - /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + /* Caller should not call me when no buffer needs WAL-logging */ + Assert(BufferNeedsWAL(reln, newbuf) || BufferNeedsWAL(reln, oldbuf)); XLogBeginInsert(); @@ -8682,9 +8728,16 @@ heap2_redo(XLogReaderState *record) * heap_sync - sync a heap, for use when no WAL has been written * * This forces the heap contents (including TOAST heap if any) down to disk. - * If we skipped using WAL, and WAL is otherwise needed, we must force the - * relation down to disk before it's safe to commit the transaction. This - * requires writing out any dirty buffers and then doing a forced fsync. + * If we did any changes to the heap bypassing the buffer manager, we must + * force the relation down to disk before it's safe to commit the + * transaction, because the direct modifications will not be flushed by + * the next checkpoint. + * + * We used to also use this after batch operations like COPY and CLUSTER, + * if we skipped using WAL and WAL is otherwise needed, but there were + * corner-cases involving other WAL-logged operations to the same + * relation, where that was not enough. heap_register_sync() should be + * used for that purpose instead. * * Indexes are not touched. (Currently, index operations associated with * the commands that use this are WAL-logged and so do not need fsync. diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index a3e51922d8..a05659b168 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -20,6 +20,7 @@ #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" @@ -258,7 +259,7 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin, /* * Emit a WAL HEAP_CLEAN record showing what we did */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index bce4274362..1ac77f7c14 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -654,9 +654,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 5c554f9465..3f5df63df8 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -929,7 +929,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, * page has been previously WAL-logged, and if not, do that * now. */ - if (RelationNeedsWAL(onerel) && + if (BufferNeedsWAL(onerel, buf) && PageGetLSN(page) == InvalidXLogRecPtr) log_newpage_buffer(buf, true); @@ -1193,7 +1193,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, } /* Now WAL-log freezing if necessary */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buf)) { XLogRecPtr recptr; @@ -1575,7 +1575,7 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 64dfe06b26..1f5f7d92dd 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -88,6 +88,7 @@ #include "access/heapam_xlog.h" #include "access/visibilitymap.h" #include "access/xlog.h" +#include "catalog/storage.h" #include "miscadmin.h" #include "port/pg_bitutils.h" #include "storage/bufmgr.h" @@ -276,7 +277,7 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, map[mapByte] |= (flags << mapOffset); MarkBufferDirty(vmBuf); - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, heapBuf)) { if (XLogRecPtrIsInvalid(recptr)) { diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 705df8900b..1074320a5a 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2391,8 +2391,7 @@ 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. + * 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 @@ -2438,7 +2437,7 @@ CopyFrom(CopyState cstate) { hi_options |= HEAP_INSERT_SKIP_FSM; if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(cstate->rel); } /* @@ -3091,11 +3090,11 @@ 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 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. */ - if (hi_options & HEAP_INSERT_SKIP_WAL) - heap_sync(cstate->rel); return processed; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 3bdb67c697..b4431f2af3 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -558,8 +558,9 @@ 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); + 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 */ @@ -604,9 +605,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 5b2cbc7c89..45e693129d 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -463,7 +463,7 @@ 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; + heap_register_sync(transientrel); myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -508,9 +508,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c9a0e02168..54ce52eaae 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4664,10 +4664,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { mycid = GetCurrentCommandId(true); bistate = GetBulkInsertState(); - hi_options = HEAP_INSERT_SKIP_FSM; + if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(newrel); } else { @@ -4958,8 +4958,6 @@ 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); table_close(newrel, NoLock); } diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 3d4fb7f3c3..97114aed3e 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -29,11 +29,10 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL #define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM #define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN #define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL -#define HEAP_INSERT_SPECULATIVE 0x0010 +#define HEAP_INSERT_SPECULATIVE 0x0008 typedef struct BulkInsertStateData *BulkInsertState; diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 4699335cdf..cf7f8e7da0 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -94,10 +94,9 @@ typedef struct TM_FailureData } TM_FailureData; /* "options" flag bits for table_insert */ -#define TABLE_INSERT_SKIP_WAL 0x0001 -#define TABLE_INSERT_SKIP_FSM 0x0002 -#define TABLE_INSERT_FROZEN 0x0004 -#define TABLE_INSERT_NO_LOGICAL 0x0008 +#define TABLE_INSERT_SKIP_FSM 0x0001 +#define TABLE_INSERT_FROZEN 0x0002 +#define TABLE_INSERT_NO_LOGICAL 0x0004 /* flag bits fortable_lock_tuple */ /* Follow tuples whose update is in progress if lock modes don't conflict */ @@ -702,10 +701,6 @@ table_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, Snapshot snap * behaviour of the AM. Several options might be ignored by AMs not supporting * them. * - * If the TABLE_INSERT_SKIP_WAL option is specified, the new tuple will not - * necessarily logged to WAL, even for a non-temp relation. It is the AMs - * choice whether this optimization is supported. - * * If the TABLE_INSERT_SKIP_FSM option is specified, AMs are free to not reuse * free space in the relation. This can save some cycles when we know the * relation is new and doesn't contain useful amounts of free space. It's -- 2.16.3 ----Next_Part(Tue_Mar_26_16_35_07_2019_128)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v9-0006-Change-cluster-to-use-the-new-pending-sync-infrastru.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 5/7] Fix WAL skipping feature. @ 2018-10-11 07:18 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2018-10-11 07:18 UTC (permalink / raw) This patch replaces WAL-skipping means from HEAP_INSERT_SKIP_WAL to pending-sync tracking infrastructure. --- src/backend/access/heap/heapam.c | 104 ++++++++++++++++++++++++-------- src/backend/access/heap/pruneheap.c | 3 +- src/backend/access/heap/rewriteheap.c | 3 - src/backend/access/heap/vacuumlazy.c | 6 +- src/backend/access/heap/visibilitymap.c | 3 +- src/backend/commands/copy.c | 13 ++-- src/backend/commands/createas.c | 9 ++- src/backend/commands/matview.c | 6 +- src/backend/commands/tablecmds.c | 5 +- src/include/access/heapam.h | 3 +- src/include/access/tableam.h | 11 +--- 11 files changed, 104 insertions(+), 62 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 024620ddc1..96f2cde3ce 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -28,6 +28,28 @@ * 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 + * BufferNeedsWAL() to check if WAL-logging is needed in this transaction + * for the given block. + * *------------------------------------------------------------------------- */ #include "postgres.h" @@ -1963,7 +1985,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; @@ -2073,7 +2095,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, int ndone; PGAlignedBlock scratch; Page page; - bool needwal; Size saveFreeSpace; bool need_tuple_data = RelationIsLogicallyLogged(relation); bool need_cids = RelationIsAccessibleInLogicalDecoding(relation); @@ -2081,7 +2102,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); @@ -2123,6 +2143,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, Buffer vmbuffer = InvalidBuffer; bool all_visible_cleared = false; int nthispage; + bool needwal; CHECK_FOR_INTERRUPTS(); @@ -2134,6 +2155,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, InvalidBuffer, options, bistate, &vmbuffer, NULL); page = BufferGetPage(buffer); + needwal = BufferNeedsWAL(relation, buffer); /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); @@ -2686,7 +2708,7 @@ l1: * NB: heap_abort_speculative() uses the same xlog record and replay * routines. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; @@ -2820,6 +2842,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, vmbuffer = InvalidBuffer, vmbuffer_new = InvalidBuffer; bool need_toast; + bool oldbuf_needs_wal, + newbuf_needs_wal; Size newtupsize, pagefree; bool have_tuple_lock = false; @@ -3371,7 +3395,7 @@ l2: MarkBufferDirty(buffer); - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -3585,8 +3609,20 @@ l2: MarkBufferDirty(newbuf); MarkBufferDirty(buffer); - /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + /* + * XLOG stuff + * + * Emit heap-update log. When wal_level = minimal, we may emit insert or + * delete record according to wal-optimization. + */ + oldbuf_needs_wal = BufferNeedsWAL(relation, buffer); + + if (newbuf == buffer) + newbuf_needs_wal = oldbuf_needs_wal; + else + newbuf_needs_wal = BufferNeedsWAL(relation, newbuf); + + if (oldbuf_needs_wal || newbuf_needs_wal) { XLogRecPtr recptr; @@ -3596,15 +3632,26 @@ l2: */ if (RelationIsAccessibleInLogicalDecoding(relation)) { - log_heap_new_cid(relation, &oldtup); - log_heap_new_cid(relation, heaptup); + if (oldbuf_needs_wal) + log_heap_new_cid(relation, &oldtup); + if (newbuf_needs_wal) + log_heap_new_cid(relation, heaptup); } - recptr = log_heap_update(relation, buffer, - newbuf, &oldtup, heaptup, - old_key_tuple, - all_visible_cleared, - all_visible_cleared_new); + if (oldbuf_needs_wal && newbuf_needs_wal) + recptr = log_heap_update(relation, buffer, newbuf, + &oldtup, heaptup, + old_key_tuple, + all_visible_cleared, + all_visible_cleared_new); + else if (oldbuf_needs_wal) + recptr = log_heap_delete(relation, buffer, &oldtup, old_key_tuple, + xmax_old_tuple, false, + all_visible_cleared); + else + recptr = log_heap_insert(relation, buffer, newtup, + 0, all_visible_cleared_new); + if (newbuf != buffer) { PageSetLSN(BufferGetPage(newbuf), recptr); @@ -4482,7 +4529,7 @@ failed: * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG * entries for everything anyway.) */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, *buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -5234,7 +5281,7 @@ l4: MarkBufferDirty(buf); /* XLOG stuff */ - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, buf)) { xl_heap_lock_updated xlrec; XLogRecPtr recptr; @@ -5394,7 +5441,7 @@ heap_finish_speculative(Relation relation, ItemPointer tid) htup->t_ctid = *tid; /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_confirm xlrec; XLogRecPtr recptr; @@ -5526,7 +5573,7 @@ heap_abort_speculative(Relation relation, ItemPointer tid) * The WAL records generated here match heap_delete(). The same recovery * routines are used. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -5635,7 +5682,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple) MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_inplace xlrec; XLogRecPtr recptr; @@ -6832,7 +6879,7 @@ log_heap_clean(Relation reln, Buffer buffer, XLogRecPtr recptr; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, buffer)); xlrec.latestRemovedXid = latestRemovedXid; xlrec.nredirected = nredirected; @@ -6880,7 +6927,7 @@ log_heap_freeze(Relation reln, Buffer buffer, TransactionId cutoff_xid, XLogRecPtr recptr; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, buffer)); /* nor when there are no tuples to freeze */ Assert(ntuples > 0); @@ -7107,7 +7154,7 @@ log_heap_update(Relation reln, Buffer oldbuf, int bufflags; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, newbuf) || BufferNeedsWAL(reln, oldbuf)); XLogBeginInsert(); @@ -8711,9 +8758,16 @@ heap2_redo(XLogReaderState *record) * heap_sync - sync a heap, for use when no WAL has been written * * This forces the heap contents (including TOAST heap if any) down to disk. - * If we skipped using WAL, and WAL is otherwise needed, we must force the - * relation down to disk before it's safe to commit the transaction. This - * requires writing out any dirty buffers and then doing a forced fsync. + * If we did any changes to the heap bypassing the buffer manager, we must + * force the relation down to disk before it's safe to commit the + * transaction, because the direct modifications will not be flushed by + * the next checkpoint. + * + * We used to also use this after batch operations like COPY and CLUSTER, + * if we skipped using WAL and WAL is otherwise needed, but there were + * corner-cases involving other WAL-logged operations to the same + * relation, where that was not enough. heap_register_sync() should be + * used for that purpose instead. * * Indexes are not touched. (Currently, index operations associated with * the commands that use this are WAL-logged and so do not need fsync. diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index a3e51922d8..a05659b168 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -20,6 +20,7 @@ #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" @@ -258,7 +259,7 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin, /* * Emit a WAL HEAP_CLEAN record showing what we did */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index bce4274362..1ac77f7c14 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -654,9 +654,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 5c554f9465..3f5df63df8 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -929,7 +929,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, * page has been previously WAL-logged, and if not, do that * now. */ - if (RelationNeedsWAL(onerel) && + if (BufferNeedsWAL(onerel, buf) && PageGetLSN(page) == InvalidXLogRecPtr) log_newpage_buffer(buf, true); @@ -1193,7 +1193,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, } /* Now WAL-log freezing if necessary */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buf)) { XLogRecPtr recptr; @@ -1575,7 +1575,7 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 64dfe06b26..1f5f7d92dd 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -88,6 +88,7 @@ #include "access/heapam_xlog.h" #include "access/visibilitymap.h" #include "access/xlog.h" +#include "catalog/storage.h" #include "miscadmin.h" #include "port/pg_bitutils.h" #include "storage/bufmgr.h" @@ -276,7 +277,7 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, map[mapByte] |= (flags << mapOffset); MarkBufferDirty(vmBuf); - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, heapBuf)) { if (XLogRecPtrIsInvalid(recptr)) { diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 705df8900b..1074320a5a 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2391,8 +2391,7 @@ 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. + * 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 @@ -2438,7 +2437,7 @@ CopyFrom(CopyState cstate) { hi_options |= HEAP_INSERT_SKIP_FSM; if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(cstate->rel); } /* @@ -3091,11 +3090,11 @@ 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 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. */ - if (hi_options & HEAP_INSERT_SKIP_WAL) - heap_sync(cstate->rel); return processed; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 36e3d44aad..8cba15fd3c 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -557,8 +557,9 @@ 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); + 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 */ @@ -604,9 +605,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 5a47be4b33..5f447c6d94 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -463,7 +463,7 @@ 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; + heap_register_sync(transientrel); myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,9 +509,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 45bb0b5614..242311b0d7 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4666,8 +4666,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); hi_options = HEAP_INSERT_SKIP_FSM; + if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(newrel); } else { @@ -4958,8 +4959,6 @@ 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); table_close(newrel, NoLock); } diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 509394bb35..a9aec90e86 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -29,11 +29,10 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL #define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM #define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN #define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL -#define HEAP_INSERT_SPECULATIVE 0x0010 +#define HEAP_INSERT_SPECULATIVE 0x0008 typedef struct BulkInsertStateData *BulkInsertState; diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index c2baa9d7a8..268e672470 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -94,10 +94,9 @@ typedef struct TM_FailureData } TM_FailureData; /* "options" flag bits for table_insert */ -#define TABLE_INSERT_SKIP_WAL 0x0001 -#define TABLE_INSERT_SKIP_FSM 0x0002 -#define TABLE_INSERT_FROZEN 0x0004 -#define TABLE_INSERT_NO_LOGICAL 0x0008 +#define TABLE_INSERT_SKIP_FSM 0x0001 +#define TABLE_INSERT_FROZEN 0x0002 +#define TABLE_INSERT_NO_LOGICAL 0x0004 /* flag bits fortable_lock_tuple */ /* Follow tuples whose update is in progress if lock modes don't conflict */ @@ -634,10 +633,6 @@ table_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, Snapshot snap * behaviour of the AM. Several options might be ignored by AMs not supporting * them. * - * If the TABLE_INSERT_SKIP_WAL option is specified, the new tuple will not - * necessarily logged to WAL, even for a non-temp relation. It is the AMs - * choice whether this optimization is supported. - * * If the TABLE_INSERT_SKIP_FSM option is specified, AMs are free to not reuse * free space in the relation. This can save some cycles when we know the * relation is new and doesn't contain useful amounts of free space. It's -- 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-0006-Change-cluster-to-use-the-new-pending-sync-infrastru.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 4/4] Fix WAL skipping feature. @ 2018-10-11 07:18 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2018-10-11 07:18 UTC (permalink / raw) This patch repalces WAL-skipping means from HEAP_INSERT_SKIP_WAL to pending-sync tracking infrastructure. --- src/backend/access/heap/heapam.c | 70 ++++++++++++++++++++++----------- src/backend/access/heap/pruneheap.c | 3 +- src/backend/access/heap/rewriteheap.c | 3 -- src/backend/access/heap/visibilitymap.c | 3 +- src/backend/commands/copy.c | 13 +++--- src/backend/commands/createas.c | 9 ++--- src/backend/commands/matview.c | 6 +-- src/backend/commands/tablecmds.c | 5 +-- src/backend/commands/vacuumlazy.c | 6 +-- src/include/access/heapam.h | 9 ++--- 10 files changed, 72 insertions(+), 55 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 8f1ea73541..c9c254a032 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -34,6 +34,28 @@ * 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 + * BufferNeedsWAL() to check if WAL-logging is needed in this transaction + * for the given block. + * *------------------------------------------------------------------------- */ #include "postgres.h" @@ -2414,12 +2436,6 @@ 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. * @@ -2528,7 +2544,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2704,7 +2720,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, int ndone; PGAlignedBlock scratch; Page page; - bool needwal; Size saveFreeSpace; bool need_tuple_data = RelationIsLogicallyLogged(relation); bool need_cids = RelationIsAccessibleInLogicalDecoding(relation); @@ -2712,7 +2727,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); @@ -2754,6 +2768,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, Buffer vmbuffer = InvalidBuffer; bool all_visible_cleared = false; int nthispage; + bool needwal; CHECK_FOR_INTERRUPTS(); @@ -2765,6 +2780,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, InvalidBuffer, options, bistate, &vmbuffer, NULL); page = BufferGetPage(buffer); + needwal = BufferNeedsWAL(relation, buffer); /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); @@ -3327,7 +3343,7 @@ l1: * NB: heap_abort_speculative() uses the same xlog record and replay * routines. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -4069,7 +4085,7 @@ l2: MarkBufferDirty(buffer); - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -4291,7 +4307,8 @@ l2: MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer) || + BufferNeedsWAL(relation, newbuf)) { XLogRecPtr recptr; @@ -5263,7 +5280,7 @@ failed: * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG * entries for everything anyway.) */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, *buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -6007,7 +6024,7 @@ l4: MarkBufferDirty(buf); /* XLOG stuff */ - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, buf)) { xl_heap_lock_updated xlrec; XLogRecPtr recptr; @@ -6167,7 +6184,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) htup->t_ctid = tuple->t_self; /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_confirm xlrec; XLogRecPtr recptr; @@ -6300,7 +6317,7 @@ heap_abort_speculative(Relation relation, HeapTuple tuple) * The WAL records generated here match heap_delete(). The same recovery * routines are used. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -6409,7 +6426,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple) MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_inplace xlrec; XLogRecPtr recptr; @@ -7605,7 +7622,7 @@ log_heap_clean(Relation reln, Buffer buffer, XLogRecPtr recptr; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, buffer)); xlrec.latestRemovedXid = latestRemovedXid; xlrec.nredirected = nredirected; @@ -7653,7 +7670,7 @@ log_heap_freeze(Relation reln, Buffer buffer, TransactionId cutoff_xid, XLogRecPtr recptr; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, buffer)); /* nor when there are no tuples to freeze */ Assert(ntuples > 0); @@ -7738,7 +7755,7 @@ log_heap_update(Relation reln, Buffer oldbuf, int bufflags; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, newbuf) || BufferNeedsWAL(reln, oldbuf)); XLogBeginInsert(); @@ -9342,9 +9359,16 @@ heap2_redo(XLogReaderState *record) * heap_sync - sync a heap, for use when no WAL has been written * * This forces the heap contents (including TOAST heap if any) down to disk. - * If we skipped using WAL, and WAL is otherwise needed, we must force the - * relation down to disk before it's safe to commit the transaction. This - * requires writing out any dirty buffers and then doing a forced fsync. + * If we did any changes to the heap bypassing the buffer manager, we must + * force the relation down to disk before it's safe to commit the + * transaction, because the direct modifications will not be flushed by + * the next checkpoint. + * + * We used to also use this after batch operations like COPY and CLUSTER, + * if we skipped using WAL and WAL is otherwise needed, but there were + * corner-cases involving other WAL-logged operations to the same + * relation, where that was not enough. heap_register_sync() should be + * used for that purpose instead. * * Indexes are not touched. (Currently, index operations associated with * the commands that use this are WAL-logged and so do not need fsync. diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index c2f5343dac..d0b68902d9 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -20,6 +20,7 @@ #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,7 +260,7 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin, /* * Emit a WAL HEAP_CLEAN record showing what we did */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 44caeca336..ecddc40329 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -655,9 +655,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 695567b4b0..fce14ce35f 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -88,6 +88,7 @@ #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,7 +308,7 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, map[mapByte] |= (flags << mapOffset); MarkBufferDirty(vmBuf); - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, heapBuf)) { if (XLogRecPtrIsInvalid(recptr)) { diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 4311e16007..d583b5a8a3 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2364,8 +2364,7 @@ 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. + * 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 @@ -2406,7 +2405,7 @@ CopyFrom(CopyState cstate) { hi_options |= HEAP_INSERT_SKIP_FSM; if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(cstate->rel); } /* @@ -3036,11 +3035,11 @@ 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 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. */ - if (hi_options & HEAP_INSERT_SKIP_WAL) - heap_sync(cstate->rel); return processed; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index d01b258b65..3d32d07d69 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -555,8 +555,9 @@ 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); + 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 */ @@ -599,9 +600,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close rel, but keep lock until commit */ heap_close(myState->rel, NoLock); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index a171ebabf8..174aa3376a 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -461,7 +461,7 @@ 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; + heap_register_sync(transientrel); myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -507,9 +507,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close transientrel, but keep lock until commit */ heap_close(myState->transientrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 879c3d981e..ce8f7cd881 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4591,8 +4591,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); hi_options = HEAP_INSERT_SKIP_FSM; + if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(newrel); } else { @@ -4857,8 +4858,6 @@ 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); } diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index 8134c52253..28caf92073 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -924,7 +924,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, * page has been previously WAL-logged, and if not, do that * now. */ - if (RelationNeedsWAL(onerel) && + if (BufferNeedsWAL(onerel, buf) && PageGetLSN(page) == InvalidXLogRecPtr) log_newpage_buffer(buf, true); @@ -1188,7 +1188,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, } /* Now WAL-log freezing if necessary */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buf)) { XLogRecPtr recptr; @@ -1569,7 +1569,7 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buffer)) { XLogRecPtr recptr; diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 4baa287c8c..d2fbc1ad47 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -25,11 +25,10 @@ /* "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 -#define HEAP_INSERT_NO_LOGICAL 0x0010 +#define HEAP_INSERT_SKIP_FSM 0x0001 +#define HEAP_INSERT_FROZEN 0x0002 +#define HEAP_INSERT_SPECULATIVE 0x0004 +#define HEAP_INSERT_NO_LOGICAL 0x0008 typedef struct BulkInsertStateData *BulkInsertState; -- 2.16.3 ----Next_Part(Thu_Dec_20_17_32_25_2018_311)---- ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 4/4] Fix WAL skipping feature. @ 2018-10-11 07:18 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2018-10-11 07:18 UTC (permalink / raw) This patch repalces WAL-skipping means from HEAP_INSERT_SKIP_WAL to pending-sync tracking infrastructure. --- src/backend/access/heap/heapam.c | 70 ++++++++++++++++++++++----------- src/backend/access/heap/pruneheap.c | 3 +- src/backend/access/heap/rewriteheap.c | 3 -- src/backend/access/heap/vacuumlazy.c | 6 +-- src/backend/access/heap/visibilitymap.c | 3 +- src/backend/commands/copy.c | 13 +++--- src/backend/commands/createas.c | 9 ++--- src/backend/commands/matview.c | 6 +-- src/backend/commands/tablecmds.c | 5 +-- src/include/access/heapam.h | 9 ++--- 10 files changed, 72 insertions(+), 55 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 5ea5ff5848..c66a468335 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -28,6 +28,28 @@ * 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 + * BufferNeedsWAL() to check if WAL-logging is needed in this transaction + * for the given block. + * *------------------------------------------------------------------------- */ #include "postgres.h" @@ -2127,12 +2149,6 @@ 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. * @@ -2239,7 +2255,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2414,7 +2430,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, int ndone; PGAlignedBlock scratch; Page page; - bool needwal; Size saveFreeSpace; bool need_tuple_data = RelationIsLogicallyLogged(relation); bool need_cids = RelationIsAccessibleInLogicalDecoding(relation); @@ -2422,7 +2437,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); @@ -2464,6 +2478,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, Buffer vmbuffer = InvalidBuffer; bool all_visible_cleared = false; int nthispage; + bool needwal; CHECK_FOR_INTERRUPTS(); @@ -2475,6 +2490,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, InvalidBuffer, options, bistate, &vmbuffer, NULL); page = BufferGetPage(buffer); + needwal = BufferNeedsWAL(relation, buffer); /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); @@ -3037,7 +3053,7 @@ l1: * NB: heap_abort_speculative() uses the same xlog record and replay * routines. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; xl_heap_header xlhdr; @@ -3776,7 +3792,7 @@ l2: MarkBufferDirty(buffer); - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -3991,7 +4007,8 @@ l2: MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer) || + BufferNeedsWAL(relation, newbuf)) { XLogRecPtr recptr; @@ -4881,7 +4898,7 @@ failed: * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG * entries for everything anyway.) */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, *buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -5625,7 +5642,7 @@ l4: MarkBufferDirty(buf); /* XLOG stuff */ - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, buf)) { xl_heap_lock_updated xlrec; XLogRecPtr recptr; @@ -5785,7 +5802,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) htup->t_ctid = tuple->t_self; /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_confirm xlrec; XLogRecPtr recptr; @@ -5918,7 +5935,7 @@ heap_abort_speculative(Relation relation, HeapTuple tuple) * The WAL records generated here match heap_delete(). The same recovery * routines are used. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -6027,7 +6044,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple) MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_inplace xlrec; XLogRecPtr recptr; @@ -7224,7 +7241,7 @@ log_heap_clean(Relation reln, Buffer buffer, XLogRecPtr recptr; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, buffer)); xlrec.latestRemovedXid = latestRemovedXid; xlrec.nredirected = nredirected; @@ -7272,7 +7289,7 @@ log_heap_freeze(Relation reln, Buffer buffer, TransactionId cutoff_xid, XLogRecPtr recptr; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, buffer)); /* nor when there are no tuples to freeze */ Assert(ntuples > 0); @@ -7357,7 +7374,7 @@ log_heap_update(Relation reln, Buffer oldbuf, int bufflags; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, newbuf) || BufferNeedsWAL(reln, oldbuf)); XLogBeginInsert(); @@ -8961,9 +8978,16 @@ heap2_redo(XLogReaderState *record) * heap_sync - sync a heap, for use when no WAL has been written * * This forces the heap contents (including TOAST heap if any) down to disk. - * If we skipped using WAL, and WAL is otherwise needed, we must force the - * relation down to disk before it's safe to commit the transaction. This - * requires writing out any dirty buffers and then doing a forced fsync. + * If we did any changes to the heap bypassing the buffer manager, we must + * force the relation down to disk before it's safe to commit the + * transaction, because the direct modifications will not be flushed by + * the next checkpoint. + * + * We used to also use this after batch operations like COPY and CLUSTER, + * if we skipped using WAL and WAL is otherwise needed, but there were + * corner-cases involving other WAL-logged operations to the same + * relation, where that was not enough. heap_register_sync() should be + * used for that purpose instead. * * Indexes are not touched. (Currently, index operations associated with * the commands that use this are WAL-logged and so do not need fsync. diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index a3e51922d8..a05659b168 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -20,6 +20,7 @@ #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" @@ -258,7 +259,7 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin, /* * Emit a WAL HEAP_CLEAN record showing what we did */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index f5cf9ffc9c..1e9c07c9b2 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -654,9 +654,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 9416c31889..1f66685c88 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -929,7 +929,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, * page has been previously WAL-logged, and if not, do that * now. */ - if (RelationNeedsWAL(onerel) && + if (BufferNeedsWAL(onerel, buf) && PageGetLSN(page) == InvalidXLogRecPtr) log_newpage_buffer(buf, true); @@ -1193,7 +1193,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, } /* Now WAL-log freezing if necessary */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buf)) { XLogRecPtr recptr; @@ -1575,7 +1575,7 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 64dfe06b26..1f5f7d92dd 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -88,6 +88,7 @@ #include "access/heapam_xlog.h" #include "access/visibilitymap.h" #include "access/xlog.h" +#include "catalog/storage.h" #include "miscadmin.h" #include "port/pg_bitutils.h" #include "storage/bufmgr.h" @@ -276,7 +277,7 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, map[mapByte] |= (flags << mapOffset); MarkBufferDirty(vmBuf); - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, heapBuf)) { if (XLogRecPtrIsInvalid(recptr)) { diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 5dd6fe02c6..db7a94ff6e 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2390,8 +2390,7 @@ 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. + * 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 @@ -2437,7 +2436,7 @@ CopyFrom(CopyState cstate) { hi_options |= HEAP_INSERT_SKIP_FSM; if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(cstate->rel); } /* @@ -3087,11 +3086,11 @@ 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 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. */ - if (hi_options & HEAP_INSERT_SKIP_WAL) - heap_sync(cstate->rel); return processed; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 6517ecb738..17fb78ba78 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -556,8 +556,9 @@ 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); + 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 */ @@ -603,9 +604,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 5a47be4b33..5f447c6d94 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -463,7 +463,7 @@ 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; + heap_register_sync(transientrel); myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,9 +509,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6190b3f605..94d7876b8c 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4617,8 +4617,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); hi_options = HEAP_INSERT_SKIP_FSM; + if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(newrel); } else { @@ -4886,8 +4887,6 @@ 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); table_close(newrel, NoLock); } diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index fab5052868..32a365021a 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -27,11 +27,10 @@ /* "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 -#define HEAP_INSERT_NO_LOGICAL 0x0010 +#define HEAP_INSERT_SKIP_FSM 0x0001 +#define HEAP_INSERT_FROZEN 0x0002 +#define HEAP_INSERT_SPECULATIVE 0x0004 +#define HEAP_INSERT_NO_LOGICAL 0x0008 typedef struct BulkInsertStateData *BulkInsertState; -- 2.16.3 ----Next_Part(Mon_Mar_04_12_24_48_2019_788)---- ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 4/4] Fix WAL skipping feature. @ 2018-10-11 07:18 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2018-10-11 07:18 UTC (permalink / raw) This patch repalces WAL-skipping means from HEAP_INSERT_SKIP_WAL to pending-sync tracking infrastructure. --- src/backend/access/heap/heapam.c | 71 ++++++++++++++++++++++----------- src/backend/access/heap/pruneheap.c | 3 +- src/backend/access/heap/rewriteheap.c | 4 +- src/backend/access/heap/visibilitymap.c | 3 +- src/backend/commands/copy.c | 13 +++--- src/backend/commands/createas.c | 9 ++--- src/backend/commands/matview.c | 6 +-- src/backend/commands/tablecmds.c | 5 +-- src/backend/commands/vacuumlazy.c | 6 +-- src/include/access/heapam.h | 7 ++-- 10 files changed, 73 insertions(+), 54 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 4e823b6e39..46a3dda09f 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -34,6 +34,28 @@ * 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 + * BufferNeedsWAL() to check if WAL-logging is needed in this transaction + * for the given block. + * *------------------------------------------------------------------------- */ #include "postgres.h" @@ -2414,12 +2436,6 @@ 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. * @@ -2450,6 +2466,7 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate) * TID where the tuple was stored. But note that any toasting of fields * within the tuple data is NOT reflected into *tup. */ + Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, BulkInsertState bistate) @@ -2523,7 +2540,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2724,12 +2741,10 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, int ndone; PGAlignedBlock scratch; Page page; - bool needwal; Size saveFreeSpace; bool need_tuple_data = RelationIsLogicallyLogged(relation); bool need_cids = RelationIsAccessibleInLogicalDecoding(relation); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); @@ -2771,6 +2786,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, Buffer vmbuffer = InvalidBuffer; bool all_visible_cleared = false; int nthispage; + bool needwal; CHECK_FOR_INTERRUPTS(); @@ -2782,6 +2798,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, InvalidBuffer, options, bistate, &vmbuffer, NULL); page = BufferGetPage(buffer); + needwal = BufferNeedsWAL(relation, buffer); /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); @@ -3344,7 +3361,7 @@ l1: * NB: heap_abort_speculative() uses the same xlog record and replay * routines. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -4101,7 +4118,7 @@ l2: MarkBufferDirty(buffer); - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -4323,7 +4340,8 @@ l2: MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer) || + BufferNeedsWAL(relation, newbuf)) { XLogRecPtr recptr; @@ -5295,7 +5313,7 @@ failed: * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG * entries for everything anyway.) */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, *buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -6039,7 +6057,7 @@ l4: MarkBufferDirty(buf); /* XLOG stuff */ - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, buf)) { xl_heap_lock_updated xlrec; XLogRecPtr recptr; @@ -6199,7 +6217,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) htup->t_ctid = tuple->t_self; /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_confirm xlrec; XLogRecPtr recptr; @@ -6332,7 +6350,7 @@ heap_abort_speculative(Relation relation, HeapTuple tuple) * The WAL records generated here match heap_delete(). The same recovery * routines are used. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -6441,7 +6459,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple) MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_inplace xlrec; XLogRecPtr recptr; @@ -7637,7 +7655,7 @@ log_heap_clean(Relation reln, Buffer buffer, XLogRecPtr recptr; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, buffer)); xlrec.latestRemovedXid = latestRemovedXid; xlrec.nredirected = nredirected; @@ -7685,7 +7703,7 @@ log_heap_freeze(Relation reln, Buffer buffer, TransactionId cutoff_xid, XLogRecPtr recptr; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, buffer)); /* nor when there are no tuples to freeze */ Assert(ntuples > 0); @@ -7770,7 +7788,7 @@ log_heap_update(Relation reln, Buffer oldbuf, int bufflags; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, newbuf) || BufferNeedsWAL(reln, oldbuf)); XLogBeginInsert(); @@ -9391,9 +9409,16 @@ heap2_redo(XLogReaderState *record) * heap_sync - sync a heap, for use when no WAL has been written * * This forces the heap contents (including TOAST heap if any) down to disk. - * If we skipped using WAL, and WAL is otherwise needed, we must force the - * relation down to disk before it's safe to commit the transaction. This - * requires writing out any dirty buffers and then doing a forced fsync. + * If we did any changes to the heap bypassing the buffer manager, we must + * force the relation down to disk before it's safe to commit the + * transaction, because the direct modifications will not be flushed by + * the next checkpoint. + * + * We used to also use this after batch operations like COPY and CLUSTER, + * if we skipped using WAL and WAL is otherwise needed, but there were + * corner-cases involving other WAL-logged operations to the same + * relation, where that was not enough. heap_register_sync() should be + * used for that purpose instead. * * Indexes are not touched. (Currently, index operations associated with * the commands that use this are WAL-logged and so do not need fsync. diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index c2f5343dac..d0b68902d9 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -20,6 +20,7 @@ #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,7 +260,7 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin, /* * Emit a WAL HEAP_CLEAN record showing what we did */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 85f92973c9..ec9d1b3113 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -653,9 +653,7 @@ 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)); + HEAP_INSERT_SKIP_FSM); else heaptup = tup; diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 695567b4b0..fce14ce35f 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -88,6 +88,7 @@ #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,7 +308,7 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, map[mapByte] |= (flags << mapOffset); MarkBufferDirty(vmBuf); - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, heapBuf)) { if (XLogRecPtrIsInvalid(recptr)) { diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 86b0fb300f..07f96fde56 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2391,8 +2391,7 @@ 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. + * 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 @@ -2424,7 +2423,7 @@ CopyFrom(CopyState cstate) { hi_options |= HEAP_INSERT_SKIP_FSM; if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(cstate->rel); } /* @@ -3079,11 +3078,11 @@ 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 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. */ - if (hi_options & HEAP_INSERT_SKIP_WAL) - heap_sync(cstate->rel); return processed; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index d5cb62da15..0f58da40c6 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -568,8 +568,9 @@ 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); + 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 */ @@ -618,9 +619,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close rel, but keep lock until commit */ heap_close(myState->rel, NoLock); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index e1eb7c374b..986f7baf39 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -464,7 +464,7 @@ 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; + heap_register_sync(transientrel); myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -507,9 +507,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close transientrel, but keep lock until commit */ heap_close(myState->transientrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6dff2c696b..715718450d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4611,8 +4611,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); hi_options = HEAP_INSERT_SKIP_FSM; + if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(newrel); } else { @@ -4885,8 +4886,6 @@ 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); } diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index 8996d366e9..72849a9a94 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -924,7 +924,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, * page has been previously WAL-logged, and if not, do that * now. */ - if (RelationNeedsWAL(onerel) && + if (BufferNeedsWAL(onerel, buf) && PageGetLSN(page) == InvalidXLogRecPtr) log_newpage_buffer(buf, true); @@ -1194,7 +1194,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, } /* Now WAL-log freezing if necessary */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buf)) { XLogRecPtr recptr; @@ -1575,7 +1575,7 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buffer)) { XLogRecPtr recptr; diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 169c2f730e..c5e5e9a8b2 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -25,10 +25,9 @@ /* "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 +#define HEAP_INSERT_SKIP_FSM 0x0001 +#define HEAP_INSERT_FROZEN 0x0002 +#define HEAP_INSERT_SPECULATIVE 0x0004 typedef struct BulkInsertStateData *BulkInsertState; -- 2.16.3 ----Next_Part(Thu_Oct_11_17_04_53_2018_748)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v3-0003-Add-infrastructure-to-WAL-logging-skip-feature.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 4/4] Fix WAL skipping feature. @ 2018-10-11 07:18 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2018-10-11 07:18 UTC (permalink / raw) This patch repalces WAL-skipping means from HEAP_INSERT_SKIP_WAL to pending-sync tracking infrastructure. --- src/backend/access/heap/heapam.c | 71 ++++++++++++++++++++++----------- src/backend/access/heap/pruneheap.c | 3 +- src/backend/access/heap/rewriteheap.c | 3 -- src/backend/access/heap/visibilitymap.c | 3 +- src/backend/commands/copy.c | 13 +++--- src/backend/commands/createas.c | 9 ++--- src/backend/commands/matview.c | 6 +-- src/backend/commands/tablecmds.c | 5 +-- src/backend/commands/vacuumlazy.c | 6 +-- src/include/access/heapam.h | 9 ++--- 10 files changed, 73 insertions(+), 55 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 7caa3ec248..a68eae9b11 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -34,6 +34,28 @@ * 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 + * BufferNeedsWAL() to check if WAL-logging is needed in this transaction + * for the given block. + * *------------------------------------------------------------------------- */ #include "postgres.h" @@ -2414,12 +2436,6 @@ 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. * @@ -2455,6 +2471,7 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate) * TID where the tuple was stored. But note that any toasting of fields * within the tuple data is NOT reflected into *tup. */ + Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, BulkInsertState bistate) @@ -2528,7 +2545,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2730,7 +2747,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, int ndone; PGAlignedBlock scratch; Page page; - bool needwal; Size saveFreeSpace; bool need_tuple_data = RelationIsLogicallyLogged(relation); bool need_cids = RelationIsAccessibleInLogicalDecoding(relation); @@ -2738,7 +2754,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); @@ -2780,6 +2795,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, Buffer vmbuffer = InvalidBuffer; bool all_visible_cleared = false; int nthispage; + bool needwal; CHECK_FOR_INTERRUPTS(); @@ -2791,6 +2807,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, InvalidBuffer, options, bistate, &vmbuffer, NULL); page = BufferGetPage(buffer); + needwal = BufferNeedsWAL(relation, buffer); /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); @@ -3353,7 +3370,7 @@ l1: * NB: heap_abort_speculative() uses the same xlog record and replay * routines. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -4110,7 +4127,7 @@ l2: MarkBufferDirty(buffer); - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -4332,7 +4349,8 @@ l2: MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer) || + BufferNeedsWAL(relation, newbuf)) { XLogRecPtr recptr; @@ -5304,7 +5322,7 @@ failed: * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG * entries for everything anyway.) */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, *buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -6048,7 +6066,7 @@ l4: MarkBufferDirty(buf); /* XLOG stuff */ - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, buf)) { xl_heap_lock_updated xlrec; XLogRecPtr recptr; @@ -6208,7 +6226,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) htup->t_ctid = tuple->t_self; /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_confirm xlrec; XLogRecPtr recptr; @@ -6341,7 +6359,7 @@ heap_abort_speculative(Relation relation, HeapTuple tuple) * The WAL records generated here match heap_delete(). The same recovery * routines are used. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -6450,7 +6468,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple) MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_inplace xlrec; XLogRecPtr recptr; @@ -7646,7 +7664,7 @@ log_heap_clean(Relation reln, Buffer buffer, XLogRecPtr recptr; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, buffer)); xlrec.latestRemovedXid = latestRemovedXid; xlrec.nredirected = nredirected; @@ -7694,7 +7712,7 @@ log_heap_freeze(Relation reln, Buffer buffer, TransactionId cutoff_xid, XLogRecPtr recptr; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, buffer)); /* nor when there are no tuples to freeze */ Assert(ntuples > 0); @@ -7779,7 +7797,7 @@ log_heap_update(Relation reln, Buffer oldbuf, int bufflags; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, newbuf) || BufferNeedsWAL(reln, oldbuf)); XLogBeginInsert(); @@ -9400,9 +9418,16 @@ heap2_redo(XLogReaderState *record) * heap_sync - sync a heap, for use when no WAL has been written * * This forces the heap contents (including TOAST heap if any) down to disk. - * If we skipped using WAL, and WAL is otherwise needed, we must force the - * relation down to disk before it's safe to commit the transaction. This - * requires writing out any dirty buffers and then doing a forced fsync. + * If we did any changes to the heap bypassing the buffer manager, we must + * force the relation down to disk before it's safe to commit the + * transaction, because the direct modifications will not be flushed by + * the next checkpoint. + * + * We used to also use this after batch operations like COPY and CLUSTER, + * if we skipped using WAL and WAL is otherwise needed, but there were + * corner-cases involving other WAL-logged operations to the same + * relation, where that was not enough. heap_register_sync() should be + * used for that purpose instead. * * Indexes are not touched. (Currently, index operations associated with * the commands that use this are WAL-logged and so do not need fsync. diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index c2f5343dac..d0b68902d9 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -20,6 +20,7 @@ #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,7 +260,7 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin, /* * Emit a WAL HEAP_CLEAN record showing what we did */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index c5db75afa1..d2f78199ee 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -655,9 +655,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * The new relfilenode's relcache entrye doesn't have the necessary * information to determine whether a relation should emit data for diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 695567b4b0..fce14ce35f 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -88,6 +88,7 @@ #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,7 +308,7 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, map[mapByte] |= (flags << mapOffset); MarkBufferDirty(vmBuf); - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, heapBuf)) { if (XLogRecPtrIsInvalid(recptr)) { diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index b58a74f4e3..f54f80777b 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2391,8 +2391,7 @@ 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. + * 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 @@ -2424,7 +2423,7 @@ CopyFrom(CopyState cstate) { hi_options |= HEAP_INSERT_SKIP_FSM; if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(cstate->rel); } /* @@ -3078,11 +3077,11 @@ 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 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. */ - if (hi_options & HEAP_INSERT_SKIP_WAL) - heap_sync(cstate->rel); return processed; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index d5cb62da15..0f58da40c6 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -568,8 +568,9 @@ 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); + 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 */ @@ -618,9 +619,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close rel, but keep lock until commit */ heap_close(myState->rel, NoLock); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index e1eb7c374b..986f7baf39 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -464,7 +464,7 @@ 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; + heap_register_sync(transientrel); myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -507,9 +507,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close transientrel, but keep lock until commit */ heap_close(myState->transientrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c507a1ab34..98084ad98c 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4617,8 +4617,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); hi_options = HEAP_INSERT_SKIP_FSM; + if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(newrel); } else { @@ -4891,8 +4892,6 @@ 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); } diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index 8996d366e9..72849a9a94 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -924,7 +924,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, * page has been previously WAL-logged, and if not, do that * now. */ - if (RelationNeedsWAL(onerel) && + if (BufferNeedsWAL(onerel, buf) && PageGetLSN(page) == InvalidXLogRecPtr) log_newpage_buffer(buf, true); @@ -1194,7 +1194,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, } /* Now WAL-log freezing if necessary */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buf)) { XLogRecPtr recptr; @@ -1575,7 +1575,7 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buffer)) { XLogRecPtr recptr; diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index f1d4a803ae..708cdd6cc5 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -25,11 +25,10 @@ /* "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 -#define HEAP_INSERT_NO_LOGICAL 0x0010 +#define HEAP_INSERT_SKIP_FSM 0x0001 +#define HEAP_INSERT_FROZEN 0x0002 +#define HEAP_INSERT_SPECULATIVE 0x0004 +#define HEAP_INSERT_NO_LOGICAL 0x0008 typedef struct BulkInsertStateData *BulkInsertState; -- 2.16.3 ----Next_Part(Wed_Nov_14_12_47_36_2018_817)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v4-0003-Add-infrastructure-to-WAL-logging-skip-feature.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 4/4] Fix WAL skipping feature. @ 2018-10-11 07:18 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2018-10-11 07:18 UTC (permalink / raw) This patch repalces WAL-skipping means from HEAP_INSERT_SKIP_WAL to pending-sync tracking infrastructure. --- src/backend/access/heap/heapam.c | 70 ++++++++++++++++++++++----------- src/backend/access/heap/pruneheap.c | 3 +- src/backend/access/heap/rewriteheap.c | 3 -- src/backend/access/heap/vacuumlazy.c | 6 +-- src/backend/access/heap/visibilitymap.c | 3 +- src/backend/commands/copy.c | 13 +++--- src/backend/commands/createas.c | 9 ++--- src/backend/commands/matview.c | 6 +-- src/backend/commands/tablecmds.c | 5 +-- src/include/access/heapam.h | 9 ++--- 10 files changed, 72 insertions(+), 55 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 5972e9d190..a2d8aefa28 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -28,6 +28,28 @@ * 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 + * BufferNeedsWAL() to check if WAL-logging is needed in this transaction + * for the given block. + * *------------------------------------------------------------------------- */ #include "postgres.h" @@ -2127,12 +2149,6 @@ 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. * @@ -2239,7 +2255,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2414,7 +2430,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, int ndone; PGAlignedBlock scratch; Page page; - bool needwal; Size saveFreeSpace; bool need_tuple_data = RelationIsLogicallyLogged(relation); bool need_cids = RelationIsAccessibleInLogicalDecoding(relation); @@ -2422,7 +2437,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); @@ -2464,6 +2478,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, Buffer vmbuffer = InvalidBuffer; bool all_visible_cleared = false; int nthispage; + bool needwal; CHECK_FOR_INTERRUPTS(); @@ -2475,6 +2490,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, InvalidBuffer, options, bistate, &vmbuffer, NULL); page = BufferGetPage(buffer); + needwal = BufferNeedsWAL(relation, buffer); /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); @@ -3037,7 +3053,7 @@ l1: * NB: heap_abort_speculative() uses the same xlog record and replay * routines. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -3777,7 +3793,7 @@ l2: MarkBufferDirty(buffer); - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -3992,7 +4008,8 @@ l2: MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer) || + BufferNeedsWAL(relation, newbuf)) { XLogRecPtr recptr; @@ -4882,7 +4899,7 @@ failed: * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG * entries for everything anyway.) */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, *buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -5626,7 +5643,7 @@ l4: MarkBufferDirty(buf); /* XLOG stuff */ - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, buf)) { xl_heap_lock_updated xlrec; XLogRecPtr recptr; @@ -5786,7 +5803,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) htup->t_ctid = tuple->t_self; /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_confirm xlrec; XLogRecPtr recptr; @@ -5919,7 +5936,7 @@ heap_abort_speculative(Relation relation, HeapTuple tuple) * The WAL records generated here match heap_delete(). The same recovery * routines are used. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -6028,7 +6045,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple) MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_inplace xlrec; XLogRecPtr recptr; @@ -7225,7 +7242,7 @@ log_heap_clean(Relation reln, Buffer buffer, XLogRecPtr recptr; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, buffer)); xlrec.latestRemovedXid = latestRemovedXid; xlrec.nredirected = nredirected; @@ -7273,7 +7290,7 @@ log_heap_freeze(Relation reln, Buffer buffer, TransactionId cutoff_xid, XLogRecPtr recptr; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, buffer)); /* nor when there are no tuples to freeze */ Assert(ntuples > 0); @@ -7358,7 +7375,7 @@ log_heap_update(Relation reln, Buffer oldbuf, int bufflags; /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + Assert(BufferNeedsWAL(reln, newbuf) || BufferNeedsWAL(reln, oldbuf)); XLogBeginInsert(); @@ -8962,9 +8979,16 @@ heap2_redo(XLogReaderState *record) * heap_sync - sync a heap, for use when no WAL has been written * * This forces the heap contents (including TOAST heap if any) down to disk. - * If we skipped using WAL, and WAL is otherwise needed, we must force the - * relation down to disk before it's safe to commit the transaction. This - * requires writing out any dirty buffers and then doing a forced fsync. + * If we did any changes to the heap bypassing the buffer manager, we must + * force the relation down to disk before it's safe to commit the + * transaction, because the direct modifications will not be flushed by + * the next checkpoint. + * + * We used to also use this after batch operations like COPY and CLUSTER, + * if we skipped using WAL and WAL is otherwise needed, but there were + * corner-cases involving other WAL-logged operations to the same + * relation, where that was not enough. heap_register_sync() should be + * used for that purpose instead. * * Indexes are not touched. (Currently, index operations associated with * the commands that use this are WAL-logged and so do not need fsync. diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index a3e51922d8..a05659b168 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -20,6 +20,7 @@ #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" @@ -258,7 +259,7 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin, /* * Emit a WAL HEAP_CLEAN record showing what we did */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index f5cf9ffc9c..1e9c07c9b2 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -654,9 +654,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 37aa484ec3..3309c93bce 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -923,7 +923,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, * page has been previously WAL-logged, and if not, do that * now. */ - if (RelationNeedsWAL(onerel) && + if (BufferNeedsWAL(onerel, buf) && PageGetLSN(page) == InvalidXLogRecPtr) log_newpage_buffer(buf, true); @@ -1187,7 +1187,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, } /* Now WAL-log freezing if necessary */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buf)) { XLogRecPtr recptr; @@ -1568,7 +1568,7 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 931ae81fd6..53da0da68f 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -88,6 +88,7 @@ #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,7 +308,7 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, map[mapByte] |= (flags << mapOffset); MarkBufferDirty(vmBuf); - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, heapBuf)) { if (XLogRecPtrIsInvalid(recptr)) { diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index dbb06397e6..b42bfbfd47 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2390,8 +2390,7 @@ 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. + * 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 @@ -2437,7 +2436,7 @@ CopyFrom(CopyState cstate) { hi_options |= HEAP_INSERT_SKIP_FSM; if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(cstate->rel); } /* @@ -3092,11 +3091,11 @@ 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 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. */ - if (hi_options & HEAP_INSERT_SKIP_WAL) - heap_sync(cstate->rel); return processed; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 2bc8f928ea..5eb45a4a65 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -556,8 +556,9 @@ 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); + 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 */ @@ -600,9 +601,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 5a47be4b33..5f447c6d94 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -463,7 +463,7 @@ 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; + heap_register_sync(transientrel); myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,9 +509,7 @@ 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); + /* If we skipped using WAL, we will sync the relation at commit */ /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index e15296e373..65be3c2869 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4616,8 +4616,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); hi_options = HEAP_INSERT_SKIP_FSM; + if (!XLogIsNeeded()) - hi_options |= HEAP_INSERT_SKIP_WAL; + heap_register_sync(newrel); } else { @@ -4882,8 +4883,6 @@ 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); table_close(newrel, NoLock); } diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index fab5052868..32a365021a 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -27,11 +27,10 @@ /* "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 -#define HEAP_INSERT_NO_LOGICAL 0x0010 +#define HEAP_INSERT_SKIP_FSM 0x0001 +#define HEAP_INSERT_FROZEN 0x0002 +#define HEAP_INSERT_SPECULATIVE 0x0004 +#define HEAP_INSERT_NO_LOGICAL 0x0008 typedef struct BulkInsertStateData *BulkInsertState; -- 2.16.3 ----Next_Part(Wed_Jan_30_10_26_34_2019_453)---- ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 6/7] Fix WAL skipping feature. @ 2019-04-02 04:29 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2019-04-02 04:29 UTC (permalink / raw) This patch replaces WAL-skipping means from HEAP_INSERT_SKIP_WAL to the new infrastructure. --- src/backend/access/heap/heapam.c | 114 +++++++++++++++++++++++-------- src/backend/access/heap/heapam_handler.c | 88 ++++++++++++++++++------ src/backend/access/heap/pruneheap.c | 3 +- src/backend/access/heap/rewriteheap.c | 28 ++------ src/backend/access/heap/vacuumlazy.c | 6 +- src/backend/access/heap/visibilitymap.c | 3 +- src/backend/commands/cluster.c | 27 ++++++++ src/backend/commands/copy.c | 15 +++- src/backend/commands/createas.c | 7 +- src/backend/commands/matview.c | 7 +- src/backend/commands/tablecmds.c | 8 ++- src/include/access/rewriteheap.h | 2 +- 12 files changed, 219 insertions(+), 89 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 267570b461..cc516e599d 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -28,6 +28,27 @@ * 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 transaction, because for + * a small number of changes, it's cheaper to just create the WAL records + * than fsync()ing the whole relation at COMMIT. It is only worthwhile for + * (presumably) large operations like COPY, CLUSTER, or VACUUM FULL. Use + * table_relation_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 + * BufferNeedsWAL() to check if WAL-logging is needed in this transaction + * for the given block. + * *------------------------------------------------------------------------- */ #include "postgres.h" @@ -51,6 +72,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" @@ -1948,7 +1970,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; @@ -2058,7 +2080,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, int ndone; PGAlignedBlock scratch; Page page; - bool needwal; Size saveFreeSpace; bool need_tuple_data = RelationIsLogicallyLogged(relation); bool need_cids = RelationIsAccessibleInLogicalDecoding(relation); @@ -2066,7 +2087,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); @@ -2108,6 +2128,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, Buffer vmbuffer = InvalidBuffer; bool all_visible_cleared = false; int nthispage; + bool needwal; CHECK_FOR_INTERRUPTS(); @@ -2119,6 +2140,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, InvalidBuffer, options, bistate, &vmbuffer, NULL); page = BufferGetPage(buffer); + needwal = BufferNeedsWAL(relation, buffer); /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); @@ -2671,7 +2693,7 @@ l1: * NB: heap_abort_speculative() uses the same xlog record and replay * routines. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; @@ -2805,6 +2827,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, vmbuffer = InvalidBuffer, vmbuffer_new = InvalidBuffer; bool need_toast; + bool oldbuf_needs_wal, + newbuf_needs_wal; Size newtupsize, pagefree; bool have_tuple_lock = false; @@ -3356,7 +3380,7 @@ l2: MarkBufferDirty(buffer); - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -3570,26 +3594,55 @@ l2: MarkBufferDirty(newbuf); MarkBufferDirty(buffer); - /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + /* + * XLOG stuff + * + * Emit heap-update log. When wal_level = minimal, we may emit insert or + * delete record according to wal-optimization. + */ + oldbuf_needs_wal = BufferNeedsWAL(relation, buffer); + + if (newbuf == buffer) + newbuf_needs_wal = oldbuf_needs_wal; + else + newbuf_needs_wal = BufferNeedsWAL(relation, newbuf); + + if (oldbuf_needs_wal || newbuf_needs_wal) { XLogRecPtr recptr; /* * For logical decoding we need combocids to properly decode the - * catalog. + * catalog. Both oldbuf_needs_wal and newbuf_needs_wal must be true + * when logical decoding is active. */ if (RelationIsAccessibleInLogicalDecoding(relation)) { + Assert(oldbuf_needs_wal && newbuf_needs_wal); + log_heap_new_cid(relation, &oldtup); log_heap_new_cid(relation, heaptup); } - recptr = log_heap_update(relation, buffer, - newbuf, &oldtup, heaptup, - old_key_tuple, - all_visible_cleared, - all_visible_cleared_new); + /* + * Insert log record. Using delete or insert log loses HOT chain + * information but that happens only when newbuf is different from + * buffer, where HOT cannot happen. + */ + if (oldbuf_needs_wal && newbuf_needs_wal) + recptr = log_heap_update(relation, buffer, newbuf, + &oldtup, heaptup, + old_key_tuple, + all_visible_cleared, + all_visible_cleared_new); + else if (oldbuf_needs_wal) + recptr = log_heap_delete(relation, buffer, &oldtup, old_key_tuple, + xmax_old_tuple, false, + all_visible_cleared); + else + recptr = log_heap_insert(relation, buffer, newtup, + 0, all_visible_cleared_new); + if (newbuf != buffer) { PageSetLSN(BufferGetPage(newbuf), recptr); @@ -4467,7 +4520,7 @@ failed: * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG * entries for everything anyway.) */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, *buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -5219,7 +5272,7 @@ l4: MarkBufferDirty(buf); /* XLOG stuff */ - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, buf)) { xl_heap_lock_updated xlrec; XLogRecPtr recptr; @@ -5379,7 +5432,7 @@ heap_finish_speculative(Relation relation, ItemPointer tid) htup->t_ctid = *tid; /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_confirm xlrec; XLogRecPtr recptr; @@ -5511,7 +5564,7 @@ heap_abort_speculative(Relation relation, ItemPointer tid) * The WAL records generated here match heap_delete(). The same recovery * routines are used. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -5620,7 +5673,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple) MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_inplace xlrec; XLogRecPtr recptr; @@ -7030,8 +7083,8 @@ log_heap_clean(Relation reln, Buffer buffer, xl_heap_clean xlrec; XLogRecPtr recptr; - /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + /* Caller should not call me on non-WAL-logged buffers */ + Assert(BufferNeedsWAL(reln, buffer)); xlrec.latestRemovedXid = latestRemovedXid; xlrec.nredirected = nredirected; @@ -7078,8 +7131,8 @@ log_heap_freeze(Relation reln, Buffer buffer, TransactionId cutoff_xid, xl_heap_freeze_page xlrec; XLogRecPtr recptr; - /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + /* Caller should not call me on non-WAL-logged buffers */ + Assert(BufferNeedsWAL(reln, buffer)); /* nor when there are no tuples to freeze */ Assert(ntuples > 0); @@ -7305,8 +7358,8 @@ log_heap_update(Relation reln, Buffer oldbuf, bool init; int bufflags; - /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + /* Caller should not call me when no buffer needs WAL-logging */ + Assert(BufferNeedsWAL(reln, newbuf) || BufferNeedsWAL(reln, oldbuf)); XLogBeginInsert(); @@ -8910,9 +8963,16 @@ heap2_redo(XLogReaderState *record) * heap_sync - sync a heap, for use when no WAL has been written * * This forces the heap contents (including TOAST heap if any) down to disk. - * If we skipped using WAL, and WAL is otherwise needed, we must force the - * relation down to disk before it's safe to commit the transaction. This - * requires writing out any dirty buffers and then doing a forced fsync. + * If we did any changes to the heap bypassing the buffer manager, we must + * force the relation down to disk before it's safe to commit the + * transaction, because the direct modifications will not be flushed by + * the next checkpoint. + * + * We used to also use this after batch operations like COPY and CLUSTER, + * if we skipped using WAL and WAL is otherwise needed, but there were + * corner-cases involving other WAL-logged operations to the same + * relation, where that was not enough. table_relation_register_sync() should + * be used for that purpose instead. * * Indexes are not touched. (Currently, index operations associated with * the commands that use this are WAL-logged and so do not need fsync. diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 5c96fc91b7..bddf026b81 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -57,6 +57,9 @@ static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, OffsetNumber tupoffset); +static void heapam_relation_register_walskip(Relation rel); +static void heapam_relation_invalidate_walskip(Relation rel); + static const TableAmRoutine heapam_methods; @@ -541,14 +544,10 @@ tuple_lock_retry: } static void -heapam_finish_bulk_insert(Relation relation, int options) +heapam_finish_bulk_insert(RelFileNode rnode, ForkNumber forkNum) { - /* - * If we skipped writing WAL, then we need to sync the heap (but not - * indexes since those use WAL anyway / don't go through tableam) - */ - if (options & HEAP_INSERT_SKIP_WAL) - heap_sync(relation); + /* Sync the file immedately */ + smgrimmedsync(smgropen(rnode, InvalidBackendId), forkNum); } @@ -616,6 +615,12 @@ heapam_relation_copy_data(Relation rel, RelFileNode newrnode) dstrel = smgropen(newrnode, rel->rd_backend); RelationOpenSmgr(rel); + /* + * Register WAL-skipping for the relation. WAL-logging is skipped and sync + * the file at commit if the AM supports the feature. + */ + table_relation_register_walskip(rel); + /* * Create and copy all forks of the relation, and schedule unlinking of * old physical files. @@ -626,8 +631,7 @@ heapam_relation_copy_data(Relation rel, RelFileNode newrnode) RelationCreateStorage(newrnode, rel->rd_rel->relpersistence); /* copy main fork */ - RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM, - rel->rd_rel->relpersistence); + RelationCopyStorage(rel, dstrel, MAIN_FORKNUM); /* copy those extra forks that exist */ for (ForkNumber forkNum = MAIN_FORKNUM + 1; @@ -645,8 +649,7 @@ heapam_relation_copy_data(Relation rel, RelFileNode newrnode) (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED && forkNum == INIT_FORKNUM)) log_smgrcreate(&newrnode, forkNum); - RelationCopyStorage(rel->rd_smgr, dstrel, forkNum, - rel->rd_rel->relpersistence); + RelationCopyStorage(rel, dstrel, forkNum); } } @@ -670,7 +673,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, IndexScanDesc indexScan; TableScanDesc tableScan; HeapScanDesc heapScan; - bool use_wal; bool is_system_catalog; Tuplesortstate *tuplesort; TupleDesc oldTupDesc = RelationGetDescr(OldHeap); @@ -684,15 +686,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Remember if it's a system catalog */ is_system_catalog = IsSystemRelation(OldHeap); - /* - * We need to log the copied data in WAL iff WAL archiving/streaming is - * enabled AND it's a WAL-logged rel. - */ - use_wal = XLogIsNeeded() && RelationNeedsWAL(NewHeap); - - /* use_wal off requires smgr_targblock be initially invalid */ - Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber); - /* Preallocate values/isnull arrays */ natts = newTupDesc->natts; values = (Datum *) palloc(natts * sizeof(Datum)); @@ -700,7 +693,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Initialize the rewrite operation */ rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, FreezeXid, - MultiXactCutoff, use_wal); + MultiXactCutoff); /* Set up sorting if wanted */ @@ -946,6 +939,55 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, pfree(isnull); } +/* + * heapam_relation_register_walskip - register a heap to be WAL-skipped then + * synced to disk at commit + * + * This can be used to skip WAL-logging changes on a relation file. 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. + * + * This does the same for the TOAST heap, if any. Indexes are not affected. + */ +static void +heapam_relation_register_walskip(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); + } + + return; +} + +/* + * heapam_relation_invalidate_walskip - invalidate registered WAL skipping + * + * After some file-replacing operations like CLUSTER, the old file no longe + * needs to be synced to disk. This function invalidates the registered + * WAL-skipping on the current relfilenode of the relation. + */ +static void +heapam_relation_invalidate_walskip(Relation rel) +{ + /* non-WAL-logged tables never need fsync */ + if (!RelationNeedsWAL(rel)) + return; + + RelationInvalidateWALSkip(rel); +} + static bool heapam_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, BufferAccessStrategy bstrategy) @@ -2423,6 +2465,8 @@ static const TableAmRoutine heapam_methods = { .relation_nontransactional_truncate = heapam_relation_nontransactional_truncate, .relation_copy_data = heapam_relation_copy_data, .relation_copy_for_cluster = heapam_relation_copy_for_cluster, + .relation_register_walskip = heapam_relation_register_walskip, + .relation_invalidate_walskip = heapam_relation_invalidate_walskip, .relation_vacuum = heap_vacuum_rel, .scan_analyze_next_block = heapam_scan_analyze_next_block, .scan_analyze_next_tuple = heapam_scan_analyze_next_tuple, diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index a3e51922d8..a05659b168 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -20,6 +20,7 @@ #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" @@ -258,7 +259,7 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin, /* * Emit a WAL HEAP_CLEAN record showing what we did */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index bce4274362..494f7fcd41 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "access/xloginsert.h" #include "catalog/catalog.h" +#include "catalog/storage.h" #include "lib/ilist.h" @@ -144,7 +145,6 @@ typedef struct RewriteStateData Page rs_buffer; /* page currently being built */ BlockNumber rs_blockno; /* block where page will go */ bool rs_buffer_valid; /* T if any tuples in buffer */ - bool rs_use_wal; /* must we WAL-log inserts? */ bool rs_logical_rewrite; /* do we need to do logical rewriting */ TransactionId rs_oldest_xmin; /* oldest xmin used by caller to determine * tuple visibility */ @@ -238,15 +238,13 @@ static void logical_end_heap_rewrite(RewriteState state); * oldest_xmin xid used by the caller to determine which tuples are dead * freeze_xid xid before which tuples will be frozen * min_multi multixact before which multis will be removed - * use_wal should the inserts to the new heap be WAL-logged? * * Returns an opaque RewriteState, allocated in current memory context, * to be used in subsequent calls to the other functions. */ RewriteState begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xmin, - TransactionId freeze_xid, MultiXactId cutoff_multi, - bool use_wal) + TransactionId freeze_xid, MultiXactId cutoff_multi) { RewriteState state; MemoryContext rw_cxt; @@ -271,7 +269,6 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm /* new_heap needn't be empty, just locked */ state->rs_blockno = RelationGetNumberOfBlocks(new_heap); state->rs_buffer_valid = false; - state->rs_use_wal = use_wal; state->rs_oldest_xmin = oldest_xmin; state->rs_freeze_xid = freeze_xid; state->rs_cutoff_multi = cutoff_multi; @@ -330,7 +327,7 @@ end_heap_rewrite(RewriteState state) /* Write the last page, if any */ if (state->rs_buffer_valid) { - if (state->rs_use_wal) + if (BlockNeedsWAL(state->rs_new_rel, state->rs_blockno)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, @@ -344,19 +341,7 @@ end_heap_rewrite(RewriteState state) (char *) state->rs_buffer, true); } - /* - * If the rel is WAL-logged, must fsync before commit. We use heap_sync - * to ensure that the toast table gets fsync'd too. - * - * It's obvious that we must do this when not WAL-logging. It's less - * obvious that we have to do it even if we did WAL-log the pages. The - * reason is the same as in tablecmds.c's copy_relation_data(): we're - * writing data that's not in shared buffers, and so a CHECKPOINT - * occurring during the rewriteheap operation won't have fsync'd data we - * wrote before the checkpoint. - */ - if (RelationNeedsWAL(state->rs_new_rel)) - heap_sync(state->rs_new_rel); + /* If we skipped using WAL, we will sync the relation at commit */ logical_end_heap_rewrite(state); @@ -654,9 +639,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is @@ -695,7 +677,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup) /* Doesn't fit, so write out the existing page */ /* XLOG stuff */ - if (state->rs_use_wal) + if (BlockNeedsWAL(state->rs_new_rel, state->rs_blockno)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index b5b464e4a9..45139ec70e 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -945,7 +945,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, * page has been previously WAL-logged, and if not, do that * now. */ - if (RelationNeedsWAL(onerel) && + if (BufferNeedsWAL(onerel, buf) && PageGetLSN(page) == InvalidXLogRecPtr) log_newpage_buffer(buf, true); @@ -1209,7 +1209,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, } /* Now WAL-log freezing if necessary */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buf)) { XLogRecPtr recptr; @@ -1591,7 +1591,7 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 64dfe06b26..1f5f7d92dd 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -88,6 +88,7 @@ #include "access/heapam_xlog.h" #include "access/visibilitymap.h" #include "access/xlog.h" +#include "catalog/storage.h" #include "miscadmin.h" #include "port/pg_bitutils.h" #include "storage/bufmgr.h" @@ -276,7 +277,7 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, map[mapByte] |= (flags << mapOffset); MarkBufferDirty(vmBuf); - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, heapBuf)) { if (XLogRecPtrIsInvalid(recptr)) { diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 4f4be1efbf..b5db26fda5 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -612,6 +612,18 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose) relpersistence, AccessExclusiveLock); + /* + * If wal_level is minimal, we skip WAL-logging even for WAL-logging + * relations. The filenode is synced at commit. + */ + if (!XLogIsNeeded()) + { + /* make_new_heap doesn't lock OIDNewHeap */ + Relation newheap = table_open(OIDNewHeap, AccessShareLock); + table_relation_register_walskip(newheap); + table_close(newheap, AccessShareLock); + } + /* Copy the heap data into the new table in the desired order */ copy_table_data(OIDNewHeap, tableOid, indexOid, verbose, &swap_toast_by_content, &frozenXid, &cutoffMulti); @@ -1355,6 +1367,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, /* Zero out possible results from swapped_relation_files */ memset(mapped_tables, 0, sizeof(mapped_tables)); + /* + * Unregister useless pending file-sync. table_relation_unregister_sync + * relies on a premise that relation cache has the correct relfilenode and + * related members. After swap_relation_files, the relcache entry for the + * heaps gets inconsistent with pg_class entry so we should do this before + * the call. + */ + if (!XLogIsNeeded()) + { + Relation oldheap = table_open(OIDOldHeap, AccessShareLock); + + table_relation_invalidate_walskip(oldheap); + table_close(oldheap, AccessShareLock); + } + /* * Swap the contents of the heap relations (including any toast tables). * Also set old heap's relfrozenxid to frozenXid. diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index c1fd7b78ce..6a85ab890e 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2437,9 +2437,13 @@ CopyFrom(CopyState cstate) (cstate->rel->rd_createSubid != InvalidSubTransactionId || cstate->rel->rd_newRelfilenodeSubid != InvalidSubTransactionId)) { - ti_options |= TABLE_INSERT_SKIP_FSM; + /* + * 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()) - ti_options |= TABLE_INSERT_SKIP_WAL; + table_relation_register_walskip(cstate->rel); + ti_options |= TABLE_INSERT_SKIP_FSM; } /* @@ -3106,7 +3110,12 @@ CopyFrom(CopyState cstate) FreeExecutorState(estate); - table_finish_bulk_insert(cstate->rel, ti_options); + /* + * 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; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 43c2fa9124..8b73654413 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -558,8 +558,9 @@ 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->ti_options = TABLE_INSERT_SKIP_FSM | - (XLogIsNeeded() ? 0 : TABLE_INSERT_SKIP_WAL); + if (!XLogIsNeeded()) + table_relation_register_walskip(intoRelationDesc); + myState->ti_options = HEAP_INSERT_SKIP_FSM; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -604,7 +605,7 @@ intorel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->rel, myState->ti_options); + /* If we skipped using WAL, we will sync the relation at commit */ /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 2aac63296b..33b7bc4c16 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -462,9 +462,10 @@ transientrel_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->ti_options = TABLE_INSERT_SKIP_FSM | TABLE_INSERT_FROZEN; if (!XLogIsNeeded()) - myState->ti_options |= TABLE_INSERT_SKIP_WAL; + table_relation_register_walskip(transientrel); + myState->ti_options = TABLE_INSERT_SKIP_FSM | TABLE_INSERT_FROZEN; + myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,7 +510,7 @@ transientrel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->transientrel, myState->ti_options); + /* If we skipped using WAL, we will sync the relation at commit */ /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 8908b77d98..deb147c45a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4716,7 +4716,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) ti_options = TABLE_INSERT_SKIP_FSM; if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; + { + /* Forget old relation's registerd sync */ + table_relation_invalidate_walskip(oldrel); + table_relation_register_walskip(newrel); + } } else { @@ -5000,7 +5004,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { FreeBulkInsertState(bistate); - table_finish_bulk_insert(newrel, ti_options); + /* If we skipped writing WAL, then it will be done at commit. */ table_close(newrel, NoLock); } diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 6006249d96..64efecf48b 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -23,7 +23,7 @@ typedef struct RewriteStateData *RewriteState; extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap, TransactionId OldestXmin, TransactionId FreezeXid, - MultiXactId MultiXactCutoff, bool use_wal); + MultiXactId MultiXactCutoff); extern void end_heap_rewrite(RewriteState state); extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple, HeapTuple newTuple); -- 2.16.3 ----Next_Part(Tue_Apr_02_19_54_06_2019_801)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v10-0007-Remove-TABLE-HEAP_INSERT_SKIP_WAL.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 6/7] Fix WAL skipping feature. @ 2019-04-02 04:29 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2019-04-02 04:29 UTC (permalink / raw) This patch replaces WAL-skipping means from HEAP_INSERT_SKIP_WAL to the new infrastructure. --- src/backend/access/heap/heapam.c | 133 ++++++++++++++++++++++++------- src/backend/access/heap/heapam_handler.c | 87 +++++++++++++++----- src/backend/access/heap/pruneheap.c | 3 +- src/backend/access/heap/rewriteheap.c | 28 ++----- src/backend/access/heap/vacuumlazy.c | 6 +- src/backend/access/heap/visibilitymap.c | 3 +- src/backend/commands/cluster.c | 27 +++++++ src/backend/commands/copy.c | 15 +++- src/backend/commands/createas.c | 7 +- src/backend/commands/matview.c | 7 +- src/backend/commands/tablecmds.c | 8 +- src/include/access/rewriteheap.h | 2 +- 12 files changed, 237 insertions(+), 89 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 223be30eb3..ae70798b3c 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -28,6 +28,27 @@ * 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 transaction, because for + * a small number of changes, it's cheaper to just create the WAL records + * than fsync()ing the whole relation at COMMIT. It is only worthwhile for + * (presumably) large operations like COPY, CLUSTER, or VACUUM FULL. Use + * table_relation_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 + * BufferNeedsWAL() to check if WAL-logging is needed in this transaction + * for the given block. + * *------------------------------------------------------------------------- */ #include "postgres.h" @@ -51,6 +72,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" @@ -1948,7 +1970,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; @@ -2065,7 +2087,6 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, int ndone; PGAlignedBlock scratch; Page page; - bool needwal; Size saveFreeSpace; bool need_tuple_data = RelationIsLogicallyLogged(relation); bool need_cids = RelationIsAccessibleInLogicalDecoding(relation); @@ -2073,7 +2094,6 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); @@ -2122,6 +2142,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, Buffer vmbuffer = InvalidBuffer; bool all_visible_cleared = false; int nthispage; + bool needwal; CHECK_FOR_INTERRUPTS(); @@ -2133,6 +2154,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, InvalidBuffer, options, bistate, &vmbuffer, NULL); page = BufferGetPage(buffer); + needwal = BufferNeedsWAL(relation, buffer); /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); @@ -2681,7 +2703,7 @@ l1: * NB: heap_abort_speculative() uses the same xlog record and replay * routines. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; @@ -2820,6 +2842,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, vmbuffer = InvalidBuffer, vmbuffer_new = InvalidBuffer; bool need_toast; + bool oldbuf_needs_wal, + newbuf_needs_wal; Size newtupsize, pagefree; bool have_tuple_lock = false; @@ -3371,7 +3395,7 @@ l2: MarkBufferDirty(buffer); - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -3585,26 +3609,74 @@ l2: MarkBufferDirty(newbuf); MarkBufferDirty(buffer); - /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + /* + * XLOG stuff + * + * Emit heap-update log. When wal_level = minimal, we may emit insert or + * delete record according to wal-optimization. + */ + oldbuf_needs_wal = BufferNeedsWAL(relation, buffer); + + if (newbuf == buffer) + newbuf_needs_wal = oldbuf_needs_wal; + else + newbuf_needs_wal = BufferNeedsWAL(relation, newbuf); + + if (oldbuf_needs_wal || newbuf_needs_wal) { XLogRecPtr recptr; /* * For logical decoding we need combocids to properly decode the - * catalog. + * catalog. Both oldbuf_needs_wal and newbuf_needs_wal must be true + * when logical decoding is active. */ if (RelationIsAccessibleInLogicalDecoding(relation)) { + Assert(oldbuf_needs_wal && newbuf_needs_wal); + log_heap_new_cid(relation, &oldtup); log_heap_new_cid(relation, heaptup); } - recptr = log_heap_update(relation, buffer, - newbuf, &oldtup, heaptup, - old_key_tuple, - all_visible_cleared, - all_visible_cleared_new); + /* + * Insert log record. When we are not running WAL-skipping, always use + * update log. Otherwise use delete or insert log instead when only + * one of the two buffers needs WAL-logging. If this were a + * HOT-update, redoing the WAL record would result in a broken + * hot-chain. However, that never happens because updates complete on + * a single page always use log_update. + * + * Using delete or insert log in place of udpate log leads to + * inconsistent series of WAL records. But note that WAL-skipping + * happens only when we are updating a tuple in a relation that has + * been create in the same transaction. Once commited, the WAL records + * recovers the same state of the relation as the synced state at the + * commit. Or the maybe-broken relation due to a crash before commit + * will be removed in recovery. + */ + if (oldbuf_needs_wal && newbuf_needs_wal) + recptr = log_heap_update(relation, buffer, newbuf, + &oldtup, heaptup, + old_key_tuple, + all_visible_cleared, + all_visible_cleared_new); + else if (oldbuf_needs_wal) + recptr = log_heap_delete(relation, buffer, &oldtup, old_key_tuple, + xmax_old_tuple, false, + all_visible_cleared); + else + { + /* + * Coming here means that the old tuple is invisible and + * inoperable to another transaction. So xmax_new_tuple is + * expected to be InvalidTransactionId here. + */ + Assert (xmax_new_tuple == InvalidTransactionId); + recptr = log_heap_insert(relation, buffer, newtup, + 0, all_visible_cleared_new); + } + if (newbuf != buffer) { PageSetLSN(BufferGetPage(newbuf), recptr); @@ -4482,7 +4554,7 @@ failed: * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG * entries for everything anyway.) */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, *buffer)) { xl_heap_lock xlrec; XLogRecPtr recptr; @@ -5234,7 +5306,7 @@ l4: MarkBufferDirty(buf); /* XLOG stuff */ - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, buf)) { xl_heap_lock_updated xlrec; XLogRecPtr recptr; @@ -5394,7 +5466,7 @@ heap_finish_speculative(Relation relation, ItemPointer tid) htup->t_ctid = *tid; /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_confirm xlrec; XLogRecPtr recptr; @@ -5526,7 +5598,7 @@ heap_abort_speculative(Relation relation, ItemPointer tid) * The WAL records generated here match heap_delete(). The same recovery * routines are used. */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_delete xlrec; XLogRecPtr recptr; @@ -5635,7 +5707,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple) MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { xl_heap_inplace xlrec; XLogRecPtr recptr; @@ -7045,8 +7117,8 @@ log_heap_clean(Relation reln, Buffer buffer, xl_heap_clean xlrec; XLogRecPtr recptr; - /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + /* Caller should not call me on non-WAL-logged buffers */ + Assert(BufferNeedsWAL(reln, buffer)); xlrec.latestRemovedXid = latestRemovedXid; xlrec.nredirected = nredirected; @@ -7093,8 +7165,8 @@ log_heap_freeze(Relation reln, Buffer buffer, TransactionId cutoff_xid, xl_heap_freeze_page xlrec; XLogRecPtr recptr; - /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + /* Caller should not call me on non-WAL-logged buffers */ + Assert(BufferNeedsWAL(reln, buffer)); /* nor when there are no tuples to freeze */ Assert(ntuples > 0); @@ -7309,8 +7381,8 @@ log_heap_update(Relation reln, Buffer oldbuf, bool init; int bufflags; - /* Caller should not call me on a non-WAL-logged relation */ - Assert(RelationNeedsWAL(reln)); + /* Caller should not call me unless both buffers need WAL-logging */ + Assert(BufferNeedsWAL(reln, newbuf) && BufferNeedsWAL(reln, oldbuf)); XLogBeginInsert(); @@ -8914,9 +8986,16 @@ heap2_redo(XLogReaderState *record) * heap_sync - sync a heap, for use when no WAL has been written * * This forces the heap contents (including TOAST heap if any) down to disk. - * If we skipped using WAL, and WAL is otherwise needed, we must force the - * relation down to disk before it's safe to commit the transaction. This - * requires writing out any dirty buffers and then doing a forced fsync. + * If we did any changes to the heap bypassing the buffer manager, we must + * force the relation down to disk before it's safe to commit the + * transaction, because the direct modifications will not be flushed by + * the next checkpoint. + * + * We used to also use this after batch operations like COPY and CLUSTER, + * if we skipped using WAL and WAL is otherwise needed, but there were + * corner-cases involving other WAL-logged operations to the same + * relation, where that was not enough. table_relation_register_sync() should + * be used for that purpose instead. * * Indexes are not touched. (Currently, index operations associated with * the commands that use this are WAL-logged and so do not need fsync. diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index add0d65f81..0c763f3a33 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -58,6 +58,8 @@ static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, OffsetNumber tupoffset); static BlockNumber heapam_scan_get_blocks_done(HeapScanDesc hscan); +static void heapam_relation_register_walskip(Relation rel); +static void heapam_relation_invalidate_walskip(Relation rel); static const TableAmRoutine heapam_methods; @@ -543,14 +545,10 @@ tuple_lock_retry: } static void -heapam_finish_bulk_insert(Relation relation, int options) +heapam_finish_bulk_insert(RelFileNode rnode, ForkNumber forkNum) { - /* - * If we skipped writing WAL, then we need to sync the heap (but not - * indexes since those use WAL anyway / don't go through tableam) - */ - if (options & HEAP_INSERT_SKIP_WAL) - heap_sync(relation); + /* Sync the file immedately */ + smgrimmedsync(smgropen(rnode, InvalidBackendId), forkNum); } @@ -618,6 +616,12 @@ heapam_relation_copy_data(Relation rel, RelFileNode newrnode) dstrel = smgropen(newrnode, rel->rd_backend); RelationOpenSmgr(rel); + /* + * Register WAL-skipping for the relation. WAL-logging is skipped and sync + * the file at commit if the AM supports the feature. + */ + table_relation_register_walskip(rel); + /* * Create and copy all forks of the relation, and schedule unlinking of * old physical files. @@ -628,8 +632,7 @@ heapam_relation_copy_data(Relation rel, RelFileNode newrnode) RelationCreateStorage(newrnode, rel->rd_rel->relpersistence); /* copy main fork */ - RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM, - rel->rd_rel->relpersistence); + RelationCopyStorage(rel, dstrel, MAIN_FORKNUM); /* copy those extra forks that exist */ for (ForkNumber forkNum = MAIN_FORKNUM + 1; @@ -647,8 +650,7 @@ heapam_relation_copy_data(Relation rel, RelFileNode newrnode) (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED && forkNum == INIT_FORKNUM)) log_smgrcreate(&newrnode, forkNum); - RelationCopyStorage(rel->rd_smgr, dstrel, forkNum, - rel->rd_rel->relpersistence); + RelationCopyStorage(rel, dstrel, forkNum); } } @@ -672,7 +674,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, IndexScanDesc indexScan; TableScanDesc tableScan; HeapScanDesc heapScan; - bool use_wal; bool is_system_catalog; Tuplesortstate *tuplesort; TupleDesc oldTupDesc = RelationGetDescr(OldHeap); @@ -686,15 +687,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Remember if it's a system catalog */ is_system_catalog = IsSystemRelation(OldHeap); - /* - * We need to log the copied data in WAL iff WAL archiving/streaming is - * enabled AND it's a WAL-logged rel. - */ - use_wal = XLogIsNeeded() && RelationNeedsWAL(NewHeap); - - /* use_wal off requires smgr_targblock be initially invalid */ - Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber); - /* Preallocate values/isnull arrays */ natts = newTupDesc->natts; values = (Datum *) palloc(natts * sizeof(Datum)); @@ -702,7 +694,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Initialize the rewrite operation */ rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, FreezeXid, - MultiXactCutoff, use_wal); + MultiXactCutoff); /* Set up sorting if wanted */ @@ -948,6 +940,55 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, pfree(isnull); } +/* + * heapam_relation_register_walskip - register a heap to be WAL-skipped then + * synced to disk at commit + * + * This can be used to skip WAL-logging changes on a relation file. 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. + * + * This does the same for the TOAST heap, if any. Indexes are not affected. + */ +static void +heapam_relation_register_walskip(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); + } + + return; +} + +/* + * heapam_relation_invalidate_walskip - invalidate registered WAL skipping + * + * After some file-replacing operations like CLUSTER, the old file no longe + * needs to be synced to disk. This function invalidates the registered + * WAL-skipping on the current relfilenode of the relation. + */ +static void +heapam_relation_invalidate_walskip(Relation rel) +{ + /* non-WAL-logged tables never need fsync */ + if (!RelationNeedsWAL(rel)) + return; + + RelationInvalidateWALSkip(rel); +} + static bool heapam_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, BufferAccessStrategy bstrategy) @@ -2531,6 +2572,8 @@ static const TableAmRoutine heapam_methods = { .relation_nontransactional_truncate = heapam_relation_nontransactional_truncate, .relation_copy_data = heapam_relation_copy_data, .relation_copy_for_cluster = heapam_relation_copy_for_cluster, + .relation_register_walskip = heapam_relation_register_walskip, + .relation_invalidate_walskip = heapam_relation_invalidate_walskip, .relation_vacuum = heap_vacuum_rel, .scan_analyze_next_block = heapam_scan_analyze_next_block, .scan_analyze_next_tuple = heapam_scan_analyze_next_tuple, diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index a3e51922d8..a05659b168 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -20,6 +20,7 @@ #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" @@ -258,7 +259,7 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin, /* * Emit a WAL HEAP_CLEAN record showing what we did */ - if (RelationNeedsWAL(relation)) + if (BufferNeedsWAL(relation, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index bce4274362..494f7fcd41 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "access/xloginsert.h" #include "catalog/catalog.h" +#include "catalog/storage.h" #include "lib/ilist.h" @@ -144,7 +145,6 @@ typedef struct RewriteStateData Page rs_buffer; /* page currently being built */ BlockNumber rs_blockno; /* block where page will go */ bool rs_buffer_valid; /* T if any tuples in buffer */ - bool rs_use_wal; /* must we WAL-log inserts? */ bool rs_logical_rewrite; /* do we need to do logical rewriting */ TransactionId rs_oldest_xmin; /* oldest xmin used by caller to determine * tuple visibility */ @@ -238,15 +238,13 @@ static void logical_end_heap_rewrite(RewriteState state); * oldest_xmin xid used by the caller to determine which tuples are dead * freeze_xid xid before which tuples will be frozen * min_multi multixact before which multis will be removed - * use_wal should the inserts to the new heap be WAL-logged? * * Returns an opaque RewriteState, allocated in current memory context, * to be used in subsequent calls to the other functions. */ RewriteState begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xmin, - TransactionId freeze_xid, MultiXactId cutoff_multi, - bool use_wal) + TransactionId freeze_xid, MultiXactId cutoff_multi) { RewriteState state; MemoryContext rw_cxt; @@ -271,7 +269,6 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm /* new_heap needn't be empty, just locked */ state->rs_blockno = RelationGetNumberOfBlocks(new_heap); state->rs_buffer_valid = false; - state->rs_use_wal = use_wal; state->rs_oldest_xmin = oldest_xmin; state->rs_freeze_xid = freeze_xid; state->rs_cutoff_multi = cutoff_multi; @@ -330,7 +327,7 @@ end_heap_rewrite(RewriteState state) /* Write the last page, if any */ if (state->rs_buffer_valid) { - if (state->rs_use_wal) + if (BlockNeedsWAL(state->rs_new_rel, state->rs_blockno)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, @@ -344,19 +341,7 @@ end_heap_rewrite(RewriteState state) (char *) state->rs_buffer, true); } - /* - * If the rel is WAL-logged, must fsync before commit. We use heap_sync - * to ensure that the toast table gets fsync'd too. - * - * It's obvious that we must do this when not WAL-logging. It's less - * obvious that we have to do it even if we did WAL-log the pages. The - * reason is the same as in tablecmds.c's copy_relation_data(): we're - * writing data that's not in shared buffers, and so a CHECKPOINT - * occurring during the rewriteheap operation won't have fsync'd data we - * wrote before the checkpoint. - */ - if (RelationNeedsWAL(state->rs_new_rel)) - heap_sync(state->rs_new_rel); + /* If we skipped using WAL, we will sync the relation at commit */ logical_end_heap_rewrite(state); @@ -654,9 +639,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is @@ -695,7 +677,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup) /* Doesn't fit, so write out the existing page */ /* XLOG stuff */ - if (state->rs_use_wal) + if (BlockNeedsWAL(state->rs_new_rel, state->rs_blockno)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index c9d83128d5..3d8d01b10f 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -959,7 +959,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, * page has been previously WAL-logged, and if not, do that * now. */ - if (RelationNeedsWAL(onerel) && + if (BufferNeedsWAL(onerel, buf) && PageGetLSN(page) == InvalidXLogRecPtr) log_newpage_buffer(buf, true); @@ -1233,7 +1233,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, } /* Now WAL-log freezing if necessary */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buf)) { XLogRecPtr recptr; @@ -1644,7 +1644,7 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, MarkBufferDirty(buffer); /* XLOG stuff */ - if (RelationNeedsWAL(onerel)) + if (BufferNeedsWAL(onerel, buffer)) { XLogRecPtr recptr; diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 64dfe06b26..1f5f7d92dd 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -88,6 +88,7 @@ #include "access/heapam_xlog.h" #include "access/visibilitymap.h" #include "access/xlog.h" +#include "catalog/storage.h" #include "miscadmin.h" #include "port/pg_bitutils.h" #include "storage/bufmgr.h" @@ -276,7 +277,7 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, map[mapByte] |= (flags << mapOffset); MarkBufferDirty(vmBuf); - if (RelationNeedsWAL(rel)) + if (BufferNeedsWAL(rel, heapBuf)) { if (XLogRecPtrIsInvalid(recptr)) { diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 4f4be1efbf..b5db26fda5 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -612,6 +612,18 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose) relpersistence, AccessExclusiveLock); + /* + * If wal_level is minimal, we skip WAL-logging even for WAL-logging + * relations. The filenode is synced at commit. + */ + if (!XLogIsNeeded()) + { + /* make_new_heap doesn't lock OIDNewHeap */ + Relation newheap = table_open(OIDNewHeap, AccessShareLock); + table_relation_register_walskip(newheap); + table_close(newheap, AccessShareLock); + } + /* Copy the heap data into the new table in the desired order */ copy_table_data(OIDNewHeap, tableOid, indexOid, verbose, &swap_toast_by_content, &frozenXid, &cutoffMulti); @@ -1355,6 +1367,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, /* Zero out possible results from swapped_relation_files */ memset(mapped_tables, 0, sizeof(mapped_tables)); + /* + * Unregister useless pending file-sync. table_relation_unregister_sync + * relies on a premise that relation cache has the correct relfilenode and + * related members. After swap_relation_files, the relcache entry for the + * heaps gets inconsistent with pg_class entry so we should do this before + * the call. + */ + if (!XLogIsNeeded()) + { + Relation oldheap = table_open(OIDOldHeap, AccessShareLock); + + table_relation_invalidate_walskip(oldheap); + table_close(oldheap, AccessShareLock); + } + /* * Swap the contents of the heap relations (including any toast tables). * Also set old heap's relfrozenxid to frozenXid. diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index c39218f8db..046acc9fbf 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2762,9 +2762,13 @@ CopyFrom(CopyState cstate) (cstate->rel->rd_createSubid != InvalidSubTransactionId || cstate->rel->rd_newRelfilenodeSubid != InvalidSubTransactionId)) { - ti_options |= TABLE_INSERT_SKIP_FSM; + /* + * 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()) - ti_options |= TABLE_INSERT_SKIP_WAL; + table_relation_register_walskip(cstate->rel); + ti_options |= TABLE_INSERT_SKIP_FSM; } /* @@ -3369,7 +3373,12 @@ CopyFrom(CopyState cstate) FreeExecutorState(estate); - table_finish_bulk_insert(cstate->rel, ti_options); + /* + * 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; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 43c2fa9124..8b73654413 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -558,8 +558,9 @@ 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->ti_options = TABLE_INSERT_SKIP_FSM | - (XLogIsNeeded() ? 0 : TABLE_INSERT_SKIP_WAL); + if (!XLogIsNeeded()) + table_relation_register_walskip(intoRelationDesc); + myState->ti_options = HEAP_INSERT_SKIP_FSM; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -604,7 +605,7 @@ intorel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->rel, myState->ti_options); + /* If we skipped using WAL, we will sync the relation at commit */ /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 2aac63296b..33b7bc4c16 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -462,9 +462,10 @@ transientrel_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->ti_options = TABLE_INSERT_SKIP_FSM | TABLE_INSERT_FROZEN; if (!XLogIsNeeded()) - myState->ti_options |= TABLE_INSERT_SKIP_WAL; + table_relation_register_walskip(transientrel); + myState->ti_options = TABLE_INSERT_SKIP_FSM | TABLE_INSERT_FROZEN; + myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,7 +510,7 @@ transientrel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->transientrel, myState->ti_options); + /* If we skipped using WAL, we will sync the relation at commit */ /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 013eb203f4..85555f87fb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4728,7 +4728,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) ti_options = TABLE_INSERT_SKIP_FSM; if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; + { + /* Forget old relation's registerd sync */ + table_relation_invalidate_walskip(oldrel); + table_relation_register_walskip(newrel); + } } else { @@ -5012,7 +5016,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { FreeBulkInsertState(bistate); - table_finish_bulk_insert(newrel, ti_options); + /* If we skipped writing WAL, then it will be done at commit. */ table_close(newrel, NoLock); } diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 6006249d96..64efecf48b 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -23,7 +23,7 @@ typedef struct RewriteStateData *RewriteState; extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap, TransactionId OldestXmin, TransactionId FreezeXid, - MultiXactId MultiXactCutoff, bool use_wal); + MultiXactId MultiXactCutoff); extern void end_heap_rewrite(RewriteState state); extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple, HeapTuple newTuple); -- 2.16.3 ----Next_Part(Fri_Apr_05_12_55_20_2019_986)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v11-0007-Remove-TABLE-HEAP_INSERT_SKIP_WAL.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 2/2] Fix WAL skipping feature @ 2019-05-20 06:38 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2019-05-20 06:38 UTC (permalink / raw) WAL-skipping operations mixed with WAL-logged operations can lead to database corruption after a crash. This patch changes the WAL-skipping feature so that no data modifcation is WAL-logged at all then sync such relations at commit. --- src/backend/access/brin/brin.c | 2 + src/backend/access/gin/ginutil.c | 2 + src/backend/access/gist/gist.c | 2 + src/backend/access/hash/hash.c | 2 + src/backend/access/heap/heapam.c | 8 +- src/backend/access/heap/heapam_handler.c | 24 ++---- src/backend/access/heap/rewriteheap.c | 12 +-- src/backend/access/index/indexam.c | 18 +++++ src/backend/access/nbtree/nbtree.c | 13 ++++ src/backend/access/transam/xact.c | 6 ++ src/backend/commands/cluster.c | 29 ++++++++ src/backend/commands/copy.c | 38 ++-------- src/backend/commands/createas.c | 5 +- src/backend/commands/matview.c | 4 - src/backend/commands/tablecmds.c | 10 +-- src/backend/utils/cache/relcache.c | 123 ++++++++++++++++++++++++++++++- src/include/access/amapi.h | 6 ++ src/include/access/genam.h | 1 + src/include/access/heapam.h | 1 - src/include/access/nbtree.h | 1 + src/include/access/rewriteheap.h | 2 +- src/include/access/tableam.h | 47 ++++++------ src/include/utils/rel.h | 35 ++++++++- src/include/utils/relcache.h | 4 + 24 files changed, 289 insertions(+), 106 deletions(-) diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index ae7b729edd..4b48f44949 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -125,6 +125,8 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amatcommitsync = NULL; + PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index cf9699ad18..f4f0eebec5 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -77,6 +77,8 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amatcommitsync = NULL; + PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index 45c00aaa87..ebaf4495b8 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -99,6 +99,8 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amatcommitsync = NULL; + PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index e9f2c84af1..ce7ac58204 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -98,6 +98,8 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amatcommitsync = NULL; + PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 6c342635e8..642e7d0cc5 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -1950,7 +1950,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (RelationNeedsWAL(relation)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2133,7 +2133,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); + needwal = RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); @@ -8906,10 +8906,6 @@ heap2_redo(XLogReaderState *record) void heap_sync(Relation rel) { - /* non-WAL-logged tables never need fsync */ - if (!RelationNeedsWAL(rel)) - return; - /* main heap */ FlushRelationBuffers(rel); /* FlushRelationBuffers will have opened rd_smgr */ diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index a4a28e88ec..17126e599b 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -559,15 +559,14 @@ tuple_lock_retry: return result; } +/* ------------------------------------------------------------------------ + * WAL-skipping related routine + * ------------------------------------------------------------------------ + */ static void -heapam_finish_bulk_insert(Relation relation, int options) +heapam_at_commit_sync(Relation relation) { - /* - * If we skipped writing WAL, then we need to sync the heap (but not - * indexes since those use WAL anyway / don't go through tableam) - */ - if (options & HEAP_INSERT_SKIP_WAL) - heap_sync(relation); + heap_sync(relation); } @@ -702,7 +701,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, IndexScanDesc indexScan; TableScanDesc tableScan; HeapScanDesc heapScan; - bool use_wal; bool is_system_catalog; Tuplesortstate *tuplesort; TupleDesc oldTupDesc = RelationGetDescr(OldHeap); @@ -716,12 +714,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Remember if it's a system catalog */ is_system_catalog = IsSystemRelation(OldHeap); - /* - * We need to log the copied data in WAL iff WAL archiving/streaming is - * enabled AND it's a WAL-logged rel. - */ - use_wal = XLogIsNeeded() && RelationNeedsWAL(NewHeap); - /* use_wal off requires smgr_targblock be initially invalid */ Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber); @@ -732,7 +724,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Initialize the rewrite operation */ rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff, - *multi_cutoff, use_wal); + *multi_cutoff); /* Set up sorting if wanted */ @@ -2626,7 +2618,7 @@ static const TableAmRoutine heapam_methods = { .tuple_delete = heapam_tuple_delete, .tuple_update = heapam_tuple_update, .tuple_lock = heapam_tuple_lock, - .finish_bulk_insert = heapam_finish_bulk_insert, + .at_commit_sync = heapam_at_commit_sync, .tuple_fetch_row_version = heapam_fetch_row_version, .tuple_get_latest_tid = heap_get_latest_tid, diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 131ec7b8d7..617eec582b 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -144,7 +144,6 @@ typedef struct RewriteStateData Page rs_buffer; /* page currently being built */ BlockNumber rs_blockno; /* block where page will go */ bool rs_buffer_valid; /* T if any tuples in buffer */ - bool rs_use_wal; /* must we WAL-log inserts? */ bool rs_logical_rewrite; /* do we need to do logical rewriting */ TransactionId rs_oldest_xmin; /* oldest xmin used by caller to determine * tuple visibility */ @@ -245,8 +244,7 @@ static void logical_end_heap_rewrite(RewriteState state); */ RewriteState begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xmin, - TransactionId freeze_xid, MultiXactId cutoff_multi, - bool use_wal) + TransactionId freeze_xid, MultiXactId cutoff_multi) { RewriteState state; MemoryContext rw_cxt; @@ -271,7 +269,6 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm /* new_heap needn't be empty, just locked */ state->rs_blockno = RelationGetNumberOfBlocks(new_heap); state->rs_buffer_valid = false; - state->rs_use_wal = use_wal; state->rs_oldest_xmin = oldest_xmin; state->rs_freeze_xid = freeze_xid; state->rs_cutoff_multi = cutoff_multi; @@ -330,7 +327,7 @@ end_heap_rewrite(RewriteState state) /* Write the last page, if any */ if (state->rs_buffer_valid) { - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, @@ -654,9 +651,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is @@ -695,7 +689,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup) /* Doesn't fit, so write out the existing page */ /* XLOG stuff */ - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index aefdd2916d..ade721a383 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -33,6 +33,7 @@ * index_can_return - does index support index-only scans? * index_getprocid - get a support procedure OID * index_getprocinfo - get a support procedure's lookup info + * index_at_commit_sync - perform at_commit_sync * * NOTES * This file contains the index_ routines which used @@ -837,6 +838,23 @@ index_getprocinfo(Relation irel, return locinfo; } +/* ---------------- + * index_at_commit_sync + * + * An index AM that defines this interface can allow derived objects created + * in the current transaction to skip WAL-logging. This routine is called + * commit-time and the AM must flush buffer and sync the underlying storage. + * + * Optional interface + * ---------------- + */ +void +index_at_commit_sync(Relation irel) +{ + if (irel->rd_indam && irel->rd_indam->amatcommitsync) + irel->rd_indam->amatcommitsync(irel); +} + /* ---------------- * index_store_float8_orderby_distances * diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 85e54ac44b..695b058b85 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -147,6 +147,8 @@ bthandler(PG_FUNCTION_ARGS) amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amatcommitsync = btatcommitsync; + PG_RETURN_POINTER(amroutine); } @@ -1385,3 +1387,14 @@ btcanreturn(Relation index, int attno) { return true; } + +/* + * btatcommitsync() -- Perform at-commit sync of WAL-skipped index + */ +void +btatcommitsync(Relation index) +{ + FlushRelationBuffers(index); + smgrimmedsync(index->rd_smgr, MAIN_FORKNUM); +} + diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index f1108ccc8b..0670985bc2 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -2120,6 +2120,9 @@ CommitTransaction(void) if (!is_parallel_worker) PreCommit_CheckForSerializationFailure(); + /* Sync WAL-skipped relations */ + PreCommit_RelationSync(); + /* * Insert notifications sent by NOTIFY commands into the queue. This * should be late in the pre-commit sequence to minimize time spent @@ -2395,6 +2398,9 @@ PrepareTransaction(void) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot PREPARE a transaction that has manipulated logical replication workers"))); + /* Sync WAL-skipped relations */ + PreCommit_RelationSync(); + /* Prevent cancel/die interrupt while cleaning up */ HOLD_INTERRUPTS(); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index ebaec4f8dd..504a04104f 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1034,12 +1034,41 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, if (OidIsValid(relfilenode1) && OidIsValid(relfilenode2)) { + Relation rel1; + Relation rel2; + /* * Normal non-mapped relations: swap relfilenodes, reltablespaces, * relpersistence */ Assert(!target_is_pg_class); + /* Update creation subid hints of relcache */ + rel1 = relation_open(r1, ExclusiveLock); + rel2 = relation_open(r2, ExclusiveLock); + + /* + * New relation's relfilenode is created in the current transaction + * and used as old ralation's new relfilenode. So its + * newRelfilenodeSubid as new relation's createSubid. We don't fix + * rel2 since it would be deleted soon. + */ + Assert(rel2->rd_createSubid != InvalidSubTransactionId); + rel1->rd_newRelfilenodeSubid = rel2->rd_createSubid; + + /* record the first relfilenode change in the current transaction */ + if (rel1->rd_firstRelfilenodeSubid == InvalidSubTransactionId) + { + rel1->rd_firstRelfilenodeSubid = GetCurrentSubTransactionId(); + + /* Flag the old relation as needing eoxact cleanup */ + RelationEOXactListAdd(rel1); + } + + relation_close(rel1, ExclusiveLock); + relation_close(rel2, ExclusiveLock); + + /* swap relfilenodes, reltablespaces, relpersistence */ swaptemp = relform1->relfilenode; relform1->relfilenode = relform2->relfilenode; relform2->relfilenode = swaptemp; diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index b00891ffd2..77608c09c3 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2720,28 +2720,9 @@ CopyFrom(CopyState cstate) * If it does commit, we'll have done the table_finish_bulk_insert() 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 - * can be cleared before the end of the transaction. The exact case is - * when a relation sets a new relfilenode twice in same transaction, yet - * the second one fails in an aborted subtransaction, e.g. - * - * BEGIN; - * TRUNCATE t; - * SAVEPOINT save; - * TRUNCATE t; - * ROLLBACK TO save; - * COPY ... - * - * Also, if the target file is new-in-transaction, we assume that checking - * FSM for free space is a waste of time, even if we must use WAL because - * of archiving. This could possibly be wrong, but it's unlikely. - * - * The comments for table_insert and RelationGetBufferForTuple specify that - * skipping WAL logging is only safe if we ensure that our tuples do not - * go into pages containing tuples from any other transactions --- but this - * must be the case if we have a new table or new relfilenode, so we need - * no additional work to enforce that. + * If the target file is new-in-transaction, we assume that checking FSM + * for free space is a waste of time, even if we must use WAL because of + * archiving. This could possibly be wrong, but it's unlikely. * * We currently don't support this optimization if the COPY target is a * partitioned table as we currently only lazily initialize partition @@ -2757,15 +2738,14 @@ CopyFrom(CopyState cstate) * are not supported as per the description above. *---------- */ - /* createSubid is creation check, newRelfilenodeSubid is truncation check */ + /* + * createSubid is creation check, firstRelfilenodeSubid is truncation and + * cluster check. Partitioned table doesn't have storage. + */ if (RELKIND_HAS_STORAGE(cstate->rel->rd_rel->relkind) && (cstate->rel->rd_createSubid != InvalidSubTransactionId || - cstate->rel->rd_newRelfilenodeSubid != InvalidSubTransactionId)) - { + cstate->rel->rd_firstRelfilenodeSubid != InvalidSubTransactionId)) ti_options |= TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; - } /* * Optimize if new relfilenode was created in this subxact or one of its @@ -3364,8 +3344,6 @@ CopyFrom(CopyState cstate) FreeExecutorState(estate); - table_finish_bulk_insert(cstate->rel, ti_options); - return processed; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 43c2fa9124..859b869b0d 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -558,8 +558,7 @@ 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->ti_options = TABLE_INSERT_SKIP_FSM | - (XLogIsNeeded() ? 0 : TABLE_INSERT_SKIP_WAL); + myState->ti_options = TABLE_INSERT_SKIP_FSM; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -604,8 +603,6 @@ intorel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->rel, myState->ti_options); - /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); myState->rel = NULL; diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index dc2940cd4e..583c542121 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -463,8 +463,6 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) * replication is in use. We can skip the FSM in any case. */ myState->ti_options = TABLE_INSERT_SKIP_FSM | TABLE_INSERT_FROZEN; - if (!XLogIsNeeded()) - myState->ti_options |= TABLE_INSERT_SKIP_WAL; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,8 +507,6 @@ transientrel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->transientrel, myState->ti_options); - /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); myState->transientrel = NULL; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 602a8dbd1c..f63662f4ed 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4733,9 +4733,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) /* * Prepare a BulkInsertState and options for table_insert. Because we're - * building a new heap, we can skip WAL-logging and fsync it to disk at - * the end instead (unless WAL-logging is required for archiving or - * streaming replication). The FSM is empty too, so don't bother using it. + * building a new heap, the underlying table AM can skip WAL-logging and + * fsync the relation to disk at the end of the current transaction + * instead. The FSM is empty too, so don't bother using it. */ if (newrel) { @@ -4743,8 +4743,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); ti_options = TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; } else { @@ -5028,8 +5026,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { FreeBulkInsertState(bistate); - table_finish_bulk_insert(newrel, ti_options); - table_close(newrel, NoLock); } } diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 2b992d7832..cd418c5f80 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -177,6 +177,13 @@ static bool eoxact_list_overflowed = false; eoxact_list_overflowed = true; \ } while (0) +/* Function version of the macro above */ +void +RelationEOXactListAdd(Relation rel) +{ + EOXactListAdd(rel); +} + /* * EOXactTupleDescArray stores TupleDescs that (might) need AtEOXact * cleanup work. The array expands as needed; there is no hashtable because @@ -263,6 +270,7 @@ static void RelationReloadIndexInfo(Relation relation); static void RelationReloadNailed(Relation relation); static void RelationFlushRelation(Relation relation); static void RememberToFreeTupleDescAtEOX(TupleDesc td); +static void PreCommit_SyncOneRelation(Relation relation); static void AtEOXact_cleanup(Relation relation, bool isCommit); static void AtEOSubXact_cleanup(Relation relation, bool isCommit, SubTransactionId mySubid, SubTransactionId parentSubid); @@ -1512,6 +1520,10 @@ RelationInitIndexAccessInfo(Relation relation) relation->rd_exclprocs = NULL; relation->rd_exclstrats = NULL; relation->rd_amcache = NULL; + + /* set AM-type-independent WAL-skip flag if this am supports it */ + if (relation->rd_indam->amatcommitsync != NULL) + relation->rd_can_skipwal = true; } /* @@ -1781,6 +1793,10 @@ RelationInitTableAccessMethod(Relation relation) * Now we can fetch the table AM's API struct */ InitTableAmRoutine(relation); + + /* set AM-type-independent WAL-skip flag if this am supports it */ + if (relation->rd_tableam && relation->rd_tableam->at_commit_sync) + relation->rd_can_skipwal = true; } /* @@ -2594,6 +2610,7 @@ RelationClearRelation(Relation relation, bool rebuild) /* creation sub-XIDs must be preserved */ SWAPFIELD(SubTransactionId, rd_createSubid); SWAPFIELD(SubTransactionId, rd_newRelfilenodeSubid); + SWAPFIELD(SubTransactionId, rd_firstRelfilenodeSubid); /* un-swap rd_rel pointers, swap contents instead */ SWAPFIELD(Form_pg_class, rd_rel); /* ... but actually, we don't have to update newrel->rd_rel */ @@ -2661,7 +2678,7 @@ static void RelationFlushRelation(Relation relation) { if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) { /* * New relcache entries are always rebuilt, not flushed; else we'd @@ -2801,7 +2818,7 @@ RelationCacheInvalidate(void) * pending invalidations. */ if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) continue; relcacheInvalsReceived++; @@ -2913,6 +2930,93 @@ RememberToFreeTupleDescAtEOX(TupleDesc td) EOXactTupleDescArray[NextEOXactTupleDescNum++] = td; } +/* + * PreCommit_RelationSync + * + * Sync relations that were WAL-skipped in this transaction . + * + * Access method may have skipped WAL-logging for relations created in the + * current transaction. Such relations need to be synced at top-transaction's + * commit. The operation requires active transaction state, so separately + * performed from AtEOXact_RelationCache. + */ +void +PreCommit_RelationSync(void) +{ + HASH_SEQ_STATUS status; + RelIdCacheEnt *idhentry; + int i; + + /* See AtEOXact_RelationCache about eoxact_list */ + if (eoxact_list_overflowed) + { + hash_seq_init(&status, RelationIdCache); + while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL) + PreCommit_SyncOneRelation(idhentry->reldesc); + } + else + { + for (i = 0; i < eoxact_list_len; i++) + { + idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache, + (void *) &eoxact_list[i], + HASH_FIND, + NULL); + + if (idhentry != NULL) + PreCommit_SyncOneRelation(idhentry->reldesc); + } + } +} + +/* + * PreCommit_SyncOneRelation + * + * Sync one relation if needed + * + * NB: this processing must be idempotent, because EOXactListAdd() doesn't + * bother to prevent duplicate entries in eoxact_list[]. + */ +static void +PreCommit_SyncOneRelation(Relation relation) +{ + HeapTuple reltup; + Form_pg_class relform; + + /* return immediately if no need for sync */ + if (!RelationNeedsAtCommitSync(relation)) + return; + + /* + * We are about to sync a WAL-skipped relation. The relfilenode here is + * wrong if the last sub transaction that created new relfilenode was + * aborted. + */ + if (relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId && + relation->rd_newRelfilenodeSubid == InvalidSubTransactionId) + { + reltup = SearchSysCache1(RELOID, ObjectIdGetDatum(relation->rd_id)); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "cache lookup failed for relation %u", relation->rd_id); + relform = (Form_pg_class) GETSTRUCT(reltup); + relation->rd_rel->relfilenode = relform->relfilenode; + relation->rd_node.relNode = relform->relfilenode; + ReleaseSysCache(reltup); + } + + if (relation->rd_tableam != NULL) + table_at_commit_sync(relation); + else + { + Assert (relation->rd_indam != NULL); + table_at_commit_sync(relation); + } + + /* We have synced the files, forget about relfilenode change */ + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; +} + /* * AtEOXact_RelationCache * @@ -3058,6 +3162,7 @@ AtEOXact_cleanup(Relation relation, bool isCommit) * Likewise, reset the hint about the relfilenode being new. */ relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; } /* @@ -3149,7 +3254,7 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, } /* - * Likewise, update or drop any new-relfilenode-in-subtransaction hint. + * Likewise, update or drop any new-relfilenode-in-subtransaction hints. */ if (relation->rd_newRelfilenodeSubid == mySubid) { @@ -3158,6 +3263,14 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, else relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; } + + if (relation->rd_firstRelfilenodeSubid == mySubid) + { + if (isCommit) + relation->rd_firstRelfilenodeSubid = parentSubid; + else + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; + } } @@ -3440,6 +3553,10 @@ RelationSetNewRelfilenode(Relation relation, char persistence) */ RelationDropStorage(relation); + /* Record the subxid where the first relfilenode change happen */ + if (relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid = GetCurrentSubTransactionId(); + /* * Create storage for the main fork of the new relfilenode. If it's a * table-like object, call into the table AM to do so, which'll also diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 6e3db06eed..75159d10d4 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -156,6 +156,9 @@ typedef void (*aminitparallelscan_function) (void *target); /* (re)start parallel index scan */ typedef void (*amparallelrescan_function) (IndexScanDesc scan); +/* sync relation at commit after skipping WAL-logging */ +typedef void (*amatcommitsync_function) (Relation indexRelation); + /* * API struct for an index AM. Note this must be stored in a single palloc'd * chunk of memory. @@ -230,6 +233,9 @@ typedef struct IndexAmRoutine amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ aminitparallelscan_function aminitparallelscan; /* can be NULL */ amparallelrescan_function amparallelrescan; /* can be NULL */ + + /* interface function to do at-commit sync after skipping WAL-logging */ + amatcommitsync_function amatcommitsync; /* can be NULL */; } IndexAmRoutine; diff --git a/src/include/access/genam.h b/src/include/access/genam.h index 8c053be2ca..8e661edfdd 100644 --- a/src/include/access/genam.h +++ b/src/include/access/genam.h @@ -177,6 +177,7 @@ extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum, uint16 procnum); extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum, uint16 procnum); +extern void index_at_commit_sync(Relation irel); extern void index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes, double *distances, bool recheckOrderBy); diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index b88bd8a4d7..187c668878 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -29,7 +29,6 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL #define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM #define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN #define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index a3583f225b..f33d2b38b5 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -717,6 +717,7 @@ extern IndexBulkDeleteResult *btbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *btvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bool btcanreturn(Relation index, int attno); +extern void btatcommitsync(Relation index); /* * prototypes for internal functions in nbtree.c diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 8056253916..7f9736e294 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -23,7 +23,7 @@ typedef struct RewriteStateData *RewriteState; extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap, TransactionId OldestXmin, TransactionId FreezeXid, - MultiXactId MultiXactCutoff, bool use_wal); + MultiXactId MultiXactCutoff); extern void end_heap_rewrite(RewriteState state); extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple, HeapTuple newTuple); diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 6f1cd382d8..759a1e806d 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -409,19 +409,15 @@ typedef struct TableAmRoutine TM_FailureData *tmfd); /* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * may for example be used to flush the relation, when the - * TABLE_INSERT_SKIP_WAL option was used. + * Sync relation at commit-time after skipping WAL-logging. * - * Typically callers of tuple_insert and multi_insert will just pass all - * the flags that apply to them, and each AM has to decide which of them - * make sense for it, and then only take actions in finish_bulk_insert for - * those flags, and ignore others. + * A table AM may skip WAL-logging for relations created in the current + * transaction. This routine is called commit-time and the table AM + * must flush buffer and sync the underlying storage. * * Optional callback. */ - void (*finish_bulk_insert) (Relation rel, int options); + void (*at_commit_sync) (Relation rel); /* ------------------------------------------------------------------------ @@ -1089,10 +1085,6 @@ table_compute_xid_horizon_for_tuples(Relation rel, * behaviour of the AM. Several options might be ignored by AMs not supporting * them. * - * If the TABLE_INSERT_SKIP_WAL option is specified, the new tuple doesn't - * need to be logged to WAL, even for a non-temp relation. It is the AMs - * choice whether this optimization is supported. - * * If the TABLE_INSERT_SKIP_FSM option is specified, AMs are free to not reuse * free space in the relation. This can save some cycles when we know the * relation is new and doesn't contain useful amounts of free space. It's @@ -1112,10 +1104,12 @@ table_compute_xid_horizon_for_tuples(Relation rel, * Note that most of these options will be applied when inserting into the * heap's TOAST table, too, if the tuple requires any out-of-line data. * + * The core function RelationNeedsWAL() considers skipping WAL-logging on + * relations created in-transaction or truncated when the AM provides + * at_commit_sync interface. * * The BulkInsertState object (if any; bistate can be NULL for default - * behavior) is also just passed through to RelationGetBufferForTuple. If - * `bistate` is provided, table_finish_bulk_insert() needs to be called. + * behavior) is also just passed through to RelationGetBufferForTuple. * * On return the slot's tts_tid and tts_tableOid are updated to reflect the * insertion. But note that any toasting of fields within the slot is NOT @@ -1205,6 +1199,8 @@ table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots, * delete it. Failure return codes are TM_SelfModified, TM_Updated, and * TM_BeingModified (the last only possible if wait == false). * + * See table_insert about skipping WAL-logging feature. + * * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, * t_xmax, and, if possible, and, if possible, t_cmax. See comments for * struct TM_FailureData for additional info. @@ -1249,6 +1245,8 @@ table_delete(Relation rel, ItemPointer tid, CommandId cid, * update was done. However, any TOAST changes in the new tuple's * data are not reflected into *newtup. * + * See table_insert about skipping WAL-logging feature. + * * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, * t_xmax, and, if possible, t_cmax. See comments for struct TM_FailureData * for additional info. @@ -1310,20 +1308,23 @@ table_lock_tuple(Relation rel, ItemPointer tid, Snapshot snapshot, } /* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * e.g. may e.g. used to flush the relation when inserting with - * TABLE_INSERT_SKIP_WAL specified. + * Sync relation at commit-time if needed. + * + * A table AM that defines this interface can allow derived objects created + * in the current transaction to skip WAL-logging. This routine is called + * commit-time and the table AM must flush buffer and sync the underlying + * storage. + * + * Optional callback. */ static inline void -table_finish_bulk_insert(Relation rel, int options) +table_at_commit_sync(Relation rel) { /* optional callback */ - if (rel->rd_tableam && rel->rd_tableam->finish_bulk_insert) - rel->rd_tableam->finish_bulk_insert(rel, options); + if (rel->rd_tableam && rel->rd_tableam->at_commit_sync) + rel->rd_tableam->at_commit_sync(rel); } - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d7f33abce3..6a3ef80575 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -63,6 +63,7 @@ typedef struct RelationData bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and * rd_replidindex) */ bool rd_statvalid; /* is rd_statlist valid? */ + bool rd_can_skipwal; /* underlying AM allow WAL-logging? */ /* * rd_createSubid is the ID of the highest subtransaction the rel has @@ -76,10 +77,17 @@ typedef struct RelationData * transaction, with one of them occurring in a subsequently aborted * subtransaction, e.g. BEGIN; TRUNCATE t; SAVEPOINT save; TRUNCATE t; * ROLLBACK TO save; -- rd_newRelfilenode is now forgotten + * rd_firstRelfilenodeSubid is the ID of the hightest subtransaction the + * relfilenode change has took place first in the current + * transaction. This won't be forgotten as newRelfilenodeSubid is. A valid + * OID means that the currently active relfilenode is transaction-local + * and no-need for WAL-logging. */ SubTransactionId rd_createSubid; /* rel was created in current xact */ SubTransactionId rd_newRelfilenodeSubid; /* new relfilenode assigned in * current xact */ + SubTransactionId rd_firstRelfilenodeSubid; /* new relfilenode assigned + * first in current xact */ Form_pg_class rd_rel; /* RELATION tuple */ TupleDesc rd_att; /* tuple descriptor */ @@ -512,9 +520,32 @@ typedef struct ViewOptions /* * RelationNeedsWAL * True if relation needs WAL. + * + * If underlying AM supports WAL-skipping feature, returns false if wal_level + * = minimal and this relation is created or truncated in the current + * transaction. */ -#define RelationNeedsWAL(relation) \ - ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) +#define RelationNeedsWAL(relation) \ + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT && \ + (!relation->rd_can_skipwal || \ + XLogIsNeeded() || \ + (relation->rd_createSubid == InvalidSubTransactionId && \ + relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId))) + +/* + * RelationNeedsAtCommitSync + * True if relation needs at-commit sync + * + * This macro is used in few places but written here because it is tightly + * related with RelationNeedsWAL() above. We don't need to sync local or temp + * relations. + */ +#define RelationNeedsAtCommitSync(relation) \ + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT && \ + !(!relation->rd_can_skipwal || \ + XLogIsNeeded() || \ + (relation->rd_createSubid == InvalidSubTransactionId && \ + relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId))) /* * RelationUsesLocalBuffers diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index d9c10ffcba..b681d3afb2 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -120,6 +120,7 @@ extern void RelationCacheInvalidate(void); extern void RelationCloseSmgrByOid(Oid relationId); +extern void PreCommit_RelationSync(void); extern void AtEOXact_RelationCache(bool isCommit); extern void AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid, SubTransactionId parentSubid); @@ -138,4 +139,7 @@ extern bool criticalRelcachesBuilt; /* should be used only by relcache.c and postinit.c */ extern bool criticalSharedRelcachesBuilt; +/* add rel to eoxact cleanup list */ +void RelationEOXactListAdd(Relation rel); + #endif /* RELCACHE_H */ -- 2.16.3 ----Next_Part(Thu_May_23_16_10_35_2019_188)---- ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 2/2] Fix WAL skipping feature @ 2019-05-20 06:38 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2019-05-20 06:38 UTC (permalink / raw) WAL-skipping operations mixed with WAL-logged operations can lead to database corruption after a crash. This patch changes the WAL-skipping feature so that no data modifcation is WAL-logged at all then sync such relations at commit. --- src/backend/access/brin/brin.c | 2 + src/backend/access/gin/ginutil.c | 2 + src/backend/access/gist/gist.c | 2 + src/backend/access/hash/hash.c | 2 + src/backend/access/heap/heapam.c | 8 +-- src/backend/access/heap/heapam_handler.c | 15 +++--- src/backend/access/heap/rewriteheap.c | 3 -- src/backend/access/index/indexam.c | 16 ++++++ src/backend/access/nbtree/nbtree.c | 13 +++++ src/backend/access/transam/xact.c | 6 +++ src/backend/commands/copy.c | 6 --- src/backend/commands/createas.c | 5 +- src/backend/commands/matview.c | 4 -- src/backend/commands/tablecmds.c | 4 -- src/backend/utils/cache/relcache.c | 87 ++++++++++++++++++++++++++++++++ src/include/access/amapi.h | 8 +++ src/include/access/genam.h | 1 + src/include/access/heapam.h | 1 - src/include/access/nbtree.h | 1 + src/include/access/tableam.h | 36 +++++++------ src/include/utils/rel.h | 21 +++++++- src/include/utils/relcache.h | 1 + 22 files changed, 188 insertions(+), 56 deletions(-) diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index aba234c0af..681520852f 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -125,6 +125,8 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amatcommitsync = NULL; + PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index cf9699ad18..f4f0eebec5 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -77,6 +77,8 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amatcommitsync = NULL; + PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index d70a138f54..3a23e7c4b2 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -99,6 +99,8 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amatcommitsync = NULL; + PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 048e40e46f..3fa8262319 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -98,6 +98,8 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amatcommitsync = NULL; + PG_RETURN_POINTER(amroutine); } diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 19d2c529d8..7f78122b81 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -1950,7 +1950,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (RelationNeedsWAL(relation)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2133,7 +2133,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); + needwal = RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); @@ -8906,10 +8906,6 @@ heap2_redo(XLogReaderState *record) void heap_sync(Relation rel) { - /* non-WAL-logged tables never need fsync */ - if (!RelationNeedsWAL(rel)) - return; - /* main heap */ FlushRelationBuffers(rel); /* FlushRelationBuffers will have opened rd_smgr */ diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 8d8161fd97..a2e1464845 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -557,15 +557,14 @@ tuple_lock_retry: return result; } +/* ------------------------------------------------------------------------ + * WAL-skipping related routine + * ------------------------------------------------------------------------ + */ static void -heapam_finish_bulk_insert(Relation relation, int options) +heapam_at_commit_sync(Relation relation) { - /* - * If we skipped writing WAL, then we need to sync the heap (but not - * indexes since those use WAL anyway / don't go through tableam) - */ - if (options & HEAP_INSERT_SKIP_WAL) - heap_sync(relation); + heap_sync(relation); } @@ -2573,7 +2572,7 @@ static const TableAmRoutine heapam_methods = { .tuple_delete = heapam_tuple_delete, .tuple_update = heapam_tuple_update, .tuple_lock = heapam_tuple_lock, - .finish_bulk_insert = heapam_finish_bulk_insert, + .at_commit_sync = heapam_at_commit_sync, .tuple_fetch_row_version = heapam_fetch_row_version, .tuple_get_latest_tid = heap_get_latest_tid, diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index bce4274362..1ac77f7c14 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -654,9 +654,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index 0fc9139bad..1d089603b7 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -33,6 +33,7 @@ * index_can_return - does index support index-only scans? * index_getprocid - get a support procedure OID * index_getprocinfo - get a support procedure's lookup info + * index_at_commit_sync - perform at_commit_sync * * NOTES * This file contains the index_ routines which used @@ -837,6 +838,21 @@ index_getprocinfo(Relation irel, return locinfo; } +/* ---------------- + * index_at_commit_sync + * + * This routine perfoms at-commit sync of index storage. This is called + * when permanent index created in the current transaction is committed. + * ---------------- + */ +void +index_at_commit_sync(Relation irel) +{ + Assert(irel->rd_indam != NULL && irel->rd_indam->amatcommitsync != NULL); + + irel->rd_indam->amatcommitsync(irel); +} + /* ---------------- * index_store_float8_orderby_distances * diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 02fb352b94..39377f35eb 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -147,6 +147,8 @@ bthandler(PG_FUNCTION_ARGS) amroutine->aminitparallelscan = btinitparallelscan; amroutine->amparallelrescan = btparallelrescan; + amroutine->amatcommitsync = btatcommitsync; + PG_RETURN_POINTER(amroutine); } @@ -1385,3 +1387,14 @@ btcanreturn(Relation index, int attno) { return true; } + +/* + * btatcommitsync() -- Perform at-commit sync of WAL-skipped index + */ +void +btatcommitsync(Relation index) +{ + FlushRelationBuffers(index); + smgrimmedsync(index->rd_smgr, MAIN_FORKNUM); +} + diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 20feeec327..bc38a53195 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -2120,6 +2120,9 @@ CommitTransaction(void) if (!is_parallel_worker) PreCommit_CheckForSerializationFailure(); + /* Sync WAL-skipped relations */ + PreCommit_RelationSync(); + /* * Insert notifications sent by NOTIFY commands into the queue. This * should be late in the pre-commit sequence to minimize time spent @@ -2395,6 +2398,9 @@ PrepareTransaction(void) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot PREPARE a transaction that has manipulated logical replication workers"))); + /* Sync WAL-skipped relations */ + PreCommit_RelationSync(); + /* Prevent cancel/die interrupt while cleaning up */ HOLD_INTERRUPTS(); diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 5f81aa57d4..a25c82438e 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2761,11 +2761,7 @@ CopyFrom(CopyState cstate) if (RELKIND_HAS_STORAGE(cstate->rel->rd_rel->relkind) && (cstate->rel->rd_createSubid != InvalidSubTransactionId || cstate->rel->rd_newRelfilenodeSubid != InvalidSubTransactionId)) - { ti_options |= TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; - } /* * Optimize if new relfilenode was created in this subxact or one of its @@ -3364,8 +3360,6 @@ CopyFrom(CopyState cstate) FreeExecutorState(estate); - table_finish_bulk_insert(cstate->rel, ti_options); - return processed; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 43c2fa9124..859b869b0d 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -558,8 +558,7 @@ 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->ti_options = TABLE_INSERT_SKIP_FSM | - (XLogIsNeeded() ? 0 : TABLE_INSERT_SKIP_WAL); + myState->ti_options = TABLE_INSERT_SKIP_FSM; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -604,8 +603,6 @@ intorel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->rel, myState->ti_options); - /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); myState->rel = NULL; diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 99bf3c29f2..c84edd0db0 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -463,8 +463,6 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) * replication is in use. We can skip the FSM in any case. */ myState->ti_options = TABLE_INSERT_SKIP_FSM | TABLE_INSERT_FROZEN; - if (!XLogIsNeeded()) - myState->ti_options |= TABLE_INSERT_SKIP_WAL; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,8 +507,6 @@ transientrel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->transientrel, myState->ti_options); - /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); myState->transientrel = NULL; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index bfcf9472d7..75f11a327d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4741,8 +4741,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); ti_options = TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; } else { @@ -5026,8 +5024,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { FreeBulkInsertState(bistate); - table_finish_bulk_insert(newrel, ti_options); - table_close(newrel, NoLock); } } diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index d0f6f715e6..4bffbfff5d 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -1512,6 +1512,9 @@ RelationInitIndexAccessInfo(Relation relation) relation->rd_exclprocs = NULL; relation->rd_exclstrats = NULL; relation->rd_amcache = NULL; + + if (relation->rd_indam->amatcommitsync != NULL) + relation->rd_can_skipwal = true; } /* @@ -1781,6 +1784,9 @@ RelationInitTableAccessMethod(Relation relation) * Now we can fetch the table AM's API struct */ InitTableAmRoutine(relation); + + if (relation->rd_tableam && relation->rd_tableam->at_commit_sync) + relation->rd_can_skipwal = true; } /* @@ -2913,6 +2919,73 @@ RememberToFreeTupleDescAtEOX(TupleDesc td) EOXactTupleDescArray[NextEOXactTupleDescNum++] = td; } +/* + * PreComimt_RelationSync + * + * Sync relations that were WAL-skipped in this transaction . + * + * AMs may have skipped WAL-logging for relations created in the current + * transaction. This let such relations be synced. This operation can only be + * perfomed while transaction status is INPROGRESS so it is separated from + * AtEOXact_RelationCache. + */ +void +PreCommit_RelationSync(void) +{ + HASH_SEQ_STATUS status; + RelIdCacheEnt *idhentry; + int i; + + /* See AtEOXact_RelationCache for details on eoxact_list */ + if (eoxact_list_overflowed) + { + hash_seq_init(&status, RelationIdCache); + while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL) + { + Relation rel = idhentry->reldesc; + + if (!RelationNeedsAtCommitSync(rel)) + continue; + + if (rel->rd_tableam != NULL) + table_at_commit_sync(rel); + else + { + Assert (rel->rd_indam != NULL); + table_at_commit_sync(rel); + } + } + } + else + { + for (i = 0; i < eoxact_list_len; i++) + { + Relation rel; + + idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache, + (void *) &eoxact_list[i], + HASH_FIND, + NULL); + + if (idhentry == NULL) + continue; + + rel = idhentry->reldesc; + + if (!RelationNeedsAtCommitSync(rel)) + continue; + + if (rel->rd_tableam != NULL) + table_at_commit_sync(rel); + else + { + Assert (rel->rd_indam != NULL); + table_at_commit_sync(rel); + } + } + } +} + /* * AtEOXact_RelationCache * @@ -3032,7 +3105,21 @@ AtEOXact_cleanup(Relation relation, bool isCommit) if (relation->rd_createSubid != InvalidSubTransactionId) { if (isCommit) + { + /* + * While wal_level=minimal, we have skipped WAL-logging on + * persistent relations created in this transaction. Sync that + * tables out before they become publicly accessible. + */ + if (!XLogIsNeeded() && relation->rd_smgr && + relation->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) + { + FlushRelationBuffers(relation); + smgrimmedsync(relation->rd_smgr, MAIN_FORKNUM); + } + relation->rd_createSubid = InvalidSubTransactionId; + } else if (RelationHasReferenceCountZero(relation)) { RelationClearRelation(relation, false); diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 09a7404267..fc6981d98a 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -156,6 +156,11 @@ typedef void (*aminitparallelscan_function) (void *target); /* (re)start parallel index scan */ typedef void (*amparallelrescan_function) (IndexScanDesc scan); +/* sync relation at commit */ +typedef void (*amatcommitsync_function) (Relation indexRelation); + + /* interface function to support WAL-skipping feature */ + /* * API struct for an index AM. Note this must be stored in a single palloc'd * chunk of memory. @@ -230,6 +235,9 @@ typedef struct IndexAmRoutine amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ aminitparallelscan_function aminitparallelscan; /* can be NULL */ amparallelrescan_function amparallelrescan; /* can be NULL */ + + /* interface function to support WAL-skipping feature */ + amatcommitsync_function amatcommitsync; /* can be NULL */; } IndexAmRoutine; diff --git a/src/include/access/genam.h b/src/include/access/genam.h index 9717183ef2..b225fd622e 100644 --- a/src/include/access/genam.h +++ b/src/include/access/genam.h @@ -177,6 +177,7 @@ extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum, uint16 procnum); extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum, uint16 procnum); +extern void index_at_commit_sync(Relation irel); extern void index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes, double *distances, bool recheckOrderBy); diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 62aaa08eff..0fb7d86bf2 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -29,7 +29,6 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL #define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM #define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN #define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 6c1acd4855..1d042e89b5 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -717,6 +717,7 @@ extern IndexBulkDeleteResult *btbulkdelete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *btvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); extern bool btcanreturn(Relation index, int attno); +extern void btatcommitsync(Relation index); /* * prototypes for internal functions in nbtree.c diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 06eae2337a..90254cb278 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -409,19 +409,15 @@ typedef struct TableAmRoutine TM_FailureData *tmfd); /* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * may for example be used to flush the relation, when the - * TABLE_INSERT_SKIP_WAL option was used. + * Sync relation at commit-time if needed. * - * Typically callers of tuple_insert and multi_insert will just pass all - * the flags that apply to them, and each AM has to decide which of them - * make sense for it, and then only take actions in finish_bulk_insert for - * those flags, and ignore others. + * A table AM may skip WAL-logging for relations created in the current + * transaction. This routine is called commit-time and the table AM + * must flush buffer and sync the underlying storage. * * Optional callback. */ - void (*finish_bulk_insert) (Relation rel, int options); + void (*at_commit_sync) (Relation rel); /* ------------------------------------------------------------------------ @@ -1104,8 +1100,7 @@ table_compute_xid_horizon_for_tuples(Relation rel, * * * The BulkInsertState object (if any; bistate can be NULL for default - * behavior) is also just passed through to RelationGetBufferForTuple. If - * `bistate` is provided, table_finish_bulk_insert() needs to be called. + * behavior) is also just passed through to RelationGetBufferForTuple. * * On return the slot's tts_tid and tts_tableOid are updated to reflect the * insertion. But note that any toasting of fields within the slot is NOT @@ -1300,20 +1295,23 @@ table_lock_tuple(Relation rel, ItemPointer tid, Snapshot snapshot, } /* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * e.g. may e.g. used to flush the relation when inserting with - * TABLE_INSERT_SKIP_WAL specified. + * Sync relation at commit-time if needed. + * + * A table AM that defines this interface can allow derived objects created + * in the current transaction to skip WAL-logging. This routine is called + * commit-time and the table AM must flush buffer and sync the underlying + * storage. + * + * Optional callback. */ static inline void -table_finish_bulk_insert(Relation rel, int options) +table_at_commit_sync(Relation rel) { /* optional callback */ - if (rel->rd_tableam && rel->rd_tableam->finish_bulk_insert) - rel->rd_tableam->finish_bulk_insert(rel, options); + if (rel->rd_tableam && rel->rd_tableam->at_commit_sync) + rel->rd_tableam->at_commit_sync(rel); } - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d7f33abce3..c09fd84a1c 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -64,6 +64,9 @@ typedef struct RelationData * rd_replidindex) */ bool rd_statvalid; /* is rd_statlist valid? */ + /* Some relations cane comit WAL-logging on certain condition. */ + bool rd_can_skipwal; /* can skip WAL-logging? */ + /* * rd_createSubid is the ID of the highest subtransaction the rel has * survived into; or zero if the rel was not created in the current top @@ -512,9 +515,25 @@ typedef struct ViewOptions /* * RelationNeedsWAL * True if relation needs WAL. + * + * If underlying table AM has at_commit_sync interface, returns false if + * wal_level = minimal and this relation is created in the current transaction */ #define RelationNeedsWAL(relation) \ - ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT && \ + (!relation->rd_can_skipwal || \ + !(RELATION_IS_LOCAL(relation) && !XLogIsNeeded()))) + +/* + * RelationNeedAtCommitSync + * True if relation needs WAL needs on-commit sync + */ +#define RelationNeedsAtCommitSync(relation) \ + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT && \ + relation->rd_can_skipwal && \ + (RELATION_IS_LOCAL(relation) || \ + relation->rd_newRelfilenodeSubid != InvalidBlockNumber) \ + && !XLogIsNeeded())) /* * RelationUsesLocalBuffers diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index 364495a5f0..07c4cfa565 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -120,6 +120,7 @@ extern void RelationCacheInvalidate(void); extern void RelationCloseSmgrByOid(Oid relationId); +extern void PreCommit_RelationSync(void); extern void AtEOXact_RelationCache(bool isCommit); extern void AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid, SubTransactionId parentSubid); -- 2.16.3 ----Next_Part(Tue_May_21_21_29_48_2019_797)---- ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 2/2] Fix WAL skipping feature @ 2019-05-20 06:38 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2019-05-20 06:38 UTC (permalink / raw) This patch shows a PoC of how change WAL-skipping feature to avoid table corruption caused by mixing wal-logged and wal-skipped operations. --- src/backend/access/heap/heapam.c | 4 ++-- src/backend/access/heap/heapam_handler.c | 7 +------ src/backend/access/heap/rewriteheap.c | 3 --- src/backend/access/transam/xact.c | 6 ++++++ src/backend/commands/copy.c | 4 ---- src/backend/commands/createas.c | 3 +-- src/backend/commands/tablecmds.c | 2 -- src/backend/utils/cache/relcache.c | 22 ++++++++++++++++++++++ src/include/access/heapam.h | 1 - src/include/utils/rel.h | 3 ++- src/include/utils/relcache.h | 1 + 11 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 19d2c529d8..dda76c8736 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -1950,7 +1950,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (RelationNeedsWAL(relation)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2133,7 +2133,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); + needwal = RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 8d8161fd97..f4af981a35 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -560,12 +560,7 @@ tuple_lock_retry: static void heapam_finish_bulk_insert(Relation relation, int options) { - /* - * If we skipped writing WAL, then we need to sync the heap (but not - * indexes since those use WAL anyway / don't go through tableam) - */ - if (options & HEAP_INSERT_SKIP_WAL) - heap_sync(relation); + /* heapam doesn't need do this */ } diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index bce4274362..1ac77f7c14 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -654,9 +654,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 20feeec327..fb35992a13 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -2133,6 +2133,9 @@ CommitTransaction(void) /* Commit updates to the relation map --- do this as late as possible */ AtEOXact_RelationMap(true, is_parallel_worker); + /* Perform pending flush */ + AtEOXact_DoPendingFlush(); + /* * set the current transaction state information appropriately during * commit processing @@ -2349,6 +2352,9 @@ PrepareTransaction(void) */ PreCommit_CheckForSerializationFailure(); + /* Perform pending flush */ + AtEOXact_DoPendingFlush(); + /* NOTIFY will be handled below */ /* diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 6ffc3a62f6..9bae04b8a7 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2761,11 +2761,7 @@ CopyFrom(CopyState cstate) if (RELKIND_HAS_STORAGE(cstate->rel->rd_rel->relkind) && (cstate->rel->rd_createSubid != InvalidSubTransactionId || cstate->rel->rd_newRelfilenodeSubid != InvalidSubTransactionId)) - { ti_options |= TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; - } /* * Optimize if new relfilenode was created in this subxact or one of its diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 43c2fa9124..83e5f9220f 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -558,8 +558,7 @@ 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->ti_options = TABLE_INSERT_SKIP_FSM | - (XLogIsNeeded() ? 0 : TABLE_INSERT_SKIP_WAL); + myState->ti_options = TABLE_INSERT_SKIP_FSM; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index bfcf9472d7..b686497443 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4741,8 +4741,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); ti_options = TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; } else { diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index d0f6f715e6..10fd405171 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -2913,6 +2913,28 @@ RememberToFreeTupleDescAtEOX(TupleDesc td) EOXactTupleDescArray[NextEOXactTupleDescNum++] = td; } +void +AtEOXact_DoPendingFlush() +{ + HASH_SEQ_STATUS status; + RelIdCacheEnt *idhentry; + + if (!RelationIdCache) + return; + + hash_seq_init(&status, RelationIdCache); + while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL) + { + Relation rel = idhentry->reldesc; + if (RELATION_IS_LOCAL(rel) && !XLogIsNeeded() && rel->rd_smgr) + { + FlushRelationBuffers(rel); + smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM); + } + } +} + + /* * AtEOXact_RelationCache * diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 62aaa08eff..0fb7d86bf2 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -29,7 +29,6 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL #define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM #define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN #define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d7f33abce3..41ab634ff5 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -514,7 +514,8 @@ typedef struct ViewOptions * True if relation needs WAL. */ #define RelationNeedsWAL(relation) \ - ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT && \ + !(RELATION_IS_LOCAL(relation) && !XLogIsNeeded())) /* * RelationUsesLocalBuffers diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index 364495a5f0..cd9b1a6f68 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -123,6 +123,7 @@ extern void RelationCloseSmgrByOid(Oid relationId); extern void AtEOXact_RelationCache(bool isCommit); extern void AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid, SubTransactionId parentSubid); +extern void AtEOXact_DoPendingFlush(void); /* * Routines to help manage rebuilding of relcache init files -- 2.16.3 ----Next_Part(Mon_May_20_15_54_31_2019_643)---- ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 2/3] Fix WAL skipping feature @ 2019-05-27 07:06 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2019-05-27 07:06 UTC (permalink / raw) WAL-skipping operations mixed with WAL-logged operations can lead to database corruption after a crash. This patch changes the WAL-skipping feature so that no data modifcation is WAL-logged at all then sync such relations at commit. --- src/backend/access/heap/heapam.c | 4 +- src/backend/access/heap/heapam_handler.c | 22 +---------- src/backend/access/heap/rewriteheap.c | 13 ++----- src/backend/catalog/storage.c | 64 +++++++++++++++++++++++++------- src/backend/commands/cluster.c | 24 ++++++++++++ src/backend/commands/copy.c | 38 ++++--------------- src/backend/commands/createas.c | 5 +-- src/backend/commands/matview.c | 4 -- src/backend/commands/tablecmds.c | 10 ++--- src/backend/storage/buffer/bufmgr.c | 33 +++++++++++----- src/backend/utils/cache/relcache.c | 16 ++++++-- src/include/access/heapam.h | 1 - src/include/access/rewriteheap.h | 2 +- src/include/access/tableam.h | 41 ++------------------ src/include/storage/bufmgr.h | 1 + src/include/utils/rel.h | 17 ++++++++- 16 files changed, 148 insertions(+), 147 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index d768b9b061..eca98fb063 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -1941,7 +1941,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (RelationNeedsWAL(relation)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2124,7 +2124,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); + needwal = RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 09bc6fe98a..b9554f6064 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -556,18 +556,6 @@ tuple_lock_retry: return result; } -static void -heapam_finish_bulk_insert(Relation relation, int options) -{ - /* - * If we skipped writing WAL, then we need to sync the heap (but not - * indexes since those use WAL anyway / don't go through tableam) - */ - if (options & HEAP_INSERT_SKIP_WAL) - heap_sync(relation); -} - - /* ------------------------------------------------------------------------ * DDL related callbacks for heap AM. * ------------------------------------------------------------------------ @@ -699,7 +687,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, IndexScanDesc indexScan; TableScanDesc tableScan; HeapScanDesc heapScan; - bool use_wal; bool is_system_catalog; Tuplesortstate *tuplesort; TupleDesc oldTupDesc = RelationGetDescr(OldHeap); @@ -713,12 +700,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Remember if it's a system catalog */ is_system_catalog = IsSystemRelation(OldHeap); - /* - * We need to log the copied data in WAL iff WAL archiving/streaming is - * enabled AND it's a WAL-logged rel. - */ - use_wal = XLogIsNeeded() && RelationNeedsWAL(NewHeap); - /* use_wal off requires smgr_targblock be initially invalid */ Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber); @@ -729,7 +710,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Initialize the rewrite operation */ rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff, - *multi_cutoff, use_wal); + *multi_cutoff); /* Set up sorting if wanted */ @@ -2517,7 +2498,6 @@ static const TableAmRoutine heapam_methods = { .tuple_delete = heapam_tuple_delete, .tuple_update = heapam_tuple_update, .tuple_lock = heapam_tuple_lock, - .finish_bulk_insert = heapam_finish_bulk_insert, .tuple_fetch_row_version = heapam_fetch_row_version, .tuple_get_latest_tid = heap_get_latest_tid, diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 72a448ad31..992d4b9880 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -144,7 +144,6 @@ typedef struct RewriteStateData Page rs_buffer; /* page currently being built */ BlockNumber rs_blockno; /* block where page will go */ bool rs_buffer_valid; /* T if any tuples in buffer */ - bool rs_use_wal; /* must we WAL-log inserts? */ bool rs_logical_rewrite; /* do we need to do logical rewriting */ TransactionId rs_oldest_xmin; /* oldest xmin used by caller to determine * tuple visibility */ @@ -238,15 +237,13 @@ static void logical_end_heap_rewrite(RewriteState state); * oldest_xmin xid used by the caller to determine which tuples are dead * freeze_xid xid before which tuples will be frozen * min_multi multixact before which multis will be removed - * use_wal should the inserts to the new heap be WAL-logged? * * Returns an opaque RewriteState, allocated in current memory context, * to be used in subsequent calls to the other functions. */ RewriteState begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xmin, - TransactionId freeze_xid, MultiXactId cutoff_multi, - bool use_wal) + TransactionId freeze_xid, MultiXactId cutoff_multi) { RewriteState state; MemoryContext rw_cxt; @@ -271,7 +268,6 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm /* new_heap needn't be empty, just locked */ state->rs_blockno = RelationGetNumberOfBlocks(new_heap); state->rs_buffer_valid = false; - state->rs_use_wal = use_wal; state->rs_oldest_xmin = oldest_xmin; state->rs_freeze_xid = freeze_xid; state->rs_cutoff_multi = cutoff_multi; @@ -330,7 +326,7 @@ end_heap_rewrite(RewriteState state) /* Write the last page, if any */ if (state->rs_buffer_valid) { - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, @@ -654,9 +650,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is @@ -695,7 +688,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup) /* Doesn't fit, so write out the existing page */ /* XLOG stuff */ - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index 3cc886f7fe..e4bcdc390f 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -57,7 +57,8 @@ typedef struct PendingRelDelete { RelFileNode relnode; /* relation that may need to be deleted */ BackendId backend; /* InvalidBackendId if not a temp rel */ - bool atCommit; /* T=delete at commit; F=delete at abort */ + bool atCommit; /* T=work at commit; F=work at abort */ + bool dosync; /* T=work is sync; F=work is delete */ int nestLevel; /* xact nesting level of request */ struct PendingRelDelete *next; /* linked-list link */ } PendingRelDelete; @@ -114,10 +115,29 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) pending->relnode = rnode; pending->backend = backend; pending->atCommit = false; /* delete if abort */ + pending->dosync = false; pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; pendingDeletes = pending; + /* + * We are going to skip WAL-logging for storage of persistent relations + * created in the current transaction when wal_level = minimal. The + * relation needs to be synced at commit. + */ + if (relpersistence == RELPERSISTENCE_PERMANENT && !XLogIsNeeded()) + { + pending = (PendingRelDelete *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); + pending->relnode = rnode; + pending->backend = backend; + pending->atCommit = true; + pending->dosync = true; + pending->nestLevel = GetCurrentTransactionNestLevel(); + pending->next = pendingDeletes; + pendingDeletes = pending; + } + return srel; } @@ -155,6 +175,7 @@ RelationDropStorage(Relation rel) pending->relnode = rel->rd_node; pending->backend = rel->rd_backend; pending->atCommit = true; /* delete if commit */ + pending->dosync = false; pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; pendingDeletes = pending; @@ -428,21 +449,34 @@ smgrDoPendingDeletes(bool isCommit) { SMgrRelation srel; - srel = smgropen(pending->relnode, pending->backend); - - /* allocate the initial array, or extend it, if needed */ - if (maxrels == 0) + if (pending->dosync) { - maxrels = 8; - srels = palloc(sizeof(SMgrRelation) * maxrels); + /* Perform pending sync of WAL-skipped relation */ + FlushRelationBuffersWithoutRelcache(pending->relnode, + false); + srel = smgropen(pending->relnode, pending->backend); + smgrimmedsync(srel, MAIN_FORKNUM); + smgrclose(srel); } - else if (maxrels <= nrels) + else { - maxrels *= 2; - srels = repalloc(srels, sizeof(SMgrRelation) * maxrels); - } + /* Collect pending deletions */ + srel = smgropen(pending->relnode, pending->backend); - srels[nrels++] = srel; + /* allocate the initial array, or extend it, if needed */ + if (maxrels == 0) + { + maxrels = 8; + srels = palloc(sizeof(SMgrRelation) * maxrels); + } + else if (maxrels <= nrels) + { + maxrels *= 2; + srels = repalloc(srels, sizeof(SMgrRelation) * maxrels); + } + + srels[nrels++] = srel; + } } /* must explicitly free the list entry */ pfree(pending); @@ -489,8 +523,9 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) nrels = 0; for (pending = pendingDeletes; pending != NULL; pending = pending->next) { + /* Pending syncs are excluded */ if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit - && pending->backend == InvalidBackendId) + && pending->backend == InvalidBackendId && !pending->dosync) nrels++; } if (nrels == 0) @@ -502,8 +537,9 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) *ptr = rptr; for (pending = pendingDeletes; pending != NULL; pending = pending->next) { + /* Pending syncs are excluded */ if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit - && pending->backend == InvalidBackendId) + && pending->backend == InvalidBackendId && !pending->dosync) { *rptr = pending->relnode; rptr++; diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index ebaec4f8dd..6fc9d7d64e 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1034,12 +1034,36 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, if (OidIsValid(relfilenode1) && OidIsValid(relfilenode2)) { + Relation rel1; + Relation rel2; + /* * Normal non-mapped relations: swap relfilenodes, reltablespaces, * relpersistence */ Assert(!target_is_pg_class); + /* Update creation subid hints of relcache */ + rel1 = relation_open(r1, ExclusiveLock); + rel2 = relation_open(r2, ExclusiveLock); + + /* + * New relation's relfilenode is created in the current transaction + * and used as old ralation's new relfilenode. So its + * newRelfilenodeSubid as new relation's createSubid. We don't fix + * rel2 since it would be deleted soon. + */ + Assert(rel2->rd_createSubid != InvalidSubTransactionId); + rel1->rd_newRelfilenodeSubid = rel2->rd_createSubid; + + /* record the first relfilenode change in the current transaction */ + if (rel1->rd_firstRelfilenodeSubid == InvalidSubTransactionId) + rel1->rd_firstRelfilenodeSubid = GetCurrentSubTransactionId(); + + relation_close(rel1, ExclusiveLock); + relation_close(rel2, ExclusiveLock); + + /* swap relfilenodes, reltablespaces, relpersistence */ swaptemp = relform1->relfilenode; relform1->relfilenode = relform2->relfilenode; relform2->relfilenode = swaptemp; diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index f1161f0fee..f4beff0001 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2722,28 +2722,9 @@ CopyFrom(CopyState cstate) * If it does commit, we'll have done the table_finish_bulk_insert() 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 - * can be cleared before the end of the transaction. The exact case is - * when a relation sets a new relfilenode twice in same transaction, yet - * the second one fails in an aborted subtransaction, e.g. - * - * BEGIN; - * TRUNCATE t; - * SAVEPOINT save; - * TRUNCATE t; - * ROLLBACK TO save; - * COPY ... - * - * Also, if the target file is new-in-transaction, we assume that checking - * FSM for free space is a waste of time, even if we must use WAL because - * of archiving. This could possibly be wrong, but it's unlikely. - * - * The comments for table_tuple_insert and RelationGetBufferForTuple - * specify that skipping WAL logging is only safe if we ensure that our - * tuples do not go into pages containing tuples from any other - * transactions --- but this must be the case if we have a new table or - * new relfilenode, so we need no additional work to enforce that. + * If the target file is new-in-transaction, we assume that checking FSM + * for free space is a waste of time, even if we must use WAL because of + * archiving. This could possibly be wrong, but it's unlikely. * * We currently don't support this optimization if the COPY target is a * partitioned table as we currently only lazily initialize partition @@ -2759,15 +2740,14 @@ CopyFrom(CopyState cstate) * are not supported as per the description above. *---------- */ - /* createSubid is creation check, newRelfilenodeSubid is truncation check */ + /* + * createSubid is creation check, firstRelfilenodeSubid is truncation and + * cluster check. Partitioned table doesn't have storage. + */ if (RELKIND_HAS_STORAGE(cstate->rel->rd_rel->relkind) && (cstate->rel->rd_createSubid != InvalidSubTransactionId || - cstate->rel->rd_newRelfilenodeSubid != InvalidSubTransactionId)) - { + cstate->rel->rd_firstRelfilenodeSubid != InvalidSubTransactionId)) ti_options |= TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; - } /* * Optimize if new relfilenode was created in this subxact or one of its @@ -3366,8 +3346,6 @@ CopyFrom(CopyState cstate) FreeExecutorState(estate); - table_finish_bulk_insert(cstate->rel, ti_options); - return processed; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 4c1d909d38..39ebd73691 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -558,8 +558,7 @@ 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->ti_options = TABLE_INSERT_SKIP_FSM | - (XLogIsNeeded() ? 0 : TABLE_INSERT_SKIP_WAL); + myState->ti_options = TABLE_INSERT_SKIP_FSM; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -604,8 +603,6 @@ intorel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->rel, myState->ti_options); - /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); myState->rel = NULL; diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 537d0e8cef..1c854dcebf 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -463,8 +463,6 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) * replication is in use. We can skip the FSM in any case. */ myState->ti_options = TABLE_INSERT_SKIP_FSM | TABLE_INSERT_FROZEN; - if (!XLogIsNeeded()) - myState->ti_options |= TABLE_INSERT_SKIP_WAL; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,8 +507,6 @@ transientrel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->transientrel, myState->ti_options); - /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); myState->transientrel = NULL; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 0f1a9f0e54..ac7336ef58 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4761,9 +4761,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) /* * Prepare a BulkInsertState and options for table_tuple_insert. Because - * we're building a new heap, we can skip WAL-logging and fsync it to disk - * at the end instead (unless WAL-logging is required for archiving or - * streaming replication). The FSM is empty too, so don't bother using it. + * we're building a new heap, the underlying table AM can skip WAL-logging + * and fsync the relation to disk at the end of the current transaction + * instead. The FSM is empty too, so don't bother using it. */ if (newrel) { @@ -4771,8 +4771,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); ti_options = TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; } else { @@ -5057,8 +5055,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { FreeBulkInsertState(bistate); - table_finish_bulk_insert(newrel, ti_options); - table_close(newrel, NoLock); } } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 7332e6b590..280fdf8080 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -171,6 +171,7 @@ static HTAB *PrivateRefCountHash = NULL; static int32 PrivateRefCountOverflowed = 0; static uint32 PrivateRefCountClock = 0; static PrivateRefCountEntry *ReservedRefCountEntry = NULL; +static void FlushRelationBuffers_common(SMgrRelation smgr, bool islocal); static void ReservePrivateRefCountEntry(void); static PrivateRefCountEntry *NewPrivateRefCountEntry(Buffer buffer); @@ -3190,20 +3191,32 @@ 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)); +} + +void +FlushRelationBuffersWithoutRelcache(RelFileNode rnode, bool islocal) +{ + FlushRelationBuffers_common(smgropen(rnode, InvalidBackendId), islocal); +} + +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)) { @@ -3220,7 +3233,7 @@ FlushRelationBuffers(Relation rel) PageSetChecksumInplace(localpage, bufHdr->tag.blockNum); - smgrwrite(rel->rd_smgr, + smgrwrite(smgr, bufHdr->tag.forkNum, bufHdr->tag.blockNum, localpage, @@ -3250,18 +3263,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 2b992d7832..812bfadb40 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -2661,7 +2661,7 @@ static void RelationFlushRelation(Relation relation) { if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) { /* * New relcache entries are always rebuilt, not flushed; else we'd @@ -2801,7 +2801,7 @@ RelationCacheInvalidate(void) * pending invalidations. */ if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) continue; relcacheInvalsReceived++; @@ -3058,6 +3058,7 @@ AtEOXact_cleanup(Relation relation, bool isCommit) * Likewise, reset the hint about the relfilenode being new. */ relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; } /* @@ -3149,7 +3150,7 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, } /* - * Likewise, update or drop any new-relfilenode-in-subtransaction hint. + * Likewise, update or drop any new-relfilenode-in-subtransaction hints. */ if (relation->rd_newRelfilenodeSubid == mySubid) { @@ -3158,6 +3159,15 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, else relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; } + + + if (relation->rd_firstRelfilenodeSubid == mySubid) + { + if (isCommit) + relation->rd_firstRelfilenodeSubid = parentSubid; + else + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; + } } diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 858bcb6bc9..80c2e1bafc 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -29,7 +29,6 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL #define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM #define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN #define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 8056253916..7f9736e294 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -23,7 +23,7 @@ typedef struct RewriteStateData *RewriteState; extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap, TransactionId OldestXmin, TransactionId FreezeXid, - MultiXactId MultiXactCutoff, bool use_wal); + MultiXactId MultiXactCutoff); extern void end_heap_rewrite(RewriteState state); extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple, HeapTuple newTuple); diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index c2b0481e7e..ac0e981acb 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -407,22 +407,6 @@ typedef struct TableAmRoutine uint8 flags, TM_FailureData *tmfd); - /* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * may for example be used to flush the relation, when the - * TABLE_INSERT_SKIP_WAL option was used. - * - * Typically callers of tuple_insert and multi_insert will just pass all - * the flags that apply to them, and each AM has to decide which of them - * make sense for it, and then only take actions in finish_bulk_insert for - * those flags, and ignore others. - * - * Optional callback. - */ - void (*finish_bulk_insert) (Relation rel, int options); - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ @@ -1088,10 +1072,6 @@ table_compute_xid_horizon_for_tuples(Relation rel, * behaviour of the AM. Several options might be ignored by AMs not supporting * them. * - * If the TABLE_INSERT_SKIP_WAL option is specified, the new tuple doesn't - * need to be logged to WAL, even for a non-temp relation. It is the AMs - * choice whether this optimization is supported. - * * If the TABLE_INSERT_SKIP_FSM option is specified, AMs are free to not reuse * free space in the relation. This can save some cycles when we know the * relation is new and doesn't contain useful amounts of free space. It's @@ -1111,10 +1091,8 @@ table_compute_xid_horizon_for_tuples(Relation rel, * Note that most of these options will be applied when inserting into the * heap's TOAST table, too, if the tuple requires any out-of-line data. * - * * The BulkInsertState object (if any; bistate can be NULL for default - * behavior) is also just passed through to RelationGetBufferForTuple. If - * `bistate` is provided, table_finish_bulk_insert() needs to be called. + * behavior) is also just passed through to RelationGetBufferForTuple. * * On return the slot's tts_tid and tts_tableOid are updated to reflect the * insertion. But note that any toasting of fields within the slot is NOT @@ -1249,6 +1227,8 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, * update was done. However, any TOAST changes in the new tuple's * data are not reflected into *newtup. * + * See table_insert about skipping WAL-logging feature. + * * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, * t_xmax, and, if possible, t_cmax. See comments for struct TM_FailureData * for additional info. @@ -1309,21 +1289,6 @@ table_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, flags, tmfd); } -/* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * e.g. may e.g. used to flush the relation when inserting with - * TABLE_INSERT_SKIP_WAL specified. - */ -static inline void -table_finish_bulk_insert(Relation rel, int options) -{ - /* optional callback */ - if (rel->rd_tableam && rel->rd_tableam->finish_bulk_insert) - rel->rd_tableam->finish_bulk_insert(rel, options); -} - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h index 509f4b7ef1..ace5f5a2ae 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -189,6 +189,7 @@ 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 d35b4a5061..5cbb5a7b27 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -76,10 +76,17 @@ typedef struct RelationData * transaction, with one of them occurring in a subsequently aborted * subtransaction, e.g. BEGIN; TRUNCATE t; SAVEPOINT save; TRUNCATE t; * ROLLBACK TO save; -- rd_newRelfilenode is now forgotten + * rd_firstRelfilenodeSubid is the ID of the hightest subtransaction the + * relfilenode change has took place first in the current + * transaction. This won't be forgotten as newRelfilenodeSubid is. A valid + * OID means that the currently active relfilenode is transaction-local + * and no-need for WAL-logging. */ SubTransactionId rd_createSubid; /* rel was created in current xact */ SubTransactionId rd_newRelfilenodeSubid; /* new relfilenode assigned in * current xact */ + SubTransactionId rd_firstRelfilenodeSubid; /* new relfilenode assigned + * first in current xact */ Form_pg_class rd_rel; /* RELATION tuple */ TupleDesc rd_att; /* tuple descriptor */ @@ -512,9 +519,15 @@ typedef struct ViewOptions /* * RelationNeedsWAL * True if relation needs WAL. + * + * Returns false if wal_level = minimal and this relation is created or + * truncated in the current transaction. */ -#define RelationNeedsWAL(relation) \ - ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) +#define RelationNeedsWAL(relation) \ + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT && \ + (XLogIsNeeded() || \ + (relation->rd_createSubid == InvalidSubTransactionId && \ + relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId))) /* * RelationUsesLocalBuffers -- 2.16.3 ----Next_Part(Wed_Jul_10_13_19_14_2019_952)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v16-0003-Rename-smgrDoPendingDeletes-to-smgrDoPendingOperatio.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 2/3] Fix WAL skipping feature @ 2019-05-27 07:06 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2019-05-27 07:06 UTC (permalink / raw) WAL-skipping operations mixed with WAL-logged operations can lead to database corruption after a crash. This patch changes the WAL-skipping feature so that no data modifcation is WAL-logged at all then sync such relations at commit. --- src/backend/access/heap/heapam.c | 4 +- src/backend/access/heap/heapam_handler.c | 22 +---------- src/backend/access/heap/rewriteheap.c | 13 ++----- src/backend/catalog/storage.c | 64 +++++++++++++++++++++++++------- src/backend/commands/cluster.c | 24 ++++++++++++ src/backend/commands/copy.c | 39 ++++--------------- src/backend/commands/createas.c | 5 +-- src/backend/commands/matview.c | 4 -- src/backend/commands/tablecmds.c | 10 ++--- src/backend/storage/buffer/bufmgr.c | 33 +++++++++++----- src/backend/utils/cache/relcache.c | 16 ++++++-- src/include/access/heapam.h | 1 - src/include/access/rewriteheap.h | 2 +- src/include/access/tableam.h | 41 ++------------------ src/include/storage/bufmgr.h | 1 + src/include/utils/rel.h | 17 ++++++++- 16 files changed, 148 insertions(+), 148 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index d768b9b061..eca98fb063 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -1941,7 +1941,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (RelationNeedsWAL(relation)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2124,7 +2124,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); + needwal = RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 09bc6fe98a..b9554f6064 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -556,18 +556,6 @@ tuple_lock_retry: return result; } -static void -heapam_finish_bulk_insert(Relation relation, int options) -{ - /* - * If we skipped writing WAL, then we need to sync the heap (but not - * indexes since those use WAL anyway / don't go through tableam) - */ - if (options & HEAP_INSERT_SKIP_WAL) - heap_sync(relation); -} - - /* ------------------------------------------------------------------------ * DDL related callbacks for heap AM. * ------------------------------------------------------------------------ @@ -699,7 +687,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, IndexScanDesc indexScan; TableScanDesc tableScan; HeapScanDesc heapScan; - bool use_wal; bool is_system_catalog; Tuplesortstate *tuplesort; TupleDesc oldTupDesc = RelationGetDescr(OldHeap); @@ -713,12 +700,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Remember if it's a system catalog */ is_system_catalog = IsSystemRelation(OldHeap); - /* - * We need to log the copied data in WAL iff WAL archiving/streaming is - * enabled AND it's a WAL-logged rel. - */ - use_wal = XLogIsNeeded() && RelationNeedsWAL(NewHeap); - /* use_wal off requires smgr_targblock be initially invalid */ Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber); @@ -729,7 +710,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Initialize the rewrite operation */ rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff, - *multi_cutoff, use_wal); + *multi_cutoff); /* Set up sorting if wanted */ @@ -2517,7 +2498,6 @@ static const TableAmRoutine heapam_methods = { .tuple_delete = heapam_tuple_delete, .tuple_update = heapam_tuple_update, .tuple_lock = heapam_tuple_lock, - .finish_bulk_insert = heapam_finish_bulk_insert, .tuple_fetch_row_version = heapam_fetch_row_version, .tuple_get_latest_tid = heap_get_latest_tid, diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 72a448ad31..992d4b9880 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -144,7 +144,6 @@ typedef struct RewriteStateData Page rs_buffer; /* page currently being built */ BlockNumber rs_blockno; /* block where page will go */ bool rs_buffer_valid; /* T if any tuples in buffer */ - bool rs_use_wal; /* must we WAL-log inserts? */ bool rs_logical_rewrite; /* do we need to do logical rewriting */ TransactionId rs_oldest_xmin; /* oldest xmin used by caller to determine * tuple visibility */ @@ -238,15 +237,13 @@ static void logical_end_heap_rewrite(RewriteState state); * oldest_xmin xid used by the caller to determine which tuples are dead * freeze_xid xid before which tuples will be frozen * min_multi multixact before which multis will be removed - * use_wal should the inserts to the new heap be WAL-logged? * * Returns an opaque RewriteState, allocated in current memory context, * to be used in subsequent calls to the other functions. */ RewriteState begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xmin, - TransactionId freeze_xid, MultiXactId cutoff_multi, - bool use_wal) + TransactionId freeze_xid, MultiXactId cutoff_multi) { RewriteState state; MemoryContext rw_cxt; @@ -271,7 +268,6 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm /* new_heap needn't be empty, just locked */ state->rs_blockno = RelationGetNumberOfBlocks(new_heap); state->rs_buffer_valid = false; - state->rs_use_wal = use_wal; state->rs_oldest_xmin = oldest_xmin; state->rs_freeze_xid = freeze_xid; state->rs_cutoff_multi = cutoff_multi; @@ -330,7 +326,7 @@ end_heap_rewrite(RewriteState state) /* Write the last page, if any */ if (state->rs_buffer_valid) { - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, @@ -654,9 +650,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is @@ -695,7 +688,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup) /* Doesn't fit, so write out the existing page */ /* XLOG stuff */ - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index 3cc886f7fe..e4bcdc390f 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -57,7 +57,8 @@ typedef struct PendingRelDelete { RelFileNode relnode; /* relation that may need to be deleted */ BackendId backend; /* InvalidBackendId if not a temp rel */ - bool atCommit; /* T=delete at commit; F=delete at abort */ + bool atCommit; /* T=work at commit; F=work at abort */ + bool dosync; /* T=work is sync; F=work is delete */ int nestLevel; /* xact nesting level of request */ struct PendingRelDelete *next; /* linked-list link */ } PendingRelDelete; @@ -114,10 +115,29 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) pending->relnode = rnode; pending->backend = backend; pending->atCommit = false; /* delete if abort */ + pending->dosync = false; pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; pendingDeletes = pending; + /* + * We are going to skip WAL-logging for storage of persistent relations + * created in the current transaction when wal_level = minimal. The + * relation needs to be synced at commit. + */ + if (relpersistence == RELPERSISTENCE_PERMANENT && !XLogIsNeeded()) + { + pending = (PendingRelDelete *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); + pending->relnode = rnode; + pending->backend = backend; + pending->atCommit = true; + pending->dosync = true; + pending->nestLevel = GetCurrentTransactionNestLevel(); + pending->next = pendingDeletes; + pendingDeletes = pending; + } + return srel; } @@ -155,6 +175,7 @@ RelationDropStorage(Relation rel) pending->relnode = rel->rd_node; pending->backend = rel->rd_backend; pending->atCommit = true; /* delete if commit */ + pending->dosync = false; pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; pendingDeletes = pending; @@ -428,21 +449,34 @@ smgrDoPendingDeletes(bool isCommit) { SMgrRelation srel; - srel = smgropen(pending->relnode, pending->backend); - - /* allocate the initial array, or extend it, if needed */ - if (maxrels == 0) + if (pending->dosync) { - maxrels = 8; - srels = palloc(sizeof(SMgrRelation) * maxrels); + /* Perform pending sync of WAL-skipped relation */ + FlushRelationBuffersWithoutRelcache(pending->relnode, + false); + srel = smgropen(pending->relnode, pending->backend); + smgrimmedsync(srel, MAIN_FORKNUM); + smgrclose(srel); } - else if (maxrels <= nrels) + else { - maxrels *= 2; - srels = repalloc(srels, sizeof(SMgrRelation) * maxrels); - } + /* Collect pending deletions */ + srel = smgropen(pending->relnode, pending->backend); - srels[nrels++] = srel; + /* allocate the initial array, or extend it, if needed */ + if (maxrels == 0) + { + maxrels = 8; + srels = palloc(sizeof(SMgrRelation) * maxrels); + } + else if (maxrels <= nrels) + { + maxrels *= 2; + srels = repalloc(srels, sizeof(SMgrRelation) * maxrels); + } + + srels[nrels++] = srel; + } } /* must explicitly free the list entry */ pfree(pending); @@ -489,8 +523,9 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) nrels = 0; for (pending = pendingDeletes; pending != NULL; pending = pending->next) { + /* Pending syncs are excluded */ if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit - && pending->backend == InvalidBackendId) + && pending->backend == InvalidBackendId && !pending->dosync) nrels++; } if (nrels == 0) @@ -502,8 +537,9 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) *ptr = rptr; for (pending = pendingDeletes; pending != NULL; pending = pending->next) { + /* Pending syncs are excluded */ if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit - && pending->backend == InvalidBackendId) + && pending->backend == InvalidBackendId && !pending->dosync) { *rptr = pending->relnode; rptr++; diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index ebaec4f8dd..6fc9d7d64e 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1034,12 +1034,36 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, if (OidIsValid(relfilenode1) && OidIsValid(relfilenode2)) { + Relation rel1; + Relation rel2; + /* * Normal non-mapped relations: swap relfilenodes, reltablespaces, * relpersistence */ Assert(!target_is_pg_class); + /* Update creation subid hints of relcache */ + rel1 = relation_open(r1, ExclusiveLock); + rel2 = relation_open(r2, ExclusiveLock); + + /* + * New relation's relfilenode is created in the current transaction + * and used as old ralation's new relfilenode. So its + * newRelfilenodeSubid as new relation's createSubid. We don't fix + * rel2 since it would be deleted soon. + */ + Assert(rel2->rd_createSubid != InvalidSubTransactionId); + rel1->rd_newRelfilenodeSubid = rel2->rd_createSubid; + + /* record the first relfilenode change in the current transaction */ + if (rel1->rd_firstRelfilenodeSubid == InvalidSubTransactionId) + rel1->rd_firstRelfilenodeSubid = GetCurrentSubTransactionId(); + + relation_close(rel1, ExclusiveLock); + relation_close(rel2, ExclusiveLock); + + /* swap relfilenodes, reltablespaces, relpersistence */ swaptemp = relform1->relfilenode; relform1->relfilenode = relform2->relfilenode; relform2->relfilenode = swaptemp; diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 4f04d122c3..f02efd59fc 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2535,9 +2535,6 @@ CopyMultiInsertBufferCleanup(CopyMultiInsertInfo *miinfo, for (i = 0; i < MAX_BUFFERED_TUPLES && buffer->slots[i] != NULL; i++) ExecDropSingleTupleTableSlot(buffer->slots[i]); - table_finish_bulk_insert(buffer->resultRelInfo->ri_RelationDesc, - miinfo->ti_options); - pfree(buffer); } @@ -2726,28 +2723,9 @@ CopyFrom(CopyState cstate) * If it does commit, we'll have done the table_finish_bulk_insert() 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 - * can be cleared before the end of the transaction. The exact case is - * when a relation sets a new relfilenode twice in same transaction, yet - * the second one fails in an aborted subtransaction, e.g. - * - * BEGIN; - * TRUNCATE t; - * SAVEPOINT save; - * TRUNCATE t; - * ROLLBACK TO save; - * COPY ... - * - * Also, if the target file is new-in-transaction, we assume that checking - * FSM for free space is a waste of time, even if we must use WAL because - * of archiving. This could possibly be wrong, but it's unlikely. - * - * The comments for table_tuple_insert and RelationGetBufferForTuple - * specify that skipping WAL logging is only safe if we ensure that our - * tuples do not go into pages containing tuples from any other - * transactions --- but this must be the case if we have a new table or - * new relfilenode, so we need no additional work to enforce that. + * If the target file is new-in-transaction, we assume that checking FSM + * for free space is a waste of time, even if we must use WAL because of + * archiving. This could possibly be wrong, but it's unlikely. * * We currently don't support this optimization if the COPY target is a * partitioned table as we currently only lazily initialize partition @@ -2763,15 +2741,14 @@ CopyFrom(CopyState cstate) * are not supported as per the description above. *---------- */ - /* createSubid is creation check, newRelfilenodeSubid is truncation check */ + /* + * createSubid is creation check, firstRelfilenodeSubid is truncation and + * cluster check. Partitioned table doesn't have storage. + */ if (RELKIND_HAS_STORAGE(cstate->rel->rd_rel->relkind) && (cstate->rel->rd_createSubid != InvalidSubTransactionId || - cstate->rel->rd_newRelfilenodeSubid != InvalidSubTransactionId)) - { + cstate->rel->rd_firstRelfilenodeSubid != InvalidSubTransactionId)) ti_options |= TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; - } /* * Optimize if new relfilenode was created in this subxact or one of its diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 4c1d909d38..39ebd73691 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -558,8 +558,7 @@ 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->ti_options = TABLE_INSERT_SKIP_FSM | - (XLogIsNeeded() ? 0 : TABLE_INSERT_SKIP_WAL); + myState->ti_options = TABLE_INSERT_SKIP_FSM; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -604,8 +603,6 @@ intorel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->rel, myState->ti_options); - /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); myState->rel = NULL; diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 537d0e8cef..1c854dcebf 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -463,8 +463,6 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) * replication is in use. We can skip the FSM in any case. */ myState->ti_options = TABLE_INSERT_SKIP_FSM | TABLE_INSERT_FROZEN; - if (!XLogIsNeeded()) - myState->ti_options |= TABLE_INSERT_SKIP_WAL; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,8 +507,6 @@ transientrel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->transientrel, myState->ti_options); - /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); myState->transientrel = NULL; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 0f1a9f0e54..ac7336ef58 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4761,9 +4761,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) /* * Prepare a BulkInsertState and options for table_tuple_insert. Because - * we're building a new heap, we can skip WAL-logging and fsync it to disk - * at the end instead (unless WAL-logging is required for archiving or - * streaming replication). The FSM is empty too, so don't bother using it. + * we're building a new heap, the underlying table AM can skip WAL-logging + * and fsync the relation to disk at the end of the current transaction + * instead. The FSM is empty too, so don't bother using it. */ if (newrel) { @@ -4771,8 +4771,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); ti_options = TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; } else { @@ -5057,8 +5055,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { FreeBulkInsertState(bistate); - table_finish_bulk_insert(newrel, ti_options); - table_close(newrel, NoLock); } } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 7332e6b590..280fdf8080 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -171,6 +171,7 @@ static HTAB *PrivateRefCountHash = NULL; static int32 PrivateRefCountOverflowed = 0; static uint32 PrivateRefCountClock = 0; static PrivateRefCountEntry *ReservedRefCountEntry = NULL; +static void FlushRelationBuffers_common(SMgrRelation smgr, bool islocal); static void ReservePrivateRefCountEntry(void); static PrivateRefCountEntry *NewPrivateRefCountEntry(Buffer buffer); @@ -3190,20 +3191,32 @@ 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)); +} + +void +FlushRelationBuffersWithoutRelcache(RelFileNode rnode, bool islocal) +{ + FlushRelationBuffers_common(smgropen(rnode, InvalidBackendId), islocal); +} + +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)) { @@ -3220,7 +3233,7 @@ FlushRelationBuffers(Relation rel) PageSetChecksumInplace(localpage, bufHdr->tag.blockNum); - smgrwrite(rel->rd_smgr, + smgrwrite(smgr, bufHdr->tag.forkNum, bufHdr->tag.blockNum, localpage, @@ -3250,18 +3263,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 2b992d7832..812bfadb40 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -2661,7 +2661,7 @@ static void RelationFlushRelation(Relation relation) { if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) { /* * New relcache entries are always rebuilt, not flushed; else we'd @@ -2801,7 +2801,7 @@ RelationCacheInvalidate(void) * pending invalidations. */ if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) continue; relcacheInvalsReceived++; @@ -3058,6 +3058,7 @@ AtEOXact_cleanup(Relation relation, bool isCommit) * Likewise, reset the hint about the relfilenode being new. */ relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; } /* @@ -3149,7 +3150,7 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, } /* - * Likewise, update or drop any new-relfilenode-in-subtransaction hint. + * Likewise, update or drop any new-relfilenode-in-subtransaction hints. */ if (relation->rd_newRelfilenodeSubid == mySubid) { @@ -3158,6 +3159,15 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, else relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; } + + + if (relation->rd_firstRelfilenodeSubid == mySubid) + { + if (isCommit) + relation->rd_firstRelfilenodeSubid = parentSubid; + else + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; + } } diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 858bcb6bc9..80c2e1bafc 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -29,7 +29,6 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL #define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM #define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN #define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 8056253916..7f9736e294 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -23,7 +23,7 @@ typedef struct RewriteStateData *RewriteState; extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap, TransactionId OldestXmin, TransactionId FreezeXid, - MultiXactId MultiXactCutoff, bool use_wal); + MultiXactId MultiXactCutoff); extern void end_heap_rewrite(RewriteState state); extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple, HeapTuple newTuple); diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index c2b0481e7e..ac0e981acb 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -407,22 +407,6 @@ typedef struct TableAmRoutine uint8 flags, TM_FailureData *tmfd); - /* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * may for example be used to flush the relation, when the - * TABLE_INSERT_SKIP_WAL option was used. - * - * Typically callers of tuple_insert and multi_insert will just pass all - * the flags that apply to them, and each AM has to decide which of them - * make sense for it, and then only take actions in finish_bulk_insert for - * those flags, and ignore others. - * - * Optional callback. - */ - void (*finish_bulk_insert) (Relation rel, int options); - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ @@ -1088,10 +1072,6 @@ table_compute_xid_horizon_for_tuples(Relation rel, * behaviour of the AM. Several options might be ignored by AMs not supporting * them. * - * If the TABLE_INSERT_SKIP_WAL option is specified, the new tuple doesn't - * need to be logged to WAL, even for a non-temp relation. It is the AMs - * choice whether this optimization is supported. - * * If the TABLE_INSERT_SKIP_FSM option is specified, AMs are free to not reuse * free space in the relation. This can save some cycles when we know the * relation is new and doesn't contain useful amounts of free space. It's @@ -1111,10 +1091,8 @@ table_compute_xid_horizon_for_tuples(Relation rel, * Note that most of these options will be applied when inserting into the * heap's TOAST table, too, if the tuple requires any out-of-line data. * - * * The BulkInsertState object (if any; bistate can be NULL for default - * behavior) is also just passed through to RelationGetBufferForTuple. If - * `bistate` is provided, table_finish_bulk_insert() needs to be called. + * behavior) is also just passed through to RelationGetBufferForTuple. * * On return the slot's tts_tid and tts_tableOid are updated to reflect the * insertion. But note that any toasting of fields within the slot is NOT @@ -1249,6 +1227,8 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, * update was done. However, any TOAST changes in the new tuple's * data are not reflected into *newtup. * + * See table_insert about skipping WAL-logging feature. + * * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, * t_xmax, and, if possible, t_cmax. See comments for struct TM_FailureData * for additional info. @@ -1309,21 +1289,6 @@ table_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, flags, tmfd); } -/* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * e.g. may e.g. used to flush the relation when inserting with - * TABLE_INSERT_SKIP_WAL specified. - */ -static inline void -table_finish_bulk_insert(Relation rel, int options) -{ - /* optional callback */ - if (rel->rd_tableam && rel->rd_tableam->finish_bulk_insert) - rel->rd_tableam->finish_bulk_insert(rel, options); -} - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h index 509f4b7ef1..ace5f5a2ae 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -189,6 +189,7 @@ 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 d35b4a5061..5cbb5a7b27 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -76,10 +76,17 @@ typedef struct RelationData * transaction, with one of them occurring in a subsequently aborted * subtransaction, e.g. BEGIN; TRUNCATE t; SAVEPOINT save; TRUNCATE t; * ROLLBACK TO save; -- rd_newRelfilenode is now forgotten + * rd_firstRelfilenodeSubid is the ID of the hightest subtransaction the + * relfilenode change has took place first in the current + * transaction. This won't be forgotten as newRelfilenodeSubid is. A valid + * OID means that the currently active relfilenode is transaction-local + * and no-need for WAL-logging. */ SubTransactionId rd_createSubid; /* rel was created in current xact */ SubTransactionId rd_newRelfilenodeSubid; /* new relfilenode assigned in * current xact */ + SubTransactionId rd_firstRelfilenodeSubid; /* new relfilenode assigned + * first in current xact */ Form_pg_class rd_rel; /* RELATION tuple */ TupleDesc rd_att; /* tuple descriptor */ @@ -512,9 +519,15 @@ typedef struct ViewOptions /* * RelationNeedsWAL * True if relation needs WAL. + * + * Returns false if wal_level = minimal and this relation is created or + * truncated in the current transaction. */ -#define RelationNeedsWAL(relation) \ - ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) +#define RelationNeedsWAL(relation) \ + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT && \ + (XLogIsNeeded() || \ + (relation->rd_createSubid == InvalidSubTransactionId && \ + relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId))) /* * RelationUsesLocalBuffers -- 2.16.3 ----Next_Part(Fri_Jul_12_17_37_25_2019_721)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v17-0003-Rename-smgrDoPendingDeletes-to-smgrDoPendingOperatio.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 2/3] Fix WAL skipping feature @ 2019-08-21 04:57 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2019-08-21 04:57 UTC (permalink / raw) WAL-skipping operations mixed with WAL-logged operations can lead to database corruption after a crash. This patch changes the WAL-skipping feature so that no data modification is WAL-logged at all then sync such relations at commit. --- src/backend/access/heap/heapam.c | 4 +- src/backend/access/heap/heapam_handler.c | 22 +---- src/backend/access/heap/rewriteheap.c | 13 +-- src/backend/access/transam/twophase.c | 23 ++++- src/backend/catalog/storage.c | 158 ++++++++++++++++++++++++++----- src/backend/commands/cluster.c | 24 +++++ src/backend/commands/copy.c | 39 ++------ src/backend/commands/createas.c | 5 +- src/backend/commands/matview.c | 4 - src/backend/commands/tablecmds.c | 10 +- src/backend/storage/buffer/bufmgr.c | 33 +++++-- src/backend/storage/smgr/md.c | 30 ++++++ src/backend/utils/cache/relcache.c | 28 ++++-- src/include/access/heapam.h | 1 - src/include/access/rewriteheap.h | 2 +- src/include/access/tableam.h | 40 +------- src/include/catalog/storage.h | 8 ++ src/include/storage/bufmgr.h | 1 + src/include/storage/md.h | 1 + src/include/utils/rel.h | 17 +++- 20 files changed, 300 insertions(+), 163 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index cb811d345a..ef18b61c55 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -1936,7 +1936,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (RelationNeedsWAL(relation)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2119,7 +2119,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); + needwal = RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index f1ff01e8cb..27f414a361 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -558,18 +558,6 @@ tuple_lock_retry: return result; } -static void -heapam_finish_bulk_insert(Relation relation, int options) -{ - /* - * If we skipped writing WAL, then we need to sync the heap (but not - * indexes since those use WAL anyway / don't go through tableam) - */ - if (options & HEAP_INSERT_SKIP_WAL) - heap_sync(relation); -} - - /* ------------------------------------------------------------------------ * DDL related callbacks for heap AM. * ------------------------------------------------------------------------ @@ -701,7 +689,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, IndexScanDesc indexScan; TableScanDesc tableScan; HeapScanDesc heapScan; - bool use_wal; bool is_system_catalog; Tuplesortstate *tuplesort; TupleDesc oldTupDesc = RelationGetDescr(OldHeap); @@ -716,12 +703,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, is_system_catalog = IsSystemRelation(OldHeap); /* - * We need to log the copied data in WAL iff WAL archiving/streaming is - * enabled AND it's a WAL-logged rel. + * smgr_targblock must be initially invalid if we are to skip WAL logging */ - use_wal = XLogIsNeeded() && RelationNeedsWAL(NewHeap); - - /* use_wal off requires smgr_targblock be initially invalid */ Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber); /* Preallocate values/isnull arrays */ @@ -731,7 +714,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Initialize the rewrite operation */ rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff, - *multi_cutoff, use_wal); + *multi_cutoff); /* Set up sorting if wanted */ @@ -2519,7 +2502,6 @@ static const TableAmRoutine heapam_methods = { .tuple_delete = heapam_tuple_delete, .tuple_update = heapam_tuple_update, .tuple_lock = heapam_tuple_lock, - .finish_bulk_insert = heapam_finish_bulk_insert, .tuple_fetch_row_version = heapam_fetch_row_version, .tuple_get_latest_tid = heap_get_latest_tid, diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index a17508a82f..9e0d7295af 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -144,7 +144,6 @@ typedef struct RewriteStateData Page rs_buffer; /* page currently being built */ BlockNumber rs_blockno; /* block where page will go */ bool rs_buffer_valid; /* T if any tuples in buffer */ - bool rs_use_wal; /* must we WAL-log inserts? */ bool rs_logical_rewrite; /* do we need to do logical rewriting */ TransactionId rs_oldest_xmin; /* oldest xmin used by caller to determine * tuple visibility */ @@ -238,15 +237,13 @@ static void logical_end_heap_rewrite(RewriteState state); * oldest_xmin xid used by the caller to determine which tuples are dead * freeze_xid xid before which tuples will be frozen * cutoff_multi multixact before which multis will be removed - * use_wal should the inserts to the new heap be WAL-logged? * * Returns an opaque RewriteState, allocated in current memory context, * to be used in subsequent calls to the other functions. */ RewriteState begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xmin, - TransactionId freeze_xid, MultiXactId cutoff_multi, - bool use_wal) + TransactionId freeze_xid, MultiXactId cutoff_multi) { RewriteState state; MemoryContext rw_cxt; @@ -271,7 +268,6 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm /* new_heap needn't be empty, just locked */ state->rs_blockno = RelationGetNumberOfBlocks(new_heap); state->rs_buffer_valid = false; - state->rs_use_wal = use_wal; state->rs_oldest_xmin = oldest_xmin; state->rs_freeze_xid = freeze_xid; state->rs_cutoff_multi = cutoff_multi; @@ -330,7 +326,7 @@ end_heap_rewrite(RewriteState state) /* Write the last page, if any */ if (state->rs_buffer_valid) { - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, @@ -654,9 +650,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is @@ -695,7 +688,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup) /* Doesn't fit, so write out the existing page */ /* XLOG stuff */ - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 477709bbc2..e3512fc415 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -921,6 +921,7 @@ typedef struct TwoPhaseFileHeader Oid owner; /* user running the transaction */ int32 nsubxacts; /* number of following subxact XIDs */ int32 ncommitrels; /* number of delete-on-commit rels */ + int32 npendsyncrels; /* number of sync-on-commit rels */ int32 nabortrels; /* number of delete-on-abort rels */ int32 ninvalmsgs; /* number of cache invalidation messages */ bool initfileinval; /* does relcache init file need invalidation? */ @@ -1009,6 +1010,7 @@ StartPrepare(GlobalTransaction gxact) TwoPhaseFileHeader hdr; TransactionId *children; RelFileNode *commitrels; + RelFileNode *pendsyncrels; RelFileNode *abortrels; SharedInvalidationMessage *invalmsgs; @@ -1034,6 +1036,7 @@ StartPrepare(GlobalTransaction gxact) hdr.owner = gxact->owner; hdr.nsubxacts = xactGetCommittedChildren(&children); hdr.ncommitrels = smgrGetPendingDeletes(true, &commitrels); + hdr.npendsyncrels = smgrGetPendingSyncs(true, &pendsyncrels); hdr.nabortrels = smgrGetPendingDeletes(false, &abortrels); hdr.ninvalmsgs = xactGetCommittedInvalidationMessages(&invalmsgs, &hdr.initfileinval); @@ -1057,6 +1060,11 @@ StartPrepare(GlobalTransaction gxact) save_state_data(commitrels, hdr.ncommitrels * sizeof(RelFileNode)); pfree(commitrels); } + if (hdr.npendsyncrels > 0) + { + save_state_data(pendsyncrels, hdr.npendsyncrels * sizeof(RelFileNode)); + pfree(pendsyncrels); + } if (hdr.nabortrels > 0) { save_state_data(abortrels, hdr.nabortrels * sizeof(RelFileNode)); @@ -1464,6 +1472,7 @@ FinishPreparedTransaction(const char *gid, bool isCommit) TransactionId latestXid; TransactionId *children; RelFileNode *commitrels; + RelFileNode *pendsyncrels; RelFileNode *abortrels; RelFileNode *delrels; int ndelrels; @@ -1499,6 +1508,8 @@ FinishPreparedTransaction(const char *gid, bool isCommit) children = (TransactionId *) bufptr; bufptr += MAXALIGN(hdr->nsubxacts * sizeof(TransactionId)); commitrels = (RelFileNode *) bufptr; + bufptr += MAXALIGN(hdr->npendsyncrels * sizeof(RelFileNode)); + pendsyncrels = (RelFileNode *) bufptr; bufptr += MAXALIGN(hdr->ncommitrels * sizeof(RelFileNode)); abortrels = (RelFileNode *) bufptr; bufptr += MAXALIGN(hdr->nabortrels * sizeof(RelFileNode)); @@ -1544,9 +1555,9 @@ FinishPreparedTransaction(const char *gid, bool isCommit) gxact->valid = false; /* - * We have to remove any files that were supposed to be dropped. For - * consistency with the regular xact.c code paths, must do this before - * releasing locks, so do it before running the callbacks. + * We have to sync or remove any files that were supposed to be done + * so. For consistency with the regular xact.c code paths, must do this + * before releasing locks, so do it before running the callbacks. * * NB: this code knows that we couldn't be dropping any temp rels ... */ @@ -1554,11 +1565,17 @@ FinishPreparedTransaction(const char *gid, bool isCommit) { delrels = commitrels; ndelrels = hdr->ncommitrels; + + /* Make sure files supposed to be synced are synced */ + SyncRelationFiles(pendsyncrels, hdr->npendsyncrels); } else { delrels = abortrels; ndelrels = hdr->nabortrels; + + /* We don't have an at-abort pending sync */ + Assert(pendsyncrels == 0); } /* Make sure files supposed to be dropped are dropped */ diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index 3cc886f7fe..354a74c27c 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -30,6 +30,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" @@ -53,11 +54,13 @@ * but I'm being paranoid. */ +/* entry type of pendingDeletes */ typedef struct PendingRelDelete { RelFileNode relnode; /* relation that may need to be deleted */ BackendId backend; /* InvalidBackendId if not a temp rel */ - bool atCommit; /* T=delete at commit; F=delete at abort */ + bool atCommit; /* T=work at commit; F=work at abort */ + PendingOpType op; /* type of operation to do */ int nestLevel; /* xact nesting level of request */ struct PendingRelDelete *next; /* linked-list link */ } PendingRelDelete; @@ -114,10 +117,29 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) pending->relnode = rnode; pending->backend = backend; pending->atCommit = false; /* delete if abort */ + pending->op = PENDING_DELETE; pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; pendingDeletes = pending; + /* + * We are going to skip WAL-logging for storage of persistent relations + * created in the current transaction when wal_level = minimal. The + * relation needs to be synced at commit. + */ + if (relpersistence == RELPERSISTENCE_PERMANENT && !XLogIsNeeded()) + { + pending = (PendingRelDelete *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); + pending->relnode = rnode; + pending->backend = backend; + pending->atCommit = true; + pending->op = PENDING_SYNC; + pending->nestLevel = GetCurrentTransactionNestLevel(); + pending->next = pendingDeletes; + pendingDeletes = pending; + } + return srel; } @@ -155,6 +177,7 @@ RelationDropStorage(Relation rel) pending->relnode = rel->rd_node; pending->backend = rel->rd_backend; pending->atCommit = true; /* delete if commit */ + pending->op = PENDING_DELETE; pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; pendingDeletes = pending; @@ -201,7 +224,8 @@ RelationPreserveStorage(RelFileNode rnode, bool atCommit) { next = pending->next; if (RelFileNodeEquals(rnode, pending->relnode) - && pending->atCommit == atCommit) + && pending->atCommit == atCommit + && pending->op == PENDING_DELETE) { /* unlink and delete list entry */ if (prev) @@ -406,6 +430,7 @@ smgrDoPendingDeletes(bool isCommit) i = 0, maxrels = 0; SMgrRelation *srels = NULL; + struct HTAB *synchash = NULL; prev = NULL; for (pending = pendingDeletes; pending != NULL; pending = next) @@ -428,21 +453,50 @@ smgrDoPendingDeletes(bool isCommit) { SMgrRelation srel; - srel = smgropen(pending->relnode, pending->backend); - - /* allocate the initial array, or extend it, if needed */ - if (maxrels == 0) + if (pending->op == PENDING_SYNC) { - maxrels = 8; - srels = palloc(sizeof(SMgrRelation) * maxrels); - } - else if (maxrels <= nrels) - { - maxrels *= 2; - srels = repalloc(srels, sizeof(SMgrRelation) * maxrels); - } + /* We don't have abort-time pending syncs */ + Assert(isCommit); - srels[nrels++] = srel; + /* Create hash if not yet */ + if (synchash == NULL) + { + HASHCTL hash_ctl; + + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(SMgrRelation*); + hash_ctl.entrysize = sizeof(SMgrRelation*); + hash_ctl.hcxt = CurrentMemoryContext; + synchash = + hash_create("pending sync hash", 8, + &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + } + + /* Collect pending syncs */ + srel = smgropen(pending->relnode, pending->backend); + (void) hash_search(synchash, (void *) &srel, + HASH_ENTER, NULL); + } + else + { + /* Collect pending deletions */ + srel = smgropen(pending->relnode, pending->backend); + + /* allocate the initial array, or extend it, if needed */ + if (maxrels == 0) + { + maxrels = 8; + srels = palloc(sizeof(SMgrRelation) * maxrels); + } + else if (maxrels <= nrels) + { + maxrels *= 2; + srels = repalloc(srels, sizeof(SMgrRelation) * maxrels); + } + + srels[nrels++] = srel; + } } /* must explicitly free the list entry */ pfree(pending); @@ -450,6 +504,43 @@ smgrDoPendingDeletes(bool isCommit) } } + /* Sync only files that are not to be removed. */ + if (synchash) + { + HASH_SEQ_STATUS hstat; + SMgrRelation *psrel; + + /* remove to-be-removed files from synchash */ + if (nrels > 0) + { + int i; + bool found; + + for (i = 0 ; i < nrels ; i++) + (void) hash_search(synchash, (void *) &(srels[i]), + HASH_REMOVE, &found); + } + + /* sync survuvied files */ + hash_seq_init(&hstat, synchash); + while ((psrel = (SMgrRelation *) hash_seq_search(&hstat)) != NULL) + { + ForkNumber fork; + + /* Perform pending sync of WAL-skipped relation */ + FlushRelationBuffersWithoutRelcache((*psrel)->smgr_rnode.node, + false); + for (fork = 0; fork <= MAX_FORKNUM; fork++) + { + if (smgrexists(*psrel, fork)) + smgrimmedsync(*psrel, fork); + } + } + + hash_destroy(synchash); + synchash = NULL; + } + if (nrels > 0) { smgrdounlinkall(srels, nrels, false); @@ -462,11 +553,12 @@ smgrDoPendingDeletes(bool isCommit) } /* - * smgrGetPendingDeletes() -- Get a list of non-temp relations to be deleted. + * smgrGetPendingOperations() -- Get a list of non-temp relations to be + * deleted or synced. * - * The return value is the number of relations scheduled for termination. - * *ptr is set to point to a freshly-palloc'd array of RelFileNodes. - * If there are no relations to be deleted, *ptr is set to NULL. + * The return value is the number of relations scheduled for the operation + * specified by op. *ptr is set to point to a freshly-palloc'd array of + * RelFileNodes. If there are no matching relations, *ptr is set to NULL. * * Only non-temporary relations are included in the returned list. This is OK * because the list is used only in contexts where temporary relations don't @@ -475,11 +567,11 @@ smgrDoPendingDeletes(bool isCommit) * (and all temporary files will be zapped if we restart anyway, so no need * for redo to do it also). * - * Note that the list does not include anything scheduled for termination - * by upper-level transactions. + * Note that the list does not include anything scheduled by upper-level + * transactions. */ -int -smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) +static inline int +smgrGetPendingOperations(PendingOpType op, bool forCommit, RelFileNode **ptr) { int nestLevel = GetCurrentTransactionNestLevel(); int nrels; @@ -490,7 +582,8 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) for (pending = pendingDeletes; pending != NULL; pending = pending->next) { if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit - && pending->backend == InvalidBackendId) + && pending->backend == InvalidBackendId + && pending->op == op) nrels++; } if (nrels == 0) @@ -503,7 +596,8 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) for (pending = pendingDeletes; pending != NULL; pending = pending->next) { if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit - && pending->backend == InvalidBackendId) + && pending->backend == InvalidBackendId + && pending->op == op) { *rptr = pending->relnode; rptr++; @@ -512,6 +606,20 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) return nrels; } +/* Returns list of pending deletes, see smgrGetPendingOperations for details */ +int +smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) +{ + return smgrGetPendingOperations(PENDING_DELETE, forCommit, ptr); +} + +/* Returns list of pending syncs, see smgrGetPendingOperations for details */ +int +smgrGetPendingSyncs(bool forCommit, RelFileNode **ptr) +{ + return smgrGetPendingOperations(PENDING_SYNC, forCommit, ptr); +} + /* * PostPrepare_smgr -- Clean up after a successful PREPARE * diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 28985a07ec..f665ee8358 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1034,12 +1034,36 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, if (OidIsValid(relfilenode1) && OidIsValid(relfilenode2)) { + Relation rel1; + Relation rel2; + /* * Normal non-mapped relations: swap relfilenodes, reltablespaces, * relpersistence */ Assert(!target_is_pg_class); + /* Update creation subid hints of relcache */ + rel1 = relation_open(r1, ExclusiveLock); + rel2 = relation_open(r2, ExclusiveLock); + + /* + * New relation's relfilenode is created in the current transaction + * and used as old ralation's new relfilenode. So its + * newRelfilenodeSubid as new relation's createSubid. We don't fix + * rel2 since it would be deleted soon. + */ + Assert(rel2->rd_createSubid != InvalidSubTransactionId); + rel1->rd_newRelfilenodeSubid = rel2->rd_createSubid; + + /* record the first relfilenode change in the current transaction */ + if (rel1->rd_firstRelfilenodeSubid == InvalidSubTransactionId) + rel1->rd_firstRelfilenodeSubid = GetCurrentSubTransactionId(); + + relation_close(rel1, ExclusiveLock); + relation_close(rel2, ExclusiveLock); + + /* swap relfilenodes, reltablespaces, relpersistence */ swaptemp = relform1->relfilenode; relform1->relfilenode = relform2->relfilenode; relform2->relfilenode = swaptemp; diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 3aeef30b28..3ce04f7efc 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2534,9 +2534,6 @@ CopyMultiInsertBufferCleanup(CopyMultiInsertInfo *miinfo, for (i = 0; i < MAX_BUFFERED_TUPLES && buffer->slots[i] != NULL; i++) ExecDropSingleTupleTableSlot(buffer->slots[i]); - table_finish_bulk_insert(buffer->resultRelInfo->ri_RelationDesc, - miinfo->ti_options); - pfree(buffer); } @@ -2725,28 +2722,9 @@ CopyFrom(CopyState cstate) * If it does commit, we'll have done the table_finish_bulk_insert() 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 - * can be cleared before the end of the transaction. The exact case is - * when a relation sets a new relfilenode twice in same transaction, yet - * the second one fails in an aborted subtransaction, e.g. - * - * BEGIN; - * TRUNCATE t; - * SAVEPOINT save; - * TRUNCATE t; - * ROLLBACK TO save; - * COPY ... - * - * Also, if the target file is new-in-transaction, we assume that checking - * FSM for free space is a waste of time, even if we must use WAL because - * of archiving. This could possibly be wrong, but it's unlikely. - * - * The comments for table_tuple_insert and RelationGetBufferForTuple - * specify that skipping WAL logging is only safe if we ensure that our - * tuples do not go into pages containing tuples from any other - * transactions --- but this must be the case if we have a new table or - * new relfilenode, so we need no additional work to enforce that. + * If the target file is new-in-transaction, we assume that checking FSM + * for free space is a waste of time, even if we must use WAL because of + * archiving. This could possibly be wrong, but it's unlikely. * * We currently don't support this optimization if the COPY target is a * partitioned table as we currently only lazily initialize partition @@ -2762,15 +2740,14 @@ CopyFrom(CopyState cstate) * are not supported as per the description above. *---------- */ - /* createSubid is creation check, newRelfilenodeSubid is truncation check */ + /* + * createSubid is creation check, firstRelfilenodeSubid is truncation and + * cluster check. Partitioned table doesn't have storage. + */ if (RELKIND_HAS_STORAGE(cstate->rel->rd_rel->relkind) && (cstate->rel->rd_createSubid != InvalidSubTransactionId || - cstate->rel->rd_newRelfilenodeSubid != InvalidSubTransactionId)) - { + cstate->rel->rd_firstRelfilenodeSubid != InvalidSubTransactionId)) ti_options |= TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; - } /* * Optimize if new relfilenode was created in this subxact or one of its diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index b7d220699f..8a91d946e3 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -558,8 +558,7 @@ 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->ti_options = TABLE_INSERT_SKIP_FSM | - (XLogIsNeeded() ? 0 : TABLE_INSERT_SKIP_WAL); + myState->ti_options = TABLE_INSERT_SKIP_FSM; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -604,8 +603,6 @@ intorel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->rel, myState->ti_options); - /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); myState->rel = NULL; diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 537d0e8cef..1c854dcebf 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -463,8 +463,6 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) * replication is in use. We can skip the FSM in any case. */ myState->ti_options = TABLE_INSERT_SKIP_FSM | TABLE_INSERT_FROZEN; - if (!XLogIsNeeded()) - myState->ti_options |= TABLE_INSERT_SKIP_WAL; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,8 +507,6 @@ transientrel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->transientrel, myState->ti_options); - /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); myState->transientrel = NULL; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index cceefbdd49..2468b178cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4762,9 +4762,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) /* * Prepare a BulkInsertState and options for table_tuple_insert. Because - * we're building a new heap, we can skip WAL-logging and fsync it to disk - * at the end instead (unless WAL-logging is required for archiving or - * streaming replication). The FSM is empty too, so don't bother using it. + * we're building a new heap, the underlying table AM can skip WAL-logging + * and smgr will sync the relation to disk at the end of the current + * transaction instead. The FSM is empty too, so don't bother using it. */ if (newrel) { @@ -4772,8 +4772,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); ti_options = TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; } else { @@ -5058,8 +5056,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { FreeBulkInsertState(bistate); - table_finish_bulk_insert(newrel, ti_options); - table_close(newrel, NoLock); } } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 6f3a402854..41ff6da9d9 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -171,6 +171,7 @@ static HTAB *PrivateRefCountHash = NULL; static int32 PrivateRefCountOverflowed = 0; static uint32 PrivateRefCountClock = 0; static PrivateRefCountEntry *ReservedRefCountEntry = NULL; +static void FlushRelationBuffers_common(SMgrRelation smgr, bool islocal); static void ReservePrivateRefCountEntry(void); static PrivateRefCountEntry *NewPrivateRefCountEntry(Buffer buffer); @@ -3191,20 +3192,32 @@ 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)); +} + +void +FlushRelationBuffersWithoutRelcache(RelFileNode rnode, bool islocal) +{ + FlushRelationBuffers_common(smgropen(rnode, InvalidBackendId), islocal); +} + +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)) { @@ -3221,7 +3234,7 @@ FlushRelationBuffers(Relation rel) PageSetChecksumInplace(localpage, bufHdr->tag.blockNum); - smgrwrite(rel->rd_smgr, + smgrwrite(smgr, bufHdr->tag.forkNum, bufHdr->tag.blockNum, localpage, @@ -3251,18 +3264,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/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 07f3c93d3f..514c6098e6 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -994,6 +994,36 @@ ForgetDatabaseSyncRequests(Oid dbid) RegisterSyncRequest(&tag, SYNC_FILTER_REQUEST, true /* retryOnError */ ); } +/* + * SyncRelationFiles -- sync files of all given relations + * + * This function is assumed to be called only when skipping WAL-logging and + * emits no xlog records. + */ +void +SyncRelationFiles(RelFileNode *syncrels, int nsyncrels) +{ + int i; + + for (i = 0; i < nsyncrels; i++) + { + SMgrRelation srel; + ForkNumber fork; + + /* sync all existing forks of the relation */ + FlushRelationBuffersWithoutRelcache(syncrels[i], false); + srel = smgropen(syncrels[i], InvalidBackendId); + + for (fork = 0; fork <= MAX_FORKNUM; fork++) + { + if (smgrexists(srel, fork)) + smgrimmedsync(srel, fork); + } + + smgrclose(srel); + } +} + /* * DropRelationFiles -- drop files of all given relations */ diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 248860758c..147babb6b5 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -1096,6 +1096,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) relation->rd_isnailed = false; relation->rd_createSubid = InvalidSubTransactionId; relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; switch (relation->rd_rel->relpersistence) { case RELPERSISTENCE_UNLOGGED: @@ -1829,6 +1830,7 @@ formrdesc(const char *relationName, Oid relationReltype, relation->rd_isnailed = true; relation->rd_createSubid = InvalidSubTransactionId; relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; relation->rd_backend = InvalidBackendId; relation->rd_islocaltemp = false; @@ -2094,7 +2096,7 @@ RelationClose(Relation relation) #ifdef RELCACHE_FORCE_RELEASE if (RelationHasReferenceCountZero(relation) && relation->rd_createSubid == InvalidSubTransactionId && - relation->rd_newRelfilenodeSubid == InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId) RelationClearRelation(relation, false); #endif } @@ -2510,8 +2512,8 @@ RelationClearRelation(Relation relation, bool rebuild) * problem. * * When rebuilding an open relcache entry, we must preserve ref count, - * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also - * attempt to preserve the pg_class entry (rd_rel), tupledesc, + * rd_createSubid/rd_new/firstRelfilenodeSubid, and rd_toastoid state. + * Also attempt to preserve the pg_class entry (rd_rel), tupledesc, * rewrite-rule, partition key, and partition descriptor substructures * in place, because various places assume that these structures won't * move while they are working with an open relcache entry. (Note: @@ -2600,6 +2602,7 @@ RelationClearRelation(Relation relation, bool rebuild) /* creation sub-XIDs must be preserved */ SWAPFIELD(SubTransactionId, rd_createSubid); SWAPFIELD(SubTransactionId, rd_newRelfilenodeSubid); + SWAPFIELD(SubTransactionId, rd_firstRelfilenodeSubid); /* un-swap rd_rel pointers, swap contents instead */ SWAPFIELD(Form_pg_class, rd_rel); /* ... but actually, we don't have to update newrel->rd_rel */ @@ -2667,7 +2670,7 @@ static void RelationFlushRelation(Relation relation) { if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) { /* * New relcache entries are always rebuilt, not flushed; else we'd @@ -2807,7 +2810,7 @@ RelationCacheInvalidate(void) * pending invalidations. */ if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) continue; relcacheInvalsReceived++; @@ -3064,6 +3067,7 @@ AtEOXact_cleanup(Relation relation, bool isCommit) * Likewise, reset the hint about the relfilenode being new. */ relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; } /* @@ -3155,7 +3159,7 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, } /* - * Likewise, update or drop any new-relfilenode-in-subtransaction hint. + * Likewise, update or drop any new-relfilenode-in-subtransaction hints. */ if (relation->rd_newRelfilenodeSubid == mySubid) { @@ -3164,6 +3168,14 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, else relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; } + + if (relation->rd_firstRelfilenodeSubid == mySubid) + { + if (isCommit) + relation->rd_firstRelfilenodeSubid = parentSubid; + else + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; + } } @@ -3253,6 +3265,7 @@ RelationBuildLocalRelation(const char *relname, /* it's being created in this transaction */ rel->rd_createSubid = GetCurrentSubTransactionId(); rel->rd_newRelfilenodeSubid = InvalidSubTransactionId; + rel->rd_firstRelfilenodeSubid = InvalidSubTransactionId; /* * create a new tuple descriptor from the one passed in. We do this @@ -3556,6 +3569,8 @@ RelationSetNewRelfilenode(Relation relation, char persistence) * operations on the rel in the same transaction. */ relation->rd_newRelfilenodeSubid = GetCurrentSubTransactionId(); + if (relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid = relation->rd_newRelfilenodeSubid; /* Flag relation as needing eoxact cleanup (to remove the hint) */ EOXactListAdd(relation); @@ -5592,6 +5607,7 @@ load_relcache_init_file(bool shared) rel->rd_fkeylist = NIL; rel->rd_createSubid = InvalidSubTransactionId; rel->rd_newRelfilenodeSubid = InvalidSubTransactionId; + rel->rd_firstRelfilenodeSubid = InvalidSubTransactionId; rel->rd_amcache = NULL; MemSet(&rel->pgstat_info, 0, sizeof(rel->pgstat_info)); diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 858bcb6bc9..80c2e1bafc 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -29,7 +29,6 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL #define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM #define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN #define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 8056253916..7f9736e294 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -23,7 +23,7 @@ typedef struct RewriteStateData *RewriteState; extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap, TransactionId OldestXmin, TransactionId FreezeXid, - MultiXactId MultiXactCutoff, bool use_wal); + MultiXactId MultiXactCutoff); extern void end_heap_rewrite(RewriteState state); extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple, HeapTuple newTuple); diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 7f81703b78..b652cd6cef 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -407,22 +407,6 @@ typedef struct TableAmRoutine uint8 flags, TM_FailureData *tmfd); - /* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * may for example be used to flush the relation, when the - * TABLE_INSERT_SKIP_WAL option was used. - * - * Typically callers of tuple_insert and multi_insert will just pass all - * the flags that apply to them, and each AM has to decide which of them - * make sense for it, and then only take actions in finish_bulk_insert for - * those flags, and ignore others. - * - * Optional callback. - */ - void (*finish_bulk_insert) (Relation rel, int options); - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ @@ -1087,10 +1071,6 @@ table_compute_xid_horizon_for_tuples(Relation rel, * The options bitmask allows the caller to specify options that may change the * behaviour of the AM. The AM will ignore options that it does not support. * - * If the TABLE_INSERT_SKIP_WAL option is specified, the new tuple doesn't - * need to be logged to WAL, even for a non-temp relation. It is the AMs - * choice whether this optimization is supported. - * * If the TABLE_INSERT_SKIP_FSM option is specified, AMs are free to not reuse * free space in the relation. This can save some cycles when we know the * relation is new and doesn't contain useful amounts of free space. @@ -1112,8 +1092,7 @@ table_compute_xid_horizon_for_tuples(Relation rel, * heap's TOAST table, too, if the tuple requires any out-of-line data. * * The BulkInsertState object (if any; bistate can be NULL for default - * behavior) is also just passed through to RelationGetBufferForTuple. If - * `bistate` is provided, table_finish_bulk_insert() needs to be called. + * behavior) is also just passed through to RelationGetBufferForTuple. * * On return the slot's tts_tid and tts_tableOid are updated to reflect the * insertion. But note that any toasting of fields within the slot is NOT @@ -1248,6 +1227,8 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, * update was done. However, any TOAST changes in the new tuple's * data are not reflected into *newtup. * + * See table_insert about skipping WAL-logging feature. + * * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, * t_xmax, and, if possible, t_cmax. See comments for struct TM_FailureData * for additional info. @@ -1308,21 +1289,6 @@ table_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, flags, tmfd); } -/* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * e.g. may e.g. used to flush the relation when inserting with - * TABLE_INSERT_SKIP_WAL specified. - */ -static inline void -table_finish_bulk_insert(Relation rel, int options) -{ - /* optional callback */ - if (rel->rd_tableam && rel->rd_tableam->finish_bulk_insert) - rel->rd_tableam->finish_bulk_insert(rel, options); -} - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h index 3579d3f3eb..1de6f1655c 100644 --- a/src/include/catalog/storage.h +++ b/src/include/catalog/storage.h @@ -19,6 +19,13 @@ #include "storage/smgr.h" #include "utils/relcache.h" +/* enum for operation type of PendingDelete entries */ +typedef enum PendingOpType +{ + PENDING_DELETE, + PENDING_SYNC +} PendingOpType; + extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence); extern void RelationDropStorage(Relation rel); extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit); @@ -32,6 +39,7 @@ extern void RelationCopyStorage(SMgrRelation src, SMgrRelation dst, */ extern void smgrDoPendingDeletes(bool isCommit); extern int smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr); +extern int smgrGetPendingSyncs(bool forCommit, RelFileNode **ptr); 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 509f4b7ef1..ace5f5a2ae 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -189,6 +189,7 @@ 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/storage/md.h b/src/include/storage/md.h index c0f05e23ff..2bb2947bdb 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -42,6 +42,7 @@ extern void mdtruncate(SMgrRelation reln, ForkNumber forknum, extern void mdimmedsync(SMgrRelation reln, ForkNumber forknum); extern void ForgetDatabaseSyncRequests(Oid dbid); +extern void SyncRelationFiles(RelFileNode *syncrels, int nsyncrels); extern void DropRelationFiles(RelFileNode *delrels, int ndelrels, bool isRedo); /* md sync callbacks */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index c5d36680a2..f372dc2086 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -75,10 +75,17 @@ typedef struct RelationData * transaction, with one of them occurring in a subsequently aborted * subtransaction, e.g. BEGIN; TRUNCATE t; SAVEPOINT save; TRUNCATE t; * ROLLBACK TO save; -- rd_newRelfilenodeSubid is now forgotten + * rd_firstRelfilenodeSubid is the ID of the first subtransaction the + * relfilenode change has took place in the current transaction. Unlike + * newRelfilenodeSubid, this won't be accidentially forgotten. A valid OID + * means that the currently active relfilenode is transaction-local and we + * sync the relation at commit instead of WAL-logging. */ SubTransactionId rd_createSubid; /* rel was created in current xact */ SubTransactionId rd_newRelfilenodeSubid; /* new relfilenode assigned in * current xact */ + SubTransactionId rd_firstRelfilenodeSubid; /* new relfilenode assigned + * first in current xact */ Form_pg_class rd_rel; /* RELATION tuple */ TupleDesc rd_att; /* tuple descriptor */ @@ -514,9 +521,15 @@ typedef struct ViewOptions /* * RelationNeedsWAL * True if relation needs WAL. + * + * Returns false if wal_level = minimal and this relation is created or + * truncated in the current transaction. */ -#define RelationNeedsWAL(relation) \ - ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) +#define RelationNeedsWAL(relation) \ + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT && \ + (XLogIsNeeded() || \ + (relation->rd_createSubid == InvalidSubTransactionId && \ + relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId))) /* * RelationUsesLocalBuffers -- 2.16.3 ----Next_Part(Wed_Aug_21_16_32_38_2019_483)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v19-0003-Rename-smgrDoPendingDeletes-to-smgrDoPendingOperatio.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 2/4] Fix WAL skipping feature @ 2019-08-21 04:57 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2019-08-21 04:57 UTC (permalink / raw) WAL-skipping operations mixed with WAL-logged operations can lead to database corruption after a crash. This patch changes the WAL-skipping feature so that no data modification is WAL-logged at all then sync such relations at commit. --- src/backend/access/heap/heapam.c | 4 +- src/backend/access/heap/heapam_handler.c | 22 +-- src/backend/access/heap/rewriteheap.c | 13 +- src/backend/access/transam/xact.c | 17 ++ src/backend/access/transam/xlogutils.c | 11 +- src/backend/catalog/storage.c | 295 +++++++++++++++++++++++++++---- src/backend/commands/cluster.c | 24 +++ src/backend/commands/copy.c | 39 +--- src/backend/commands/createas.c | 5 +- src/backend/commands/matview.c | 4 - src/backend/commands/tablecmds.c | 10 +- src/backend/storage/buffer/bufmgr.c | 41 +++-- src/backend/storage/smgr/md.c | 30 ++++ src/backend/utils/cache/relcache.c | 28 ++- src/backend/utils/misc/guc.c | 13 ++ src/include/access/heapam.h | 1 - src/include/access/rewriteheap.h | 2 +- src/include/access/tableam.h | 40 +---- src/include/catalog/storage.h | 12 ++ src/include/storage/bufmgr.h | 1 + src/include/storage/md.h | 1 + src/include/utils/rel.h | 17 +- 22 files changed, 455 insertions(+), 175 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index cb811d345a..ef18b61c55 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -1936,7 +1936,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (RelationNeedsWAL(relation)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2119,7 +2119,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); + needwal = RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index f1ff01e8cb..27f414a361 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -558,18 +558,6 @@ tuple_lock_retry: return result; } -static void -heapam_finish_bulk_insert(Relation relation, int options) -{ - /* - * If we skipped writing WAL, then we need to sync the heap (but not - * indexes since those use WAL anyway / don't go through tableam) - */ - if (options & HEAP_INSERT_SKIP_WAL) - heap_sync(relation); -} - - /* ------------------------------------------------------------------------ * DDL related callbacks for heap AM. * ------------------------------------------------------------------------ @@ -701,7 +689,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, IndexScanDesc indexScan; TableScanDesc tableScan; HeapScanDesc heapScan; - bool use_wal; bool is_system_catalog; Tuplesortstate *tuplesort; TupleDesc oldTupDesc = RelationGetDescr(OldHeap); @@ -716,12 +703,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, is_system_catalog = IsSystemRelation(OldHeap); /* - * We need to log the copied data in WAL iff WAL archiving/streaming is - * enabled AND it's a WAL-logged rel. + * smgr_targblock must be initially invalid if we are to skip WAL logging */ - use_wal = XLogIsNeeded() && RelationNeedsWAL(NewHeap); - - /* use_wal off requires smgr_targblock be initially invalid */ Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber); /* Preallocate values/isnull arrays */ @@ -731,7 +714,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Initialize the rewrite operation */ rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff, - *multi_cutoff, use_wal); + *multi_cutoff); /* Set up sorting if wanted */ @@ -2519,7 +2502,6 @@ static const TableAmRoutine heapam_methods = { .tuple_delete = heapam_tuple_delete, .tuple_update = heapam_tuple_update, .tuple_lock = heapam_tuple_lock, - .finish_bulk_insert = heapam_finish_bulk_insert, .tuple_fetch_row_version = heapam_fetch_row_version, .tuple_get_latest_tid = heap_get_latest_tid, diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index a17508a82f..9e0d7295af 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -144,7 +144,6 @@ typedef struct RewriteStateData Page rs_buffer; /* page currently being built */ BlockNumber rs_blockno; /* block where page will go */ bool rs_buffer_valid; /* T if any tuples in buffer */ - bool rs_use_wal; /* must we WAL-log inserts? */ bool rs_logical_rewrite; /* do we need to do logical rewriting */ TransactionId rs_oldest_xmin; /* oldest xmin used by caller to determine * tuple visibility */ @@ -238,15 +237,13 @@ static void logical_end_heap_rewrite(RewriteState state); * oldest_xmin xid used by the caller to determine which tuples are dead * freeze_xid xid before which tuples will be frozen * cutoff_multi multixact before which multis will be removed - * use_wal should the inserts to the new heap be WAL-logged? * * Returns an opaque RewriteState, allocated in current memory context, * to be used in subsequent calls to the other functions. */ RewriteState begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xmin, - TransactionId freeze_xid, MultiXactId cutoff_multi, - bool use_wal) + TransactionId freeze_xid, MultiXactId cutoff_multi) { RewriteState state; MemoryContext rw_cxt; @@ -271,7 +268,6 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm /* new_heap needn't be empty, just locked */ state->rs_blockno = RelationGetNumberOfBlocks(new_heap); state->rs_buffer_valid = false; - state->rs_use_wal = use_wal; state->rs_oldest_xmin = oldest_xmin; state->rs_freeze_xid = freeze_xid; state->rs_cutoff_multi = cutoff_multi; @@ -330,7 +326,7 @@ end_heap_rewrite(RewriteState state) /* Write the last page, if any */ if (state->rs_buffer_valid) { - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, @@ -654,9 +650,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is @@ -695,7 +688,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup) /* Doesn't fit, so write out the existing page */ /* XLOG stuff */ - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index f594d33e7a..1c4b264947 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -2107,6 +2107,13 @@ CommitTransaction(void) */ PreCommit_on_commit_actions(); + /* + * Synchronize files that are created and not WAL-logged during this + * transaction. This must happen before emitting commit record so that we + * don't see committed-but-broken files after a crash. + */ + smgrDoPendingSyncs(true, false); + /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); @@ -2339,6 +2346,14 @@ PrepareTransaction(void) */ PreCommit_on_commit_actions(); + /* + * Sync all WAL-skipped files now. Some of them may be deleted at + * transaction end but we don't bother store that information in PREPARE + * record or two-phase files. Like commit, we should sync WAL-skipped + * files before emitting PREPARE record. See CommitTransaction(). + */ + smgrDoPendingSyncs(true, true); + /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); @@ -2657,6 +2672,7 @@ AbortTransaction(void) */ AfterTriggerEndXact(false); /* 'false' means it's abort */ AtAbort_Portals(); + smgrDoPendingSyncs(false, false); AtEOXact_LargeObject(false); AtAbort_Notify(); AtEOXact_RelationMap(false, is_parallel_worker); @@ -4941,6 +4957,7 @@ AbortSubTransaction(void) s->parent->curTransactionOwner); AtEOSubXact_LargeObject(false, s->subTransactionId, s->parent->subTransactionId); + smgrDoPendingSyncs(false, false); AtSubAbort_Notify(); /* Advertise the fact that we aborted in pg_xact. */ diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 1fc39333f1..ff7dba429a 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -544,6 +544,8 @@ typedef FakeRelCacheEntryData *FakeRelCacheEntry; * fields related to physical storage, like rd_rel, are initialized, so the * fake entry is only usable in low-level operations like ReadBuffer(). * + * This is also used for syncing WAL-skipped files. + * * Caller must free the returned entry with FreeFakeRelcacheEntry(). */ Relation @@ -552,18 +554,19 @@ CreateFakeRelcacheEntry(RelFileNode rnode) FakeRelCacheEntry fakeentry; Relation rel; - Assert(InRecovery); - /* Allocate the Relation struct and all related space in one block. */ fakeentry = palloc0(sizeof(FakeRelCacheEntryData)); rel = (Relation) fakeentry; rel->rd_rel = &fakeentry->pgc; rel->rd_node = rnode; - /* We will never be working with temp rels during recovery */ + /* + * We will never be working with temp rels during recovery or syncing + * WAL-skpped files. + */ rel->rd_backend = InvalidBackendId; - /* It must be a permanent table if we're in recovery. */ + /* It must be a permanent table here */ rel->rd_rel->relpersistence = RELPERSISTENCE_PERMANENT; /* We don't know the name of the relation; use relfilenode instead */ diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index 3cc886f7fe..43926ecaba 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -30,9 +30,13 @@ #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" +/* GUC variables */ +int effective_io_block_size = 64; /* threshold of WAL-skipping in kilobytes */ + /* * We keep a list of all relations (represented as RelFileNode values) * that have been created or deleted in the current transaction. When @@ -53,16 +57,17 @@ * but I'm being paranoid. */ -typedef struct PendingRelDelete +typedef struct PendingRelOp { RelFileNode relnode; /* relation that may need to be deleted */ BackendId backend; /* InvalidBackendId if not a temp rel */ - bool atCommit; /* T=delete at commit; F=delete at abort */ + bool atCommit; /* T=work at commit; F=work at abort */ int nestLevel; /* xact nesting level of request */ - struct PendingRelDelete *next; /* linked-list link */ -} PendingRelDelete; + struct PendingRelOp *next; /* linked-list link */ +} PendingRelOp; -static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */ +static PendingRelOp *pendingDeletes = NULL; /* head of linked list */ +static PendingRelOp *pendingSyncs = NULL; /* head of linked list */ /* * RelationCreateStorage @@ -78,7 +83,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */ SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence) { - PendingRelDelete *pending; + PendingRelOp *pending; SMgrRelation srel; BackendId backend; bool needs_wal; @@ -109,8 +114,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) log_smgrcreate(&srel->smgr_rnode.node, MAIN_FORKNUM); /* Add the relation to the list of stuff to delete at abort */ - pending = (PendingRelDelete *) - MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); + pending = (PendingRelOp *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelOp)); pending->relnode = rnode; pending->backend = backend; pending->atCommit = false; /* delete if abort */ @@ -118,6 +123,25 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) pending->next = pendingDeletes; pendingDeletes = pending; + /* + * When wal_level = minimal, we are going to skip WAL-logging for storage + * of persistent relations created in the current transaction. The + * relation needs to be synced at commit. + */ + if (relpersistence == RELPERSISTENCE_PERMANENT && !XLogIsNeeded()) + { + int nestLevel = GetCurrentTransactionNestLevel(); + + pending = (PendingRelOp *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelOp)); + pending->relnode = rnode; + pending->backend = backend; + pending->atCommit = true; + pending->nestLevel = nestLevel; + pending->next = pendingSyncs; + pendingSyncs = pending; + } + return srel; } @@ -147,11 +171,11 @@ log_smgrcreate(const RelFileNode *rnode, ForkNumber forkNum) void RelationDropStorage(Relation rel) { - PendingRelDelete *pending; + PendingRelOp *pending; /* Add the relation to the list of stuff to delete at commit */ - pending = (PendingRelDelete *) - MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); + pending = (PendingRelOp *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelOp)); pending->relnode = rel->rd_node; pending->backend = rel->rd_backend; pending->atCommit = true; /* delete if commit */ @@ -192,9 +216,9 @@ RelationDropStorage(Relation rel) void RelationPreserveStorage(RelFileNode rnode, bool atCommit) { - PendingRelDelete *pending; - PendingRelDelete *prev; - PendingRelDelete *next; + PendingRelOp *pending; + PendingRelOp *prev; + PendingRelOp *next; prev = NULL; for (pending = pendingDeletes; pending != NULL; pending = next) @@ -399,9 +423,9 @@ void smgrDoPendingDeletes(bool isCommit) { int nestLevel = GetCurrentTransactionNestLevel(); - PendingRelDelete *pending; - PendingRelDelete *prev; - PendingRelDelete *next; + PendingRelOp *pending; + PendingRelOp *prev; + PendingRelOp *next; int nrels = 0, i = 0, maxrels = 0; @@ -462,11 +486,195 @@ smgrDoPendingDeletes(bool isCommit) } /* - * smgrGetPendingDeletes() -- Get a list of non-temp relations to be deleted. + * smgrDoPendingSyncs() -- Take care of relation syncs at end of xact. * - * The return value is the number of relations scheduled for termination. - * *ptr is set to point to a freshly-palloc'd array of RelFileNodes. - * If there are no relations to be deleted, *ptr is set to NULL. + * This should be called before smgrDoPendingDeletes() at every subtransaction + * end. Also this should be called before emitting WAL record so that sync + * failure prevents commit. + * + * If sync_all is true, syncs all files including that are scheduled to be + * deleted. + */ +void +smgrDoPendingSyncs(bool isCommit, bool sync_all) +{ + int nestLevel = GetCurrentTransactionNestLevel(); + PendingRelOp *pending; + PendingRelOp *prev; + PendingRelOp *next; + SMgrRelation srel = NULL; + ForkNumber fork; + BlockNumber nblocks[MAX_FORKNUM + 1]; + BlockNumber total_blocks = 0; + HTAB *delhash = NULL; + + /* Return if nothing to be synced in this nestlevel */ + if (!pendingSyncs || pendingSyncs->nestLevel < nestLevel) + return; + + Assert (pendingSyncs->nestLevel <= nestLevel); + Assert (pendingSyncs->backend == InvalidBackendId); + + /* + * If sync_all is false, pending syncs on the relation that are to be + * deleted in this transaction-end should be ignored. Collect pending + * deletes that will happen in the following call to + * smgrDoPendingDeletes(). + */ + if (!sync_all) + { + for (pending = pendingDeletes; pending != NULL; pending = pending->next) + { + bool found PG_USED_FOR_ASSERTS_ONLY; + + if (pending->nestLevel < pendingSyncs->nestLevel || + pending->atCommit != isCommit) + continue; + + /* create the hash if not yet */ + if (delhash == NULL) + { + HASHCTL hash_ctl; + + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(RelFileNode); + hash_ctl.entrysize = sizeof(RelFileNode); + hash_ctl.hcxt = CurrentMemoryContext; + delhash = + hash_create("pending del temporary hash", 8, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + } + + (void) hash_search(delhash, (void *) &(pending->relnode), + HASH_ENTER, &found); + Assert(!found); + } + } + + /* Loop over pendingSyncs */ + prev = NULL; + for (pending = pendingSyncs; pending != NULL; pending = next) + { + bool to_be_removed = (!isCommit); /* don't sync if aborted */ + + next = pending->next; + + /* outer-level entries should not be processed yet */ + if (pending->nestLevel < nestLevel) + { + prev = pending; + continue; + } + + /* don't sync relnodes that is being deleted */ + if (delhash && !to_be_removed) + hash_search(delhash, (void *) &pending->relnode, + HASH_FIND, &to_be_removed); + + /* remove the entry if no longer useful */ + if (to_be_removed) + { + if (prev) + prev->next = next; + else + pendingSyncs = next; + pfree(pending); + continue; + } + + /* actual sync happens at the end of top transaction */ + if (nestLevel > 1) + { + prev = pending; + continue; + } + + /* Now the time to sync the rnode */ + srel = smgropen(pendingSyncs->relnode, pendingSyncs->backend); + + /* + * We emit newpage WAL records for smaller size of relations. + * + * Small WAL records have a chance to be emitted at once along with + * other backends' WAL records. We emit WAL records instead of syncing + * for files that are smaller than a certain threshold expecting + * faster commit. The threshold is defined by the GUC + * effective_io_block_size. + */ + for (fork = 0 ; fork <= MAX_FORKNUM ; fork++) + { + /* FSM doesn't need WAL nor sync */ + if (fork != FSM_FORKNUM && smgrexists(srel, fork)) + { + BlockNumber n = smgrnblocks(srel, fork); + + /* we shouldn't come here for unlogged relations */ + Assert(fork != INIT_FORKNUM); + + nblocks[fork] = n; + total_blocks += n; + } + else + nblocks[fork] = InvalidBlockNumber; + } + + /* + * Sync file or emit WAL record for the file according to the total + * size. + */ + if (total_blocks * BLCKSZ >= effective_io_block_size * 1024) + { + /* Flush all buffers then sync the file */ + FlushRelationBuffersWithoutRelcache(srel->smgr_rnode.node, false); + + for (fork = 0; fork <= MAX_FORKNUM; fork++) + { + if (smgrexists(srel, fork)) + smgrimmedsync(srel, fork); + } + } + else + { + /* + * Emit WAL records for all blocks. Some of the blocks might have + * been synced or evicted, but We don't bother checking that. The + * file is small enough. + */ + for (fork = 0 ; fork <= MAX_FORKNUM ; fork++) + { + bool page_std = (fork == MAIN_FORKNUM); + int n = nblocks[fork]; + Relation rel; + + if (!BlockNumberIsValid(n)) + continue; + + /* Emit WAL for the whole file */ + rel = CreateFakeRelcacheEntry(srel->smgr_rnode.node); + log_newpage_range(rel, fork, 0, n, page_std); + FreeFakeRelcacheEntry(rel); + } + } + + /* done remove from list */ + if (prev) + prev->next = next; + else + pendingSyncs = next; + pfree(pending); + } + + if (delhash) + hash_destroy(delhash); +} + +/* + * smgrGetPendingOperations() -- Get a list of non-temp relations to be + * deleted or synced. + * + * The return value is the number of relations scheduled in the given + * list. *ptr is set to point to a freshly-palloc'd array of RelFileNodes. If + * there are no matching relations, *ptr is set to NULL. * * Only non-temporary relations are included in the returned list. This is OK * because the list is used only in contexts where temporary relations don't @@ -475,19 +683,19 @@ smgrDoPendingDeletes(bool isCommit) * (and all temporary files will be zapped if we restart anyway, so no need * for redo to do it also). * - * Note that the list does not include anything scheduled for termination - * by upper-level transactions. + * Note that the list does not include anything scheduled by upper-level + * transactions. */ -int -smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) +static inline int +smgrGetPendingOperations(PendingRelOp *list, bool forCommit, RelFileNode **ptr) { int nestLevel = GetCurrentTransactionNestLevel(); int nrels; RelFileNode *rptr; - PendingRelDelete *pending; + PendingRelOp *pending; nrels = 0; - for (pending = pendingDeletes; pending != NULL; pending = pending->next) + for (pending = list; pending != NULL; pending = pending->next) { if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit && pending->backend == InvalidBackendId) @@ -500,7 +708,7 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) } rptr = (RelFileNode *) palloc(nrels * sizeof(RelFileNode)); *ptr = rptr; - for (pending = pendingDeletes; pending != NULL; pending = pending->next) + for (pending = list; pending != NULL; pending = pending->next) { if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit && pending->backend == InvalidBackendId) @@ -512,6 +720,20 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) return nrels; } +/* Returns list of pending deletes, see smgrGetPendingOperations for details */ +int +smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) +{ + return smgrGetPendingOperations(pendingDeletes, forCommit, ptr); +} + +/* Returns list of pending syncs, see smgrGetPendingOperations for details */ +int +smgrGetPendingSyncs(bool forCommit, RelFileNode **ptr) +{ + return smgrGetPendingOperations(pendingSyncs, forCommit, ptr); +} + /* * PostPrepare_smgr -- Clean up after a successful PREPARE * @@ -522,8 +744,8 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) void PostPrepare_smgr(void) { - PendingRelDelete *pending; - PendingRelDelete *next; + PendingRelOp *pending; + PendingRelOp *next; for (pending = pendingDeletes; pending != NULL; pending = next) { @@ -532,25 +754,34 @@ PostPrepare_smgr(void) /* must explicitly free the list entry */ pfree(pending); } + + /* We shouldn't have an entry in pendingSyncs */ + Assert(pendingSyncs == NULL); } /* * AtSubCommit_smgr() --- Take care of subtransaction commit. * - * Reassign all items in the pending-deletes list to the parent transaction. + * Reassign all items in the pending-operations list to the parent transaction. */ void AtSubCommit_smgr(void) { int nestLevel = GetCurrentTransactionNestLevel(); - PendingRelDelete *pending; + PendingRelOp *pending; for (pending = pendingDeletes; pending != NULL; pending = pending->next) { if (pending->nestLevel >= nestLevel) pending->nestLevel = nestLevel - 1; } + + for (pending = pendingSyncs; pending != NULL; pending = pending->next) + { + if (pending->nestLevel >= nestLevel) + pending->nestLevel = nestLevel - 1; + } } /* diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 28985a07ec..f665ee8358 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1034,12 +1034,36 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, if (OidIsValid(relfilenode1) && OidIsValid(relfilenode2)) { + Relation rel1; + Relation rel2; + /* * Normal non-mapped relations: swap relfilenodes, reltablespaces, * relpersistence */ Assert(!target_is_pg_class); + /* Update creation subid hints of relcache */ + rel1 = relation_open(r1, ExclusiveLock); + rel2 = relation_open(r2, ExclusiveLock); + + /* + * New relation's relfilenode is created in the current transaction + * and used as old ralation's new relfilenode. So its + * newRelfilenodeSubid as new relation's createSubid. We don't fix + * rel2 since it would be deleted soon. + */ + Assert(rel2->rd_createSubid != InvalidSubTransactionId); + rel1->rd_newRelfilenodeSubid = rel2->rd_createSubid; + + /* record the first relfilenode change in the current transaction */ + if (rel1->rd_firstRelfilenodeSubid == InvalidSubTransactionId) + rel1->rd_firstRelfilenodeSubid = GetCurrentSubTransactionId(); + + relation_close(rel1, ExclusiveLock); + relation_close(rel2, ExclusiveLock); + + /* swap relfilenodes, reltablespaces, relpersistence */ swaptemp = relform1->relfilenode; relform1->relfilenode = relform2->relfilenode; relform2->relfilenode = swaptemp; diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 3aeef30b28..3ce04f7efc 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2534,9 +2534,6 @@ CopyMultiInsertBufferCleanup(CopyMultiInsertInfo *miinfo, for (i = 0; i < MAX_BUFFERED_TUPLES && buffer->slots[i] != NULL; i++) ExecDropSingleTupleTableSlot(buffer->slots[i]); - table_finish_bulk_insert(buffer->resultRelInfo->ri_RelationDesc, - miinfo->ti_options); - pfree(buffer); } @@ -2725,28 +2722,9 @@ CopyFrom(CopyState cstate) * If it does commit, we'll have done the table_finish_bulk_insert() 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 - * can be cleared before the end of the transaction. The exact case is - * when a relation sets a new relfilenode twice in same transaction, yet - * the second one fails in an aborted subtransaction, e.g. - * - * BEGIN; - * TRUNCATE t; - * SAVEPOINT save; - * TRUNCATE t; - * ROLLBACK TO save; - * COPY ... - * - * Also, if the target file is new-in-transaction, we assume that checking - * FSM for free space is a waste of time, even if we must use WAL because - * of archiving. This could possibly be wrong, but it's unlikely. - * - * The comments for table_tuple_insert and RelationGetBufferForTuple - * specify that skipping WAL logging is only safe if we ensure that our - * tuples do not go into pages containing tuples from any other - * transactions --- but this must be the case if we have a new table or - * new relfilenode, so we need no additional work to enforce that. + * If the target file is new-in-transaction, we assume that checking FSM + * for free space is a waste of time, even if we must use WAL because of + * archiving. This could possibly be wrong, but it's unlikely. * * We currently don't support this optimization if the COPY target is a * partitioned table as we currently only lazily initialize partition @@ -2762,15 +2740,14 @@ CopyFrom(CopyState cstate) * are not supported as per the description above. *---------- */ - /* createSubid is creation check, newRelfilenodeSubid is truncation check */ + /* + * createSubid is creation check, firstRelfilenodeSubid is truncation and + * cluster check. Partitioned table doesn't have storage. + */ if (RELKIND_HAS_STORAGE(cstate->rel->rd_rel->relkind) && (cstate->rel->rd_createSubid != InvalidSubTransactionId || - cstate->rel->rd_newRelfilenodeSubid != InvalidSubTransactionId)) - { + cstate->rel->rd_firstRelfilenodeSubid != InvalidSubTransactionId)) ti_options |= TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; - } /* * Optimize if new relfilenode was created in this subxact or one of its diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index b7d220699f..8a91d946e3 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -558,8 +558,7 @@ 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->ti_options = TABLE_INSERT_SKIP_FSM | - (XLogIsNeeded() ? 0 : TABLE_INSERT_SKIP_WAL); + myState->ti_options = TABLE_INSERT_SKIP_FSM; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -604,8 +603,6 @@ intorel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->rel, myState->ti_options); - /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); myState->rel = NULL; diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 537d0e8cef..1c854dcebf 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -463,8 +463,6 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) * replication is in use. We can skip the FSM in any case. */ myState->ti_options = TABLE_INSERT_SKIP_FSM | TABLE_INSERT_FROZEN; - if (!XLogIsNeeded()) - myState->ti_options |= TABLE_INSERT_SKIP_WAL; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,8 +507,6 @@ transientrel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->transientrel, myState->ti_options); - /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); myState->transientrel = NULL; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index cceefbdd49..2468b178cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4762,9 +4762,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) /* * Prepare a BulkInsertState and options for table_tuple_insert. Because - * we're building a new heap, we can skip WAL-logging and fsync it to disk - * at the end instead (unless WAL-logging is required for archiving or - * streaming replication). The FSM is empty too, so don't bother using it. + * we're building a new heap, the underlying table AM can skip WAL-logging + * and smgr will sync the relation to disk at the end of the current + * transaction instead. The FSM is empty too, so don't bother using it. */ if (newrel) { @@ -4772,8 +4772,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); ti_options = TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; } else { @@ -5058,8 +5056,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { FreeBulkInsertState(bistate); - table_finish_bulk_insert(newrel, ti_options); - table_close(newrel, NoLock); } } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 6f3a402854..55c122b3a7 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -171,6 +171,7 @@ static HTAB *PrivateRefCountHash = NULL; static int32 PrivateRefCountOverflowed = 0; static uint32 PrivateRefCountClock = 0; static PrivateRefCountEntry *ReservedRefCountEntry = NULL; +static void FlushRelationBuffers_common(SMgrRelation smgr, bool islocal); static void ReservePrivateRefCountEntry(void); static PrivateRefCountEntry *NewPrivateRefCountEntry(Buffer buffer); @@ -675,10 +676,10 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, * ReadBufferWithoutRelcache -- like ReadBufferExtended, but doesn't require * a relcache entry for the relation. * - * NB: At present, this function may only be used on permanent relations, which - * is OK, because we only use it during XLOG replay. If in the future we - * want to use it on temporary or unlogged relations, we could pass additional - * parameters. + * NB: At present, this function may only be used on permanent relations, + * which is OK, because we only use it during XLOG replay and processing + * pending syncs. If in the future we want to use it on temporary or unlogged + * relations, we could pass additional parameters. */ Buffer ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum, @@ -3191,20 +3192,32 @@ 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)); +} + +void +FlushRelationBuffersWithoutRelcache(RelFileNode rnode, bool islocal) +{ + FlushRelationBuffers_common(smgropen(rnode, InvalidBackendId), islocal); +} + +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)) { @@ -3221,7 +3234,7 @@ FlushRelationBuffers(Relation rel) PageSetChecksumInplace(localpage, bufHdr->tag.blockNum); - smgrwrite(rel->rd_smgr, + smgrwrite(smgr, bufHdr->tag.forkNum, bufHdr->tag.blockNum, localpage, @@ -3251,18 +3264,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/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 07f3c93d3f..514c6098e6 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -994,6 +994,36 @@ ForgetDatabaseSyncRequests(Oid dbid) RegisterSyncRequest(&tag, SYNC_FILTER_REQUEST, true /* retryOnError */ ); } +/* + * SyncRelationFiles -- sync files of all given relations + * + * This function is assumed to be called only when skipping WAL-logging and + * emits no xlog records. + */ +void +SyncRelationFiles(RelFileNode *syncrels, int nsyncrels) +{ + int i; + + for (i = 0; i < nsyncrels; i++) + { + SMgrRelation srel; + ForkNumber fork; + + /* sync all existing forks of the relation */ + FlushRelationBuffersWithoutRelcache(syncrels[i], false); + srel = smgropen(syncrels[i], InvalidBackendId); + + for (fork = 0; fork <= MAX_FORKNUM; fork++) + { + if (smgrexists(srel, fork)) + smgrimmedsync(srel, fork); + } + + smgrclose(srel); + } +} + /* * DropRelationFiles -- drop files of all given relations */ diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 248860758c..147babb6b5 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -1096,6 +1096,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) relation->rd_isnailed = false; relation->rd_createSubid = InvalidSubTransactionId; relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; switch (relation->rd_rel->relpersistence) { case RELPERSISTENCE_UNLOGGED: @@ -1829,6 +1830,7 @@ formrdesc(const char *relationName, Oid relationReltype, relation->rd_isnailed = true; relation->rd_createSubid = InvalidSubTransactionId; relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; relation->rd_backend = InvalidBackendId; relation->rd_islocaltemp = false; @@ -2094,7 +2096,7 @@ RelationClose(Relation relation) #ifdef RELCACHE_FORCE_RELEASE if (RelationHasReferenceCountZero(relation) && relation->rd_createSubid == InvalidSubTransactionId && - relation->rd_newRelfilenodeSubid == InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId) RelationClearRelation(relation, false); #endif } @@ -2510,8 +2512,8 @@ RelationClearRelation(Relation relation, bool rebuild) * problem. * * When rebuilding an open relcache entry, we must preserve ref count, - * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also - * attempt to preserve the pg_class entry (rd_rel), tupledesc, + * rd_createSubid/rd_new/firstRelfilenodeSubid, and rd_toastoid state. + * Also attempt to preserve the pg_class entry (rd_rel), tupledesc, * rewrite-rule, partition key, and partition descriptor substructures * in place, because various places assume that these structures won't * move while they are working with an open relcache entry. (Note: @@ -2600,6 +2602,7 @@ RelationClearRelation(Relation relation, bool rebuild) /* creation sub-XIDs must be preserved */ SWAPFIELD(SubTransactionId, rd_createSubid); SWAPFIELD(SubTransactionId, rd_newRelfilenodeSubid); + SWAPFIELD(SubTransactionId, rd_firstRelfilenodeSubid); /* un-swap rd_rel pointers, swap contents instead */ SWAPFIELD(Form_pg_class, rd_rel); /* ... but actually, we don't have to update newrel->rd_rel */ @@ -2667,7 +2670,7 @@ static void RelationFlushRelation(Relation relation) { if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) { /* * New relcache entries are always rebuilt, not flushed; else we'd @@ -2807,7 +2810,7 @@ RelationCacheInvalidate(void) * pending invalidations. */ if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) continue; relcacheInvalsReceived++; @@ -3064,6 +3067,7 @@ AtEOXact_cleanup(Relation relation, bool isCommit) * Likewise, reset the hint about the relfilenode being new. */ relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; } /* @@ -3155,7 +3159,7 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, } /* - * Likewise, update or drop any new-relfilenode-in-subtransaction hint. + * Likewise, update or drop any new-relfilenode-in-subtransaction hints. */ if (relation->rd_newRelfilenodeSubid == mySubid) { @@ -3164,6 +3168,14 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, else relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; } + + if (relation->rd_firstRelfilenodeSubid == mySubid) + { + if (isCommit) + relation->rd_firstRelfilenodeSubid = parentSubid; + else + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; + } } @@ -3253,6 +3265,7 @@ RelationBuildLocalRelation(const char *relname, /* it's being created in this transaction */ rel->rd_createSubid = GetCurrentSubTransactionId(); rel->rd_newRelfilenodeSubid = InvalidSubTransactionId; + rel->rd_firstRelfilenodeSubid = InvalidSubTransactionId; /* * create a new tuple descriptor from the one passed in. We do this @@ -3556,6 +3569,8 @@ RelationSetNewRelfilenode(Relation relation, char persistence) * operations on the rel in the same transaction. */ relation->rd_newRelfilenodeSubid = GetCurrentSubTransactionId(); + if (relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid = relation->rd_newRelfilenodeSubid; /* Flag relation as needing eoxact cleanup (to remove the hint) */ EOXactListAdd(relation); @@ -5592,6 +5607,7 @@ load_relcache_init_file(bool shared) rel->rd_fkeylist = NIL; rel->rd_createSubid = InvalidSubTransactionId; rel->rd_newRelfilenodeSubid = InvalidSubTransactionId; + rel->rd_firstRelfilenodeSubid = InvalidSubTransactionId; rel->rd_amcache = NULL; MemSet(&rel->pgstat_info, 0, sizeof(rel->pgstat_info)); diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 90ffd89339..1e4fc256fc 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -36,6 +36,7 @@ #include "access/xlog_internal.h" #include "catalog/namespace.h" #include "catalog/pg_authid.h" +#include "catalog/storage.h" #include "commands/async.h" #include "commands/prepare.h" #include "commands/user.h" @@ -2774,6 +2775,18 @@ static struct config_int ConfigureNamesInt[] = check_effective_io_concurrency, assign_effective_io_concurrency, NULL }, + { + {"effective_io_block_size", PGC_USERSET, RESOURCES_DISK, + gettext_noop("Size of file that can be fsync'ed in the minimum required duration."), + gettext_noop("For rotating magnetic disks, it is around the size of a track or sylinder."), + GUC_UNIT_KB + }, + &effective_io_block_size, + 64, + 0, MAX_KILOBYTES, + NULL, NULL, NULL + }, + { {"backend_flush_after", PGC_USERSET, RESOURCES_ASYNCHRONOUS, gettext_noop("Number of pages after which previously performed writes are flushed to disk."), diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 858bcb6bc9..80c2e1bafc 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -29,7 +29,6 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL #define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM #define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN #define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 8056253916..7f9736e294 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -23,7 +23,7 @@ typedef struct RewriteStateData *RewriteState; extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap, TransactionId OldestXmin, TransactionId FreezeXid, - MultiXactId MultiXactCutoff, bool use_wal); + MultiXactId MultiXactCutoff); extern void end_heap_rewrite(RewriteState state); extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple, HeapTuple newTuple); diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 7f81703b78..b652cd6cef 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -407,22 +407,6 @@ typedef struct TableAmRoutine uint8 flags, TM_FailureData *tmfd); - /* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * may for example be used to flush the relation, when the - * TABLE_INSERT_SKIP_WAL option was used. - * - * Typically callers of tuple_insert and multi_insert will just pass all - * the flags that apply to them, and each AM has to decide which of them - * make sense for it, and then only take actions in finish_bulk_insert for - * those flags, and ignore others. - * - * Optional callback. - */ - void (*finish_bulk_insert) (Relation rel, int options); - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ @@ -1087,10 +1071,6 @@ table_compute_xid_horizon_for_tuples(Relation rel, * The options bitmask allows the caller to specify options that may change the * behaviour of the AM. The AM will ignore options that it does not support. * - * If the TABLE_INSERT_SKIP_WAL option is specified, the new tuple doesn't - * need to be logged to WAL, even for a non-temp relation. It is the AMs - * choice whether this optimization is supported. - * * If the TABLE_INSERT_SKIP_FSM option is specified, AMs are free to not reuse * free space in the relation. This can save some cycles when we know the * relation is new and doesn't contain useful amounts of free space. @@ -1112,8 +1092,7 @@ table_compute_xid_horizon_for_tuples(Relation rel, * heap's TOAST table, too, if the tuple requires any out-of-line data. * * The BulkInsertState object (if any; bistate can be NULL for default - * behavior) is also just passed through to RelationGetBufferForTuple. If - * `bistate` is provided, table_finish_bulk_insert() needs to be called. + * behavior) is also just passed through to RelationGetBufferForTuple. * * On return the slot's tts_tid and tts_tableOid are updated to reflect the * insertion. But note that any toasting of fields within the slot is NOT @@ -1248,6 +1227,8 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, * update was done. However, any TOAST changes in the new tuple's * data are not reflected into *newtup. * + * See table_insert about skipping WAL-logging feature. + * * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, * t_xmax, and, if possible, t_cmax. See comments for struct TM_FailureData * for additional info. @@ -1308,21 +1289,6 @@ table_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, flags, tmfd); } -/* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * e.g. may e.g. used to flush the relation when inserting with - * TABLE_INSERT_SKIP_WAL specified. - */ -static inline void -table_finish_bulk_insert(Relation rel, int options) -{ - /* optional callback */ - if (rel->rd_tableam && rel->rd_tableam->finish_bulk_insert) - rel->rd_tableam->finish_bulk_insert(rel, options); -} - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h index 3579d3f3eb..1c1cf5d252 100644 --- a/src/include/catalog/storage.h +++ b/src/include/catalog/storage.h @@ -19,6 +19,16 @@ #include "storage/smgr.h" #include "utils/relcache.h" +/* enum for operation type of PendingDelete entries */ +typedef enum PendingOpType +{ + PENDING_DELETE, + PENDING_SYNC +} PendingOpType; + +/* GUC variables */ +extern int effective_io_block_size; /* threshold for WAL-skipping */ + extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence); extern void RelationDropStorage(Relation rel); extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit); @@ -31,7 +41,9 @@ extern void RelationCopyStorage(SMgrRelation src, SMgrRelation dst, * naming */ extern void smgrDoPendingDeletes(bool isCommit); +extern void smgrDoPendingSyncs(bool isCommit, bool sync_all); extern int smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr); +extern int smgrGetPendingSyncs(bool forCommit, RelFileNode **ptr); 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 509f4b7ef1..ace5f5a2ae 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -189,6 +189,7 @@ 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/storage/md.h b/src/include/storage/md.h index c0f05e23ff..2bb2947bdb 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -42,6 +42,7 @@ extern void mdtruncate(SMgrRelation reln, ForkNumber forknum, extern void mdimmedsync(SMgrRelation reln, ForkNumber forknum); extern void ForgetDatabaseSyncRequests(Oid dbid); +extern void SyncRelationFiles(RelFileNode *syncrels, int nsyncrels); extern void DropRelationFiles(RelFileNode *delrels, int ndelrels, bool isRedo); /* md sync callbacks */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index c5d36680a2..f372dc2086 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -75,10 +75,17 @@ typedef struct RelationData * transaction, with one of them occurring in a subsequently aborted * subtransaction, e.g. BEGIN; TRUNCATE t; SAVEPOINT save; TRUNCATE t; * ROLLBACK TO save; -- rd_newRelfilenodeSubid is now forgotten + * rd_firstRelfilenodeSubid is the ID of the first subtransaction the + * relfilenode change has took place in the current transaction. Unlike + * newRelfilenodeSubid, this won't be accidentially forgotten. A valid OID + * means that the currently active relfilenode is transaction-local and we + * sync the relation at commit instead of WAL-logging. */ SubTransactionId rd_createSubid; /* rel was created in current xact */ SubTransactionId rd_newRelfilenodeSubid; /* new relfilenode assigned in * current xact */ + SubTransactionId rd_firstRelfilenodeSubid; /* new relfilenode assigned + * first in current xact */ Form_pg_class rd_rel; /* RELATION tuple */ TupleDesc rd_att; /* tuple descriptor */ @@ -514,9 +521,15 @@ typedef struct ViewOptions /* * RelationNeedsWAL * True if relation needs WAL. + * + * Returns false if wal_level = minimal and this relation is created or + * truncated in the current transaction. */ -#define RelationNeedsWAL(relation) \ - ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) +#define RelationNeedsWAL(relation) \ + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT && \ + (XLogIsNeeded() || \ + (relation->rd_createSubid == InvalidSubTransactionId && \ + relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId))) /* * RelationUsesLocalBuffers -- 2.16.3 ----Next_Part(Wed_Aug_28_15_42_10_2019_037)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v20-0003-Documentation-for-effective_io_block_size.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH v23 2/5] Fix WAL skipping feature @ 2019-10-25 03:07 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2019-10-25 03:07 UTC (permalink / raw) WAL-skipping operations mixed with WAL-logged operations can lead to database corruption after a crash. This patch changes the WAL-skipping feature so that no data modification is WAL-logged at all then sync such relations at commit. --- src/backend/access/heap/heapam.c | 4 +- src/backend/access/heap/heapam_handler.c | 22 +- src/backend/access/heap/rewriteheap.c | 13 +- src/backend/access/transam/xact.c | 17 ++ src/backend/access/transam/xlogutils.c | 11 +- src/backend/catalog/storage.c | 294 ++++++++++++++++++++--- src/backend/commands/cluster.c | 28 +++ src/backend/commands/copy.c | 39 +-- src/backend/commands/createas.c | 5 +- src/backend/commands/matview.c | 4 - src/backend/commands/tablecmds.c | 10 +- src/backend/storage/buffer/bufmgr.c | 41 ++-- src/backend/storage/smgr/md.c | 30 +++ src/backend/utils/cache/relcache.c | 28 ++- src/backend/utils/misc/guc.c | 13 + src/include/access/heapam.h | 1 - src/include/access/rewriteheap.h | 2 +- src/include/access/tableam.h | 40 +-- src/include/catalog/storage.h | 12 + src/include/storage/bufmgr.h | 1 + src/include/storage/md.h | 1 + src/include/utils/rel.h | 52 +++- 22 files changed, 483 insertions(+), 185 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 0128bb34ef..a7ead9405a 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -1936,7 +1936,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (RelationNeedsWAL(relation)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2119,7 +2119,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); + needwal = RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 2dd8821fac..0871df7730 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -558,18 +558,6 @@ tuple_lock_retry: return result; } -static void -heapam_finish_bulk_insert(Relation relation, int options) -{ - /* - * If we skipped writing WAL, then we need to sync the heap (but not - * indexes since those use WAL anyway / don't go through tableam) - */ - if (options & HEAP_INSERT_SKIP_WAL) - heap_sync(relation); -} - - /* ------------------------------------------------------------------------ * DDL related callbacks for heap AM. * ------------------------------------------------------------------------ @@ -701,7 +689,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, IndexScanDesc indexScan; TableScanDesc tableScan; HeapScanDesc heapScan; - bool use_wal; bool is_system_catalog; Tuplesortstate *tuplesort; TupleDesc oldTupDesc = RelationGetDescr(OldHeap); @@ -716,12 +703,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, is_system_catalog = IsSystemRelation(OldHeap); /* - * We need to log the copied data in WAL iff WAL archiving/streaming is - * enabled AND it's a WAL-logged rel. + * smgr_targblock must be initially invalid if we are to skip WAL logging */ - use_wal = XLogIsNeeded() && RelationNeedsWAL(NewHeap); - - /* use_wal off requires smgr_targblock be initially invalid */ Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber); /* Preallocate values/isnull arrays */ @@ -731,7 +714,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Initialize the rewrite operation */ rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff, - *multi_cutoff, use_wal); + *multi_cutoff); /* Set up sorting if wanted */ @@ -2519,7 +2502,6 @@ static const TableAmRoutine heapam_methods = { .tuple_delete = heapam_tuple_delete, .tuple_update = heapam_tuple_update, .tuple_lock = heapam_tuple_lock, - .finish_bulk_insert = heapam_finish_bulk_insert, .tuple_fetch_row_version = heapam_fetch_row_version, .tuple_get_latest_tid = heap_get_latest_tid, diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index d41dbcf5f7..9b757cacf4 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -144,7 +144,6 @@ typedef struct RewriteStateData Page rs_buffer; /* page currently being built */ BlockNumber rs_blockno; /* block where page will go */ bool rs_buffer_valid; /* T if any tuples in buffer */ - bool rs_use_wal; /* must we WAL-log inserts? */ bool rs_logical_rewrite; /* do we need to do logical rewriting */ TransactionId rs_oldest_xmin; /* oldest xmin used by caller to determine * tuple visibility */ @@ -238,15 +237,13 @@ static void logical_end_heap_rewrite(RewriteState state); * oldest_xmin xid used by the caller to determine which tuples are dead * freeze_xid xid before which tuples will be frozen * cutoff_multi multixact before which multis will be removed - * use_wal should the inserts to the new heap be WAL-logged? * * Returns an opaque RewriteState, allocated in current memory context, * to be used in subsequent calls to the other functions. */ RewriteState begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xmin, - TransactionId freeze_xid, MultiXactId cutoff_multi, - bool use_wal) + TransactionId freeze_xid, MultiXactId cutoff_multi) { RewriteState state; MemoryContext rw_cxt; @@ -271,7 +268,6 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm /* new_heap needn't be empty, just locked */ state->rs_blockno = RelationGetNumberOfBlocks(new_heap); state->rs_buffer_valid = false; - state->rs_use_wal = use_wal; state->rs_oldest_xmin = oldest_xmin; state->rs_freeze_xid = freeze_xid; state->rs_cutoff_multi = cutoff_multi; @@ -330,7 +326,7 @@ end_heap_rewrite(RewriteState state) /* Write the last page, if any */ if (state->rs_buffer_valid) { - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, @@ -654,9 +650,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is @@ -695,7 +688,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup) /* Doesn't fit, so write out the existing page */ /* XLOG stuff */ - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index fc55fa6d53..59d65bc214 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -2107,6 +2107,13 @@ CommitTransaction(void) */ PreCommit_on_commit_actions(); + /* + * Synchronize files that are created and not WAL-logged during this + * transaction. This must happen before emitting commit record so that we + * don't see committed-but-broken files after a crash. + */ + smgrDoPendingSyncs(true, false); + /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); @@ -2339,6 +2346,14 @@ PrepareTransaction(void) */ PreCommit_on_commit_actions(); + /* + * Sync all WAL-skipped files now. Some of them may be deleted at + * transaction end but we don't bother store that information in PREPARE + * record or two-phase files. Like commit, we should sync WAL-skipped + * files before emitting PREPARE record. See CommitTransaction(). + */ + smgrDoPendingSyncs(true, true); + /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); @@ -2657,6 +2672,7 @@ AbortTransaction(void) */ AfterTriggerEndXact(false); /* 'false' means it's abort */ AtAbort_Portals(); + smgrDoPendingSyncs(false, false); AtEOXact_LargeObject(false); AtAbort_Notify(); AtEOXact_RelationMap(false, is_parallel_worker); @@ -4964,6 +4980,7 @@ AbortSubTransaction(void) s->parent->curTransactionOwner); AtEOSubXact_LargeObject(false, s->subTransactionId, s->parent->subTransactionId); + smgrDoPendingSyncs(false, false); AtSubAbort_Notify(); /* Advertise the fact that we aborted in pg_xact. */ diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 5f1e5ba75d..e566f01eef 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -544,6 +544,8 @@ typedef FakeRelCacheEntryData *FakeRelCacheEntry; * fields related to physical storage, like rd_rel, are initialized, so the * fake entry is only usable in low-level operations like ReadBuffer(). * + * This is also used for syncing WAL-skipped files. + * * Caller must free the returned entry with FreeFakeRelcacheEntry(). */ Relation @@ -552,18 +554,19 @@ CreateFakeRelcacheEntry(RelFileNode rnode) FakeRelCacheEntry fakeentry; Relation rel; - Assert(InRecovery); - /* Allocate the Relation struct and all related space in one block. */ fakeentry = palloc0(sizeof(FakeRelCacheEntryData)); rel = (Relation) fakeentry; rel->rd_rel = &fakeentry->pgc; rel->rd_node = rnode; - /* We will never be working with temp rels during recovery */ + /* + * We will never be working with temp rels during recovery or syncing + * WAL-skipped files. + */ rel->rd_backend = InvalidBackendId; - /* It must be a permanent table if we're in recovery. */ + /* It must be a permanent table here */ rel->rd_rel->relpersistence = RELPERSISTENCE_PERMANENT; /* We don't know the name of the relation; use relfilenode instead */ diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index 625af8d49a..806f235a24 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -30,9 +30,13 @@ #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" +/* GUC variables */ +int wal_skip_threshold = 64; /* threshold of WAL-skipping in kilobytes */ + /* * We keep a list of all relations (represented as RelFileNode values) * that have been created or deleted in the current transaction. When @@ -53,16 +57,17 @@ * but I'm being paranoid. */ -typedef struct PendingRelDelete +typedef struct PendingRelOp { RelFileNode relnode; /* relation that may need to be deleted */ BackendId backend; /* InvalidBackendId if not a temp rel */ - bool atCommit; /* T=delete at commit; F=delete at abort */ + bool atCommit; /* T=work at commit; F=work at abort */ int nestLevel; /* xact nesting level of request */ - struct PendingRelDelete *next; /* linked-list link */ -} PendingRelDelete; + struct PendingRelOp *next; /* linked-list link */ +} PendingRelOp; -static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */ +static PendingRelOp *pendingDeletes = NULL; /* head of linked list */ +static PendingRelOp *pendingSyncs = NULL; /* head of linked list */ /* * RelationCreateStorage @@ -78,7 +83,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */ SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence) { - PendingRelDelete *pending; + PendingRelOp *pending; SMgrRelation srel; BackendId backend; bool needs_wal; @@ -109,8 +114,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) log_smgrcreate(&srel->smgr_rnode.node, MAIN_FORKNUM); /* Add the relation to the list of stuff to delete at abort */ - pending = (PendingRelDelete *) - MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); + pending = (PendingRelOp *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelOp)); pending->relnode = rnode; pending->backend = backend; pending->atCommit = false; /* delete if abort */ @@ -118,6 +123,25 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) pending->next = pendingDeletes; pendingDeletes = pending; + /* + * When wal_level = minimal, we are going to skip WAL-logging for storage + * of persistent relations created in the current transaction. The + * relation needs to be synced at commit. + */ + if (relpersistence == RELPERSISTENCE_PERMANENT && !XLogIsNeeded()) + { + int nestLevel = GetCurrentTransactionNestLevel(); + + pending = (PendingRelOp *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelOp)); + pending->relnode = rnode; + pending->backend = backend; + pending->atCommit = true; + pending->nestLevel = nestLevel; + pending->next = pendingSyncs; + pendingSyncs = pending; + } + return srel; } @@ -147,11 +171,11 @@ log_smgrcreate(const RelFileNode *rnode, ForkNumber forkNum) void RelationDropStorage(Relation rel) { - PendingRelDelete *pending; + PendingRelOp *pending; /* Add the relation to the list of stuff to delete at commit */ - pending = (PendingRelDelete *) - MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); + pending = (PendingRelOp *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelOp)); pending->relnode = rel->rd_node; pending->backend = rel->rd_backend; pending->atCommit = true; /* delete if commit */ @@ -192,9 +216,9 @@ RelationDropStorage(Relation rel) void RelationPreserveStorage(RelFileNode rnode, bool atCommit) { - PendingRelDelete *pending; - PendingRelDelete *prev; - PendingRelDelete *next; + PendingRelOp *pending; + PendingRelOp *prev; + PendingRelOp *next; prev = NULL; for (pending = pendingDeletes; pending != NULL; pending = next) @@ -431,9 +455,9 @@ void smgrDoPendingDeletes(bool isCommit) { int nestLevel = GetCurrentTransactionNestLevel(); - PendingRelDelete *pending; - PendingRelDelete *prev; - PendingRelDelete *next; + PendingRelOp *pending; + PendingRelOp *prev; + PendingRelOp *next; int nrels = 0, i = 0, maxrels = 0; @@ -494,11 +518,194 @@ smgrDoPendingDeletes(bool isCommit) } /* - * smgrGetPendingDeletes() -- Get a list of non-temp relations to be deleted. + * smgrDoPendingSyncs() -- Take care of relation syncs at end of xact. + * + * This should be called before smgrDoPendingDeletes() at every subtransaction + * end. Also this should be called before emitting WAL record so that sync + * failure prevents commit. + * + * If sync_all is true, syncs all files including that are scheduled to be + * deleted. + */ +void +smgrDoPendingSyncs(bool isCommit, bool sync_all) +{ + int nestLevel = GetCurrentTransactionNestLevel(); + PendingRelOp *pending; + PendingRelOp *prev; + PendingRelOp *next; + SMgrRelation srel = NULL; + ForkNumber fork; + BlockNumber nblocks[MAX_FORKNUM + 1]; + BlockNumber total_blocks = 0; + HTAB *delhash = NULL; + + /* Return if nothing to be synced in this nestlevel */ + if (!pendingSyncs || pendingSyncs->nestLevel < nestLevel) + return; + + Assert (pendingSyncs->nestLevel <= nestLevel); + Assert (pendingSyncs->backend == InvalidBackendId); + + /* + * If sync_all is false, pending syncs on the relation that are to be + * deleted in this transaction-end should be ignored. Collect pending + * deletes that will happen in the following call to + * smgrDoPendingDeletes(). + */ + if (!sync_all) + { + for (pending = pendingDeletes; pending != NULL; pending = pending->next) + { + bool found PG_USED_FOR_ASSERTS_ONLY; + + if (pending->nestLevel < pendingSyncs->nestLevel || + pending->atCommit != isCommit) + continue; + + /* create the hash if not yet */ + if (delhash == NULL) + { + HASHCTL hash_ctl; + + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(RelFileNode); + hash_ctl.entrysize = sizeof(RelFileNode); + hash_ctl.hcxt = CurrentMemoryContext; + delhash = + hash_create("pending del temporary hash", 8, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + } + + (void) hash_search(delhash, (void *) &(pending->relnode), + HASH_ENTER, &found); + Assert(!found); + } + } + + /* Loop over pendingSyncs */ + prev = NULL; + for (pending = pendingSyncs; pending != NULL; pending = next) + { + bool to_be_removed = (!isCommit); /* don't sync if aborted */ + + next = pending->next; + + /* outer-level entries should not be processed yet */ + if (pending->nestLevel < nestLevel) + { + prev = pending; + continue; + } + + /* don't sync relnodes that is being deleted */ + if (delhash && !to_be_removed) + hash_search(delhash, (void *) &pending->relnode, + HASH_FIND, &to_be_removed); + + /* remove the entry if no longer useful */ + if (to_be_removed) + { + if (prev) + prev->next = next; + else + pendingSyncs = next; + pfree(pending); + continue; + } + + /* actual sync happens at the end of top transaction */ + if (nestLevel > 1) + { + prev = pending; + continue; + } + + /* Now the time to sync the rnode */ + srel = smgropen(pendingSyncs->relnode, pendingSyncs->backend); + + /* + * We emit newpage WAL records for smaller size of relations. + * + * Small WAL records have a chance to be emitted at once along with + * other backends' WAL records. We emit WAL records instead of syncing + * for files that are smaller than a certain threshold expecting faster + * commit. The threshold is defined by the GUC wal_skip_threshold. + */ + for (fork = 0 ; fork <= MAX_FORKNUM ; fork++) + { + /* FSM doesn't need WAL nor sync */ + if (fork != FSM_FORKNUM && smgrexists(srel, fork)) + { + BlockNumber n = smgrnblocks(srel, fork); + + /* we shouldn't come here for unlogged relations */ + Assert(fork != INIT_FORKNUM); + + nblocks[fork] = n; + total_blocks += n; + } + else + nblocks[fork] = InvalidBlockNumber; + } + + /* + * Sync file or emit WAL record for the file according to the total + * size. + */ + if (total_blocks * BLCKSZ >= wal_skip_threshold * 1024) + { + /* Flush all buffers then sync the file */ + FlushRelationBuffersWithoutRelcache(srel->smgr_rnode.node, false); + + for (fork = 0; fork <= MAX_FORKNUM; fork++) + { + if (smgrexists(srel, fork)) + smgrimmedsync(srel, fork); + } + } + else + { + /* + * Emit WAL records for all blocks. Some of the blocks might have + * been synced or evicted, but We don't bother checking that. The + * file is small enough. + */ + for (fork = 0 ; fork <= MAX_FORKNUM ; fork++) + { + bool page_std = (fork == MAIN_FORKNUM); + int n = nblocks[fork]; + Relation rel; + + if (!BlockNumberIsValid(n)) + continue; + + /* Emit WAL for the whole file */ + rel = CreateFakeRelcacheEntry(srel->smgr_rnode.node); + log_newpage_range(rel, fork, 0, n, page_std); + FreeFakeRelcacheEntry(rel); + } + } + + /* done remove from list */ + if (prev) + prev->next = next; + else + pendingSyncs = next; + pfree(pending); + } + + if (delhash) + hash_destroy(delhash); +} + +/* + * smgrGetPendingOperations() -- Get a list of non-temp relations to be + * deleted or synced. * - * The return value is the number of relations scheduled for termination. - * *ptr is set to point to a freshly-palloc'd array of RelFileNodes. - * If there are no relations to be deleted, *ptr is set to NULL. + * The return value is the number of relations scheduled in the given + * list. *ptr is set to point to a freshly-palloc'd array of RelFileNodes. If + * there are no matching relations, *ptr is set to NULL. * * Only non-temporary relations are included in the returned list. This is OK * because the list is used only in contexts where temporary relations don't @@ -507,19 +714,19 @@ smgrDoPendingDeletes(bool isCommit) * (and all temporary files will be zapped if we restart anyway, so no need * for redo to do it also). * - * Note that the list does not include anything scheduled for termination - * by upper-level transactions. + * Note that the list does not include anything scheduled by upper-level + * transactions. */ -int -smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) +static inline int +smgrGetPendingOperations(PendingRelOp *list, bool forCommit, RelFileNode **ptr) { int nestLevel = GetCurrentTransactionNestLevel(); int nrels; RelFileNode *rptr; - PendingRelDelete *pending; + PendingRelOp *pending; nrels = 0; - for (pending = pendingDeletes; pending != NULL; pending = pending->next) + for (pending = list; pending != NULL; pending = pending->next) { if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit && pending->backend == InvalidBackendId) @@ -532,7 +739,7 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) } rptr = (RelFileNode *) palloc(nrels * sizeof(RelFileNode)); *ptr = rptr; - for (pending = pendingDeletes; pending != NULL; pending = pending->next) + for (pending = list; pending != NULL; pending = pending->next) { if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit && pending->backend == InvalidBackendId) @@ -544,6 +751,20 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) return nrels; } +/* Returns list of pending deletes, see smgrGetPendingOperations for details */ +int +smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) +{ + return smgrGetPendingOperations(pendingDeletes, forCommit, ptr); +} + +/* Returns list of pending syncs, see smgrGetPendingOperations for details */ +int +smgrGetPendingSyncs(bool forCommit, RelFileNode **ptr) +{ + return smgrGetPendingOperations(pendingSyncs, forCommit, ptr); +} + /* * PostPrepare_smgr -- Clean up after a successful PREPARE * @@ -554,8 +775,8 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) void PostPrepare_smgr(void) { - PendingRelDelete *pending; - PendingRelDelete *next; + PendingRelOp *pending; + PendingRelOp *next; for (pending = pendingDeletes; pending != NULL; pending = next) { @@ -564,25 +785,34 @@ PostPrepare_smgr(void) /* must explicitly free the list entry */ pfree(pending); } + + /* We shouldn't have an entry in pendingSyncs */ + Assert(pendingSyncs == NULL); } /* * AtSubCommit_smgr() --- Take care of subtransaction commit. * - * Reassign all items in the pending-deletes list to the parent transaction. + * Reassign all items in the pending-operations list to the parent transaction. */ void AtSubCommit_smgr(void) { int nestLevel = GetCurrentTransactionNestLevel(); - PendingRelDelete *pending; + PendingRelOp *pending; for (pending = pendingDeletes; pending != NULL; pending = pending->next) { if (pending->nestLevel >= nestLevel) pending->nestLevel = nestLevel - 1; } + + for (pending = pendingSyncs; pending != NULL; pending = pending->next) + { + if (pending->nestLevel >= nestLevel) + pending->nestLevel = nestLevel - 1; + } } /* diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index a23128d7a0..3559d11eb7 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1034,12 +1034,40 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, if (OidIsValid(relfilenode1) && OidIsValid(relfilenode2)) { + Relation rel1; + Relation rel2; + /* * Normal non-mapped relations: swap relfilenodes, reltablespaces, * relpersistence */ Assert(!target_is_pg_class); + /* + * Update creation subid hints of relcache. Although we don't need for + * additional lock, we must use AccessShareLock here since caller may + * omit locks on relations that cannot be concurrently accessed. + */ + rel1 = relation_open(r1, AccessShareLock); + rel2 = relation_open(r2, AccessShareLock); + + /* + * New relation's relfilenode is created in the current transaction + * and used as old ralation's new relfilenode. So its + * newRelfilenodeSubid as new relation's createSubid. We don't fix + * rel2 since it would be deleted soon. + */ + Assert(rel2->rd_createSubid != InvalidSubTransactionId); + rel1->rd_newRelfilenodeSubid = rel2->rd_createSubid; + + /* record the first relfilenode change in the current transaction */ + if (rel1->rd_firstRelfilenodeSubid == InvalidSubTransactionId) + rel1->rd_firstRelfilenodeSubid = GetCurrentSubTransactionId(); + + relation_close(rel1, AccessShareLock); + relation_close(rel2, AccessShareLock); + + /* swap relfilenodes, reltablespaces, relpersistence */ swaptemp = relform1->relfilenode; relform1->relfilenode = relform2->relfilenode; relform2->relfilenode = swaptemp; diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index e17d8c760f..e6abc11e4c 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2532,9 +2532,6 @@ CopyMultiInsertBufferCleanup(CopyMultiInsertInfo *miinfo, for (i = 0; i < MAX_BUFFERED_TUPLES && buffer->slots[i] != NULL; i++) ExecDropSingleTupleTableSlot(buffer->slots[i]); - table_finish_bulk_insert(buffer->resultRelInfo->ri_RelationDesc, - miinfo->ti_options); - pfree(buffer); } @@ -2723,28 +2720,9 @@ CopyFrom(CopyState cstate) * If it does commit, we'll have done the table_finish_bulk_insert() 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 - * can be cleared before the end of the transaction. The exact case is - * when a relation sets a new relfilenode twice in same transaction, yet - * the second one fails in an aborted subtransaction, e.g. - * - * BEGIN; - * TRUNCATE t; - * SAVEPOINT save; - * TRUNCATE t; - * ROLLBACK TO save; - * COPY ... - * - * Also, if the target file is new-in-transaction, we assume that checking - * FSM for free space is a waste of time, even if we must use WAL because - * of archiving. This could possibly be wrong, but it's unlikely. - * - * The comments for table_tuple_insert and RelationGetBufferForTuple - * specify that skipping WAL logging is only safe if we ensure that our - * tuples do not go into pages containing tuples from any other - * transactions --- but this must be the case if we have a new table or - * new relfilenode, so we need no additional work to enforce that. + * If the target file is new-in-transaction, we assume that checking FSM + * for free space is a waste of time, even if we must use WAL because of + * archiving. This could possibly be wrong, but it's unlikely. * * We currently don't support this optimization if the COPY target is a * partitioned table as we currently only lazily initialize partition @@ -2760,15 +2738,14 @@ CopyFrom(CopyState cstate) * are not supported as per the description above. *---------- */ - /* createSubid is creation check, newRelfilenodeSubid is truncation check */ + /* + * createSubid is creation check, firstRelfilenodeSubid is truncation and + * cluster check. Partitioned table doesn't have storage. + */ if (RELKIND_HAS_STORAGE(cstate->rel->rd_rel->relkind) && (cstate->rel->rd_createSubid != InvalidSubTransactionId || - cstate->rel->rd_newRelfilenodeSubid != InvalidSubTransactionId)) - { + cstate->rel->rd_firstRelfilenodeSubid != InvalidSubTransactionId)) ti_options |= TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; - } /* * Optimize if new relfilenode was created in this subxact or one of its diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index b7d220699f..8a91d946e3 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -558,8 +558,7 @@ 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->ti_options = TABLE_INSERT_SKIP_FSM | - (XLogIsNeeded() ? 0 : TABLE_INSERT_SKIP_WAL); + myState->ti_options = TABLE_INSERT_SKIP_FSM; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -604,8 +603,6 @@ intorel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->rel, myState->ti_options); - /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); myState->rel = NULL; diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 537d0e8cef..1c854dcebf 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -463,8 +463,6 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) * replication is in use. We can skip the FSM in any case. */ myState->ti_options = TABLE_INSERT_SKIP_FSM | TABLE_INSERT_FROZEN; - if (!XLogIsNeeded()) - myState->ti_options |= TABLE_INSERT_SKIP_WAL; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,8 +507,6 @@ transientrel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->transientrel, myState->ti_options); - /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); myState->transientrel = NULL; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 5597be6e3d..3ec218aca4 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4764,9 +4764,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) /* * Prepare a BulkInsertState and options for table_tuple_insert. Because - * we're building a new heap, we can skip WAL-logging and fsync it to disk - * at the end instead (unless WAL-logging is required for archiving or - * streaming replication). The FSM is empty too, so don't bother using it. + * we're building a new heap, the underlying table AM can skip WAL-logging + * and smgr will sync the relation to disk at the end of the current + * transaction instead. The FSM is empty too, so don't bother using it. */ if (newrel) { @@ -4774,8 +4774,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); ti_options = TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; } else { @@ -5070,8 +5068,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { FreeBulkInsertState(bistate); - table_finish_bulk_insert(newrel, ti_options); - table_close(newrel, NoLock); } } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 7ad10736d5..1d9438ad56 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -171,6 +171,7 @@ static HTAB *PrivateRefCountHash = NULL; static int32 PrivateRefCountOverflowed = 0; static uint32 PrivateRefCountClock = 0; static PrivateRefCountEntry *ReservedRefCountEntry = NULL; +static void FlushRelationBuffers_common(SMgrRelation smgr, bool islocal); static void ReservePrivateRefCountEntry(void); static PrivateRefCountEntry *NewPrivateRefCountEntry(Buffer buffer); @@ -675,10 +676,10 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, * ReadBufferWithoutRelcache -- like ReadBufferExtended, but doesn't require * a relcache entry for the relation. * - * NB: At present, this function may only be used on permanent relations, which - * is OK, because we only use it during XLOG replay. If in the future we - * want to use it on temporary or unlogged relations, we could pass additional - * parameters. + * NB: At present, this function may only be used on permanent relations, + * which is OK, because we only use it during XLOG replay and processing + * pending syncs. If in the future we want to use it on temporary or unlogged + * relations, we could pass additional parameters. */ Buffer ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum, @@ -3203,20 +3204,32 @@ 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)); +} + +void +FlushRelationBuffersWithoutRelcache(RelFileNode rnode, bool islocal) +{ + FlushRelationBuffers_common(smgropen(rnode, InvalidBackendId), islocal); +} + +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)) { @@ -3233,7 +3246,7 @@ FlushRelationBuffers(Relation rel) PageSetChecksumInplace(localpage, bufHdr->tag.blockNum); - smgrwrite(rel->rd_smgr, + smgrwrite(smgr, bufHdr->tag.forkNum, bufHdr->tag.blockNum, localpage, @@ -3263,18 +3276,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/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 07f3c93d3f..514c6098e6 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -994,6 +994,36 @@ ForgetDatabaseSyncRequests(Oid dbid) RegisterSyncRequest(&tag, SYNC_FILTER_REQUEST, true /* retryOnError */ ); } +/* + * SyncRelationFiles -- sync files of all given relations + * + * This function is assumed to be called only when skipping WAL-logging and + * emits no xlog records. + */ +void +SyncRelationFiles(RelFileNode *syncrels, int nsyncrels) +{ + int i; + + for (i = 0; i < nsyncrels; i++) + { + SMgrRelation srel; + ForkNumber fork; + + /* sync all existing forks of the relation */ + FlushRelationBuffersWithoutRelcache(syncrels[i], false); + srel = smgropen(syncrels[i], InvalidBackendId); + + for (fork = 0; fork <= MAX_FORKNUM; fork++) + { + if (smgrexists(srel, fork)) + smgrimmedsync(srel, fork); + } + + smgrclose(srel); + } +} + /* * DropRelationFiles -- drop files of all given relations */ diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 585dcee5db..892462873f 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -1096,6 +1096,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) relation->rd_isnailed = false; relation->rd_createSubid = InvalidSubTransactionId; relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; switch (relation->rd_rel->relpersistence) { case RELPERSISTENCE_UNLOGGED: @@ -1829,6 +1830,7 @@ formrdesc(const char *relationName, Oid relationReltype, relation->rd_isnailed = true; relation->rd_createSubid = InvalidSubTransactionId; relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; relation->rd_backend = InvalidBackendId; relation->rd_islocaltemp = false; @@ -2094,7 +2096,7 @@ RelationClose(Relation relation) #ifdef RELCACHE_FORCE_RELEASE if (RelationHasReferenceCountZero(relation) && relation->rd_createSubid == InvalidSubTransactionId && - relation->rd_newRelfilenodeSubid == InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId) RelationClearRelation(relation, false); #endif } @@ -2510,8 +2512,8 @@ RelationClearRelation(Relation relation, bool rebuild) * problem. * * When rebuilding an open relcache entry, we must preserve ref count, - * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also - * attempt to preserve the pg_class entry (rd_rel), tupledesc, + * rd_createSubid/rd_new/firstRelfilenodeSubid, and rd_toastoid state. + * Also attempt to preserve the pg_class entry (rd_rel), tupledesc, * rewrite-rule, partition key, and partition descriptor substructures * in place, because various places assume that these structures won't * move while they are working with an open relcache entry. (Note: @@ -2600,6 +2602,7 @@ RelationClearRelation(Relation relation, bool rebuild) /* creation sub-XIDs must be preserved */ SWAPFIELD(SubTransactionId, rd_createSubid); SWAPFIELD(SubTransactionId, rd_newRelfilenodeSubid); + SWAPFIELD(SubTransactionId, rd_firstRelfilenodeSubid); /* un-swap rd_rel pointers, swap contents instead */ SWAPFIELD(Form_pg_class, rd_rel); /* ... but actually, we don't have to update newrel->rd_rel */ @@ -2667,7 +2670,7 @@ static void RelationFlushRelation(Relation relation) { if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) { /* * New relcache entries are always rebuilt, not flushed; else we'd @@ -2807,7 +2810,7 @@ RelationCacheInvalidate(void) * pending invalidations. */ if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) continue; relcacheInvalsReceived++; @@ -3064,6 +3067,7 @@ AtEOXact_cleanup(Relation relation, bool isCommit) * Likewise, reset the hint about the relfilenode being new. */ relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; } /* @@ -3155,7 +3159,7 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, } /* - * Likewise, update or drop any new-relfilenode-in-subtransaction hint. + * Likewise, update or drop any new-relfilenode-in-subtransaction hints. */ if (relation->rd_newRelfilenodeSubid == mySubid) { @@ -3164,6 +3168,14 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, else relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; } + + if (relation->rd_firstRelfilenodeSubid == mySubid) + { + if (isCommit) + relation->rd_firstRelfilenodeSubid = parentSubid; + else + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; + } } @@ -3253,6 +3265,7 @@ RelationBuildLocalRelation(const char *relname, /* it's being created in this transaction */ rel->rd_createSubid = GetCurrentSubTransactionId(); rel->rd_newRelfilenodeSubid = InvalidSubTransactionId; + rel->rd_firstRelfilenodeSubid = InvalidSubTransactionId; /* * create a new tuple descriptor from the one passed in. We do this @@ -3556,6 +3569,8 @@ RelationSetNewRelfilenode(Relation relation, char persistence) * operations on the rel in the same transaction. */ relation->rd_newRelfilenodeSubid = GetCurrentSubTransactionId(); + if (relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid = relation->rd_newRelfilenodeSubid; /* Flag relation as needing eoxact cleanup (to remove the hint) */ EOXactListAdd(relation); @@ -5592,6 +5607,7 @@ load_relcache_init_file(bool shared) rel->rd_fkeylist = NIL; rel->rd_createSubid = InvalidSubTransactionId; rel->rd_newRelfilenodeSubid = InvalidSubTransactionId; + rel->rd_firstRelfilenodeSubid = InvalidSubTransactionId; rel->rd_amcache = NULL; MemSet(&rel->pgstat_info, 0, sizeof(rel->pgstat_info)); diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 31a5ef0474..559f96a6dc 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -36,6 +36,7 @@ #include "access/xlog_internal.h" #include "catalog/namespace.h" #include "catalog/pg_authid.h" +#include "catalog/storage.h" #include "commands/async.h" #include "commands/prepare.h" #include "commands/user.h" @@ -2774,6 +2775,18 @@ static struct config_int ConfigureNamesInt[] = check_effective_io_concurrency, assign_effective_io_concurrency, NULL }, + { + {"wal_skip_threshold", PGC_USERSET, RESOURCES_DISK, + gettext_noop("Size of file that can be fsync'ed in the minimum required duration."), + NULL, + GUC_UNIT_KB + }, + &wal_skip_threshold, + 64, + 0, MAX_KILOBYTES, + NULL, NULL, NULL + }, + { {"backend_flush_after", PGC_USERSET, RESOURCES_ASYNCHRONOUS, gettext_noop("Number of pages after which previously performed writes are flushed to disk."), diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 858bcb6bc9..80c2e1bafc 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -29,7 +29,6 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL #define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM #define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN #define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 8056253916..7f9736e294 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -23,7 +23,7 @@ typedef struct RewriteStateData *RewriteState; extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap, TransactionId OldestXmin, TransactionId FreezeXid, - MultiXactId MultiXactCutoff, bool use_wal); + MultiXactId MultiXactCutoff); extern void end_heap_rewrite(RewriteState state); extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple, HeapTuple newTuple); diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 7f81703b78..b652cd6cef 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -407,22 +407,6 @@ typedef struct TableAmRoutine uint8 flags, TM_FailureData *tmfd); - /* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * may for example be used to flush the relation, when the - * TABLE_INSERT_SKIP_WAL option was used. - * - * Typically callers of tuple_insert and multi_insert will just pass all - * the flags that apply to them, and each AM has to decide which of them - * make sense for it, and then only take actions in finish_bulk_insert for - * those flags, and ignore others. - * - * Optional callback. - */ - void (*finish_bulk_insert) (Relation rel, int options); - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ @@ -1087,10 +1071,6 @@ table_compute_xid_horizon_for_tuples(Relation rel, * The options bitmask allows the caller to specify options that may change the * behaviour of the AM. The AM will ignore options that it does not support. * - * If the TABLE_INSERT_SKIP_WAL option is specified, the new tuple doesn't - * need to be logged to WAL, even for a non-temp relation. It is the AMs - * choice whether this optimization is supported. - * * If the TABLE_INSERT_SKIP_FSM option is specified, AMs are free to not reuse * free space in the relation. This can save some cycles when we know the * relation is new and doesn't contain useful amounts of free space. @@ -1112,8 +1092,7 @@ table_compute_xid_horizon_for_tuples(Relation rel, * heap's TOAST table, too, if the tuple requires any out-of-line data. * * The BulkInsertState object (if any; bistate can be NULL for default - * behavior) is also just passed through to RelationGetBufferForTuple. If - * `bistate` is provided, table_finish_bulk_insert() needs to be called. + * behavior) is also just passed through to RelationGetBufferForTuple. * * On return the slot's tts_tid and tts_tableOid are updated to reflect the * insertion. But note that any toasting of fields within the slot is NOT @@ -1248,6 +1227,8 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, * update was done. However, any TOAST changes in the new tuple's * data are not reflected into *newtup. * + * See table_insert about skipping WAL-logging feature. + * * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, * t_xmax, and, if possible, t_cmax. See comments for struct TM_FailureData * for additional info. @@ -1308,21 +1289,6 @@ table_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, flags, tmfd); } -/* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * e.g. may e.g. used to flush the relation when inserting with - * TABLE_INSERT_SKIP_WAL specified. - */ -static inline void -table_finish_bulk_insert(Relation rel, int options) -{ - /* optional callback */ - if (rel->rd_tableam && rel->rd_tableam->finish_bulk_insert) - rel->rd_tableam->finish_bulk_insert(rel, options); -} - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h index 3579d3f3eb..24e71651c3 100644 --- a/src/include/catalog/storage.h +++ b/src/include/catalog/storage.h @@ -19,6 +19,16 @@ #include "storage/smgr.h" #include "utils/relcache.h" +/* enum for operation type of PendingDelete entries */ +typedef enum PendingOpType +{ + PENDING_DELETE, + PENDING_SYNC +} PendingOpType; + +/* GUC variables */ +extern int wal_skip_threshold; /* threshold for WAL-skipping */ + extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence); extern void RelationDropStorage(Relation rel); extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit); @@ -31,7 +41,9 @@ extern void RelationCopyStorage(SMgrRelation src, SMgrRelation dst, * naming */ extern void smgrDoPendingDeletes(bool isCommit); +extern void smgrDoPendingSyncs(bool isCommit, bool sync_all); extern int smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr); +extern int smgrGetPendingSyncs(bool forCommit, RelFileNode **ptr); 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 17b97f7e38..f31a36de17 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -189,6 +189,7 @@ 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, int nforks, BlockNumber *firstDelBlock); diff --git a/src/include/storage/md.h b/src/include/storage/md.h index c0f05e23ff..2bb2947bdb 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -42,6 +42,7 @@ extern void mdtruncate(SMgrRelation reln, ForkNumber forknum, extern void mdimmedsync(SMgrRelation reln, ForkNumber forknum); extern void ForgetDatabaseSyncRequests(Oid dbid); +extern void SyncRelationFiles(RelFileNode *syncrels, int nsyncrels); extern void DropRelationFiles(RelFileNode *delrels, int ndelrels, bool isRedo); /* md sync callbacks */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 8b8b237f0d..a46c086cc2 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -66,19 +66,41 @@ typedef struct RelationData /* * rd_createSubid is the ID of the highest subtransaction the rel has * survived into; or zero if the rel was not created in the current top - * transaction. This can be now be relied on, whereas previously it could - * be "forgotten" in earlier releases. Likewise, rd_newRelfilenodeSubid is - * the ID of the highest subtransaction the relfilenode change has - * survived into, or zero if not changed in the current transaction (or we - * have forgotten changing it). rd_newRelfilenodeSubid can be forgotten - * when a relation has multiple new relfilenodes within a single - * transaction, with one of them occurring in a subsequently aborted - * subtransaction, e.g. BEGIN; TRUNCATE t; SAVEPOINT save; TRUNCATE t; - * ROLLBACK TO save; -- rd_newRelfilenodeSubid is now forgotten + * transaction. A valid value means the relation is created in the + * subtransction and non-rollbackable trancation is usable in the same + * subtransaction. This can be now be relied on, whereas previously it + * could be "forgotten" in earlier releases. + * + * Likewise, rd_newRelfilenodeSubid is the subtransaction ID where the + * current relfilenode can be assumed to have been created, or zero if + * not. If this is equal to the current subtransaction id we can truncate + * the current relfilenode in a non-rollbackable way. It survibes moving to + * parent subtransaction as long as comitted. It is not totally reliable + * and used just as hint because it is forgotten by overwriting in a + * subsequent subtransaction. e.g. BEGIN; TRUNCATE t; SAVEPOINT save; + * TRUNCATE t; ROLLBACK TO save; TRUNCATE t; -- The ROLLBACK TO doesn't + * restore the value for the first TRUNCATE and the value is now + * forgotten. The last TRUNCATE doesn't use non-rollbackable truncation. + * + * rd_firstRelfilenodeSubid is the ID of the subtransaction where the + * change of relfilenode took place first in the top trasaction. A valid + * value means that there are one or more relfilenodes created in the + * top-transaction. They are all local and inaccessible from outside. When + * wal_level is minimal, WAL-logging is omitted and the relfilenode at + * commit is sync'ed (and others are removed). Unlike newRelfilenodeSubid, + * this is reliable. No overwriting happens and the value is moved to + * parent subtransaction at subtransacton commit, and forgotten at + * rollback. + * + * A valid value of rd_createSubid or rd_firstRelfilenodeSubid prevents the + * relcache entry from flushing or rebuilding in order to preseve the + * value. */ SubTransactionId rd_createSubid; /* rel was created in current xact */ SubTransactionId rd_newRelfilenodeSubid; /* new relfilenode assigned in * current xact */ + SubTransactionId rd_firstRelfilenodeSubid; /* new relfilenode assigned + * first in current xact */ Form_pg_class rd_rel; /* RELATION tuple */ TupleDesc rd_att; /* tuple descriptor */ @@ -521,9 +543,15 @@ typedef struct ViewOptions /* * RelationNeedsWAL * True if relation needs WAL. - */ -#define RelationNeedsWAL(relation) \ - ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) + * + * Returns false if wal_level = minimal and this relation is created or + * truncated in the current transaction. + */ +#define RelationNeedsWAL(relation) \ + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT && \ + (XLogIsNeeded() || \ + (relation->rd_createSubid == InvalidSubTransactionId && \ + relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId))) /* * RelationUsesLocalBuffers -- 2.23.0 ----Next_Part(Wed_Nov__6_17_29_27_2019_575)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v23-0003-Fix-MarkBufferDirtyHint.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH v21 2/5] Fix WAL skipping feature @ 2019-10-25 03:07 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Kyotaro Horiguchi @ 2019-10-25 03:07 UTC (permalink / raw) WAL-skipping operations mixed with WAL-logged operations can lead to database corruption after a crash. This patch changes the WAL-skipping feature so that no data modification is WAL-logged at all then sync such relations at commit. --- src/backend/access/heap/heapam.c | 4 +- src/backend/access/heap/heapam_handler.c | 22 +- src/backend/access/heap/rewriteheap.c | 13 +- src/backend/access/transam/xact.c | 17 ++ src/backend/access/transam/xlogutils.c | 11 +- src/backend/catalog/storage.c | 294 ++++++++++++++++++++--- src/backend/commands/cluster.c | 24 ++ src/backend/commands/copy.c | 39 +-- src/backend/commands/createas.c | 5 +- src/backend/commands/matview.c | 4 - src/backend/commands/tablecmds.c | 10 +- src/backend/storage/buffer/bufmgr.c | 41 ++-- src/backend/storage/smgr/md.c | 30 +++ src/backend/utils/cache/relcache.c | 28 ++- src/backend/utils/misc/guc.c | 13 + src/include/access/heapam.h | 1 - src/include/access/rewriteheap.h | 2 +- src/include/access/tableam.h | 40 +-- src/include/catalog/storage.h | 12 + src/include/storage/bufmgr.h | 1 + src/include/storage/md.h | 1 + src/include/utils/rel.h | 19 +- 22 files changed, 455 insertions(+), 176 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 0128bb34ef..a7ead9405a 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -1936,7 +1936,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, MarkBufferDirty(buffer); /* XLOG stuff */ - if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) + if (RelationNeedsWAL(relation)) { xl_heap_insert xlrec; xl_heap_header xlhdr; @@ -2119,7 +2119,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); - needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); + needwal = RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 2dd8821fac..0871df7730 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -558,18 +558,6 @@ tuple_lock_retry: return result; } -static void -heapam_finish_bulk_insert(Relation relation, int options) -{ - /* - * If we skipped writing WAL, then we need to sync the heap (but not - * indexes since those use WAL anyway / don't go through tableam) - */ - if (options & HEAP_INSERT_SKIP_WAL) - heap_sync(relation); -} - - /* ------------------------------------------------------------------------ * DDL related callbacks for heap AM. * ------------------------------------------------------------------------ @@ -701,7 +689,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, IndexScanDesc indexScan; TableScanDesc tableScan; HeapScanDesc heapScan; - bool use_wal; bool is_system_catalog; Tuplesortstate *tuplesort; TupleDesc oldTupDesc = RelationGetDescr(OldHeap); @@ -716,12 +703,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, is_system_catalog = IsSystemRelation(OldHeap); /* - * We need to log the copied data in WAL iff WAL archiving/streaming is - * enabled AND it's a WAL-logged rel. + * smgr_targblock must be initially invalid if we are to skip WAL logging */ - use_wal = XLogIsNeeded() && RelationNeedsWAL(NewHeap); - - /* use_wal off requires smgr_targblock be initially invalid */ Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber); /* Preallocate values/isnull arrays */ @@ -731,7 +714,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, /* Initialize the rewrite operation */ rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff, - *multi_cutoff, use_wal); + *multi_cutoff); /* Set up sorting if wanted */ @@ -2519,7 +2502,6 @@ static const TableAmRoutine heapam_methods = { .tuple_delete = heapam_tuple_delete, .tuple_update = heapam_tuple_update, .tuple_lock = heapam_tuple_lock, - .finish_bulk_insert = heapam_finish_bulk_insert, .tuple_fetch_row_version = heapam_fetch_row_version, .tuple_get_latest_tid = heap_get_latest_tid, diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index d41dbcf5f7..9b757cacf4 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -144,7 +144,6 @@ typedef struct RewriteStateData Page rs_buffer; /* page currently being built */ BlockNumber rs_blockno; /* block where page will go */ bool rs_buffer_valid; /* T if any tuples in buffer */ - bool rs_use_wal; /* must we WAL-log inserts? */ bool rs_logical_rewrite; /* do we need to do logical rewriting */ TransactionId rs_oldest_xmin; /* oldest xmin used by caller to determine * tuple visibility */ @@ -238,15 +237,13 @@ static void logical_end_heap_rewrite(RewriteState state); * oldest_xmin xid used by the caller to determine which tuples are dead * freeze_xid xid before which tuples will be frozen * cutoff_multi multixact before which multis will be removed - * use_wal should the inserts to the new heap be WAL-logged? * * Returns an opaque RewriteState, allocated in current memory context, * to be used in subsequent calls to the other functions. */ RewriteState begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xmin, - TransactionId freeze_xid, MultiXactId cutoff_multi, - bool use_wal) + TransactionId freeze_xid, MultiXactId cutoff_multi) { RewriteState state; MemoryContext rw_cxt; @@ -271,7 +268,6 @@ begin_heap_rewrite(Relation old_heap, Relation new_heap, TransactionId oldest_xm /* new_heap needn't be empty, just locked */ state->rs_blockno = RelationGetNumberOfBlocks(new_heap); state->rs_buffer_valid = false; - state->rs_use_wal = use_wal; state->rs_oldest_xmin = oldest_xmin; state->rs_freeze_xid = freeze_xid; state->rs_cutoff_multi = cutoff_multi; @@ -330,7 +326,7 @@ end_heap_rewrite(RewriteState state) /* Write the last page, if any */ if (state->rs_buffer_valid) { - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, @@ -654,9 +650,6 @@ raw_heap_insert(RewriteState state, HeapTuple tup) { int options = HEAP_INSERT_SKIP_FSM; - if (!state->rs_use_wal) - options |= HEAP_INSERT_SKIP_WAL; - /* * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data * for the TOAST table are not logically decoded. The main heap is @@ -695,7 +688,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup) /* Doesn't fit, so write out the existing page */ /* XLOG stuff */ - if (state->rs_use_wal) + if (RelationNeedsWAL(state->rs_new_rel)) log_newpage(&state->rs_new_rel->rd_node, MAIN_FORKNUM, state->rs_blockno, diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index fc55fa6d53..59d65bc214 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -2107,6 +2107,13 @@ CommitTransaction(void) */ PreCommit_on_commit_actions(); + /* + * Synchronize files that are created and not WAL-logged during this + * transaction. This must happen before emitting commit record so that we + * don't see committed-but-broken files after a crash. + */ + smgrDoPendingSyncs(true, false); + /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); @@ -2339,6 +2346,14 @@ PrepareTransaction(void) */ PreCommit_on_commit_actions(); + /* + * Sync all WAL-skipped files now. Some of them may be deleted at + * transaction end but we don't bother store that information in PREPARE + * record or two-phase files. Like commit, we should sync WAL-skipped + * files before emitting PREPARE record. See CommitTransaction(). + */ + smgrDoPendingSyncs(true, true); + /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); @@ -2657,6 +2672,7 @@ AbortTransaction(void) */ AfterTriggerEndXact(false); /* 'false' means it's abort */ AtAbort_Portals(); + smgrDoPendingSyncs(false, false); AtEOXact_LargeObject(false); AtAbort_Notify(); AtEOXact_RelationMap(false, is_parallel_worker); @@ -4964,6 +4980,7 @@ AbortSubTransaction(void) s->parent->curTransactionOwner); AtEOSubXact_LargeObject(false, s->subTransactionId, s->parent->subTransactionId); + smgrDoPendingSyncs(false, false); AtSubAbort_Notify(); /* Advertise the fact that we aborted in pg_xact. */ diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 5f1e5ba75d..fc296abf91 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -544,6 +544,8 @@ typedef FakeRelCacheEntryData *FakeRelCacheEntry; * fields related to physical storage, like rd_rel, are initialized, so the * fake entry is only usable in low-level operations like ReadBuffer(). * + * This is also used for syncing WAL-skipped files. + * * Caller must free the returned entry with FreeFakeRelcacheEntry(). */ Relation @@ -552,18 +554,19 @@ CreateFakeRelcacheEntry(RelFileNode rnode) FakeRelCacheEntry fakeentry; Relation rel; - Assert(InRecovery); - /* Allocate the Relation struct and all related space in one block. */ fakeentry = palloc0(sizeof(FakeRelCacheEntryData)); rel = (Relation) fakeentry; rel->rd_rel = &fakeentry->pgc; rel->rd_node = rnode; - /* We will never be working with temp rels during recovery */ + /* + * We will never be working with temp rels during recovery or syncing + * WAL-skpped files. + */ rel->rd_backend = InvalidBackendId; - /* It must be a permanent table if we're in recovery. */ + /* It must be a permanent table here */ rel->rd_rel->relpersistence = RELPERSISTENCE_PERMANENT; /* We don't know the name of the relation; use relfilenode instead */ diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index 625af8d49a..806f235a24 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -30,9 +30,13 @@ #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" +/* GUC variables */ +int wal_skip_threshold = 64; /* threshold of WAL-skipping in kilobytes */ + /* * We keep a list of all relations (represented as RelFileNode values) * that have been created or deleted in the current transaction. When @@ -53,16 +57,17 @@ * but I'm being paranoid. */ -typedef struct PendingRelDelete +typedef struct PendingRelOp { RelFileNode relnode; /* relation that may need to be deleted */ BackendId backend; /* InvalidBackendId if not a temp rel */ - bool atCommit; /* T=delete at commit; F=delete at abort */ + bool atCommit; /* T=work at commit; F=work at abort */ int nestLevel; /* xact nesting level of request */ - struct PendingRelDelete *next; /* linked-list link */ -} PendingRelDelete; + struct PendingRelOp *next; /* linked-list link */ +} PendingRelOp; -static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */ +static PendingRelOp *pendingDeletes = NULL; /* head of linked list */ +static PendingRelOp *pendingSyncs = NULL; /* head of linked list */ /* * RelationCreateStorage @@ -78,7 +83,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */ SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence) { - PendingRelDelete *pending; + PendingRelOp *pending; SMgrRelation srel; BackendId backend; bool needs_wal; @@ -109,8 +114,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) log_smgrcreate(&srel->smgr_rnode.node, MAIN_FORKNUM); /* Add the relation to the list of stuff to delete at abort */ - pending = (PendingRelDelete *) - MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); + pending = (PendingRelOp *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelOp)); pending->relnode = rnode; pending->backend = backend; pending->atCommit = false; /* delete if abort */ @@ -118,6 +123,25 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) pending->next = pendingDeletes; pendingDeletes = pending; + /* + * When wal_level = minimal, we are going to skip WAL-logging for storage + * of persistent relations created in the current transaction. The + * relation needs to be synced at commit. + */ + if (relpersistence == RELPERSISTENCE_PERMANENT && !XLogIsNeeded()) + { + int nestLevel = GetCurrentTransactionNestLevel(); + + pending = (PendingRelOp *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelOp)); + pending->relnode = rnode; + pending->backend = backend; + pending->atCommit = true; + pending->nestLevel = nestLevel; + pending->next = pendingSyncs; + pendingSyncs = pending; + } + return srel; } @@ -147,11 +171,11 @@ log_smgrcreate(const RelFileNode *rnode, ForkNumber forkNum) void RelationDropStorage(Relation rel) { - PendingRelDelete *pending; + PendingRelOp *pending; /* Add the relation to the list of stuff to delete at commit */ - pending = (PendingRelDelete *) - MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); + pending = (PendingRelOp *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelOp)); pending->relnode = rel->rd_node; pending->backend = rel->rd_backend; pending->atCommit = true; /* delete if commit */ @@ -192,9 +216,9 @@ RelationDropStorage(Relation rel) void RelationPreserveStorage(RelFileNode rnode, bool atCommit) { - PendingRelDelete *pending; - PendingRelDelete *prev; - PendingRelDelete *next; + PendingRelOp *pending; + PendingRelOp *prev; + PendingRelOp *next; prev = NULL; for (pending = pendingDeletes; pending != NULL; pending = next) @@ -431,9 +455,9 @@ void smgrDoPendingDeletes(bool isCommit) { int nestLevel = GetCurrentTransactionNestLevel(); - PendingRelDelete *pending; - PendingRelDelete *prev; - PendingRelDelete *next; + PendingRelOp *pending; + PendingRelOp *prev; + PendingRelOp *next; int nrels = 0, i = 0, maxrels = 0; @@ -494,11 +518,194 @@ smgrDoPendingDeletes(bool isCommit) } /* - * smgrGetPendingDeletes() -- Get a list of non-temp relations to be deleted. + * smgrDoPendingSyncs() -- Take care of relation syncs at end of xact. + * + * This should be called before smgrDoPendingDeletes() at every subtransaction + * end. Also this should be called before emitting WAL record so that sync + * failure prevents commit. + * + * If sync_all is true, syncs all files including that are scheduled to be + * deleted. + */ +void +smgrDoPendingSyncs(bool isCommit, bool sync_all) +{ + int nestLevel = GetCurrentTransactionNestLevel(); + PendingRelOp *pending; + PendingRelOp *prev; + PendingRelOp *next; + SMgrRelation srel = NULL; + ForkNumber fork; + BlockNumber nblocks[MAX_FORKNUM + 1]; + BlockNumber total_blocks = 0; + HTAB *delhash = NULL; + + /* Return if nothing to be synced in this nestlevel */ + if (!pendingSyncs || pendingSyncs->nestLevel < nestLevel) + return; + + Assert (pendingSyncs->nestLevel <= nestLevel); + Assert (pendingSyncs->backend == InvalidBackendId); + + /* + * If sync_all is false, pending syncs on the relation that are to be + * deleted in this transaction-end should be ignored. Collect pending + * deletes that will happen in the following call to + * smgrDoPendingDeletes(). + */ + if (!sync_all) + { + for (pending = pendingDeletes; pending != NULL; pending = pending->next) + { + bool found PG_USED_FOR_ASSERTS_ONLY; + + if (pending->nestLevel < pendingSyncs->nestLevel || + pending->atCommit != isCommit) + continue; + + /* create the hash if not yet */ + if (delhash == NULL) + { + HASHCTL hash_ctl; + + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(RelFileNode); + hash_ctl.entrysize = sizeof(RelFileNode); + hash_ctl.hcxt = CurrentMemoryContext; + delhash = + hash_create("pending del temporary hash", 8, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + } + + (void) hash_search(delhash, (void *) &(pending->relnode), + HASH_ENTER, &found); + Assert(!found); + } + } + + /* Loop over pendingSyncs */ + prev = NULL; + for (pending = pendingSyncs; pending != NULL; pending = next) + { + bool to_be_removed = (!isCommit); /* don't sync if aborted */ + + next = pending->next; + + /* outer-level entries should not be processed yet */ + if (pending->nestLevel < nestLevel) + { + prev = pending; + continue; + } + + /* don't sync relnodes that is being deleted */ + if (delhash && !to_be_removed) + hash_search(delhash, (void *) &pending->relnode, + HASH_FIND, &to_be_removed); + + /* remove the entry if no longer useful */ + if (to_be_removed) + { + if (prev) + prev->next = next; + else + pendingSyncs = next; + pfree(pending); + continue; + } + + /* actual sync happens at the end of top transaction */ + if (nestLevel > 1) + { + prev = pending; + continue; + } + + /* Now the time to sync the rnode */ + srel = smgropen(pendingSyncs->relnode, pendingSyncs->backend); + + /* + * We emit newpage WAL records for smaller size of relations. + * + * Small WAL records have a chance to be emitted at once along with + * other backends' WAL records. We emit WAL records instead of syncing + * for files that are smaller than a certain threshold expecting faster + * commit. The threshold is defined by the GUC wal_skip_threshold. + */ + for (fork = 0 ; fork <= MAX_FORKNUM ; fork++) + { + /* FSM doesn't need WAL nor sync */ + if (fork != FSM_FORKNUM && smgrexists(srel, fork)) + { + BlockNumber n = smgrnblocks(srel, fork); + + /* we shouldn't come here for unlogged relations */ + Assert(fork != INIT_FORKNUM); + + nblocks[fork] = n; + total_blocks += n; + } + else + nblocks[fork] = InvalidBlockNumber; + } + + /* + * Sync file or emit WAL record for the file according to the total + * size. + */ + if (total_blocks * BLCKSZ >= wal_skip_threshold * 1024) + { + /* Flush all buffers then sync the file */ + FlushRelationBuffersWithoutRelcache(srel->smgr_rnode.node, false); + + for (fork = 0; fork <= MAX_FORKNUM; fork++) + { + if (smgrexists(srel, fork)) + smgrimmedsync(srel, fork); + } + } + else + { + /* + * Emit WAL records for all blocks. Some of the blocks might have + * been synced or evicted, but We don't bother checking that. The + * file is small enough. + */ + for (fork = 0 ; fork <= MAX_FORKNUM ; fork++) + { + bool page_std = (fork == MAIN_FORKNUM); + int n = nblocks[fork]; + Relation rel; + + if (!BlockNumberIsValid(n)) + continue; + + /* Emit WAL for the whole file */ + rel = CreateFakeRelcacheEntry(srel->smgr_rnode.node); + log_newpage_range(rel, fork, 0, n, page_std); + FreeFakeRelcacheEntry(rel); + } + } + + /* done remove from list */ + if (prev) + prev->next = next; + else + pendingSyncs = next; + pfree(pending); + } + + if (delhash) + hash_destroy(delhash); +} + +/* + * smgrGetPendingOperations() -- Get a list of non-temp relations to be + * deleted or synced. * - * The return value is the number of relations scheduled for termination. - * *ptr is set to point to a freshly-palloc'd array of RelFileNodes. - * If there are no relations to be deleted, *ptr is set to NULL. + * The return value is the number of relations scheduled in the given + * list. *ptr is set to point to a freshly-palloc'd array of RelFileNodes. If + * there are no matching relations, *ptr is set to NULL. * * Only non-temporary relations are included in the returned list. This is OK * because the list is used only in contexts where temporary relations don't @@ -507,19 +714,19 @@ smgrDoPendingDeletes(bool isCommit) * (and all temporary files will be zapped if we restart anyway, so no need * for redo to do it also). * - * Note that the list does not include anything scheduled for termination - * by upper-level transactions. + * Note that the list does not include anything scheduled by upper-level + * transactions. */ -int -smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) +static inline int +smgrGetPendingOperations(PendingRelOp *list, bool forCommit, RelFileNode **ptr) { int nestLevel = GetCurrentTransactionNestLevel(); int nrels; RelFileNode *rptr; - PendingRelDelete *pending; + PendingRelOp *pending; nrels = 0; - for (pending = pendingDeletes; pending != NULL; pending = pending->next) + for (pending = list; pending != NULL; pending = pending->next) { if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit && pending->backend == InvalidBackendId) @@ -532,7 +739,7 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) } rptr = (RelFileNode *) palloc(nrels * sizeof(RelFileNode)); *ptr = rptr; - for (pending = pendingDeletes; pending != NULL; pending = pending->next) + for (pending = list; pending != NULL; pending = pending->next) { if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit && pending->backend == InvalidBackendId) @@ -544,6 +751,20 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) return nrels; } +/* Returns list of pending deletes, see smgrGetPendingOperations for details */ +int +smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) +{ + return smgrGetPendingOperations(pendingDeletes, forCommit, ptr); +} + +/* Returns list of pending syncs, see smgrGetPendingOperations for details */ +int +smgrGetPendingSyncs(bool forCommit, RelFileNode **ptr) +{ + return smgrGetPendingOperations(pendingSyncs, forCommit, ptr); +} + /* * PostPrepare_smgr -- Clean up after a successful PREPARE * @@ -554,8 +775,8 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) void PostPrepare_smgr(void) { - PendingRelDelete *pending; - PendingRelDelete *next; + PendingRelOp *pending; + PendingRelOp *next; for (pending = pendingDeletes; pending != NULL; pending = next) { @@ -564,25 +785,34 @@ PostPrepare_smgr(void) /* must explicitly free the list entry */ pfree(pending); } + + /* We shouldn't have an entry in pendingSyncs */ + Assert(pendingSyncs == NULL); } /* * AtSubCommit_smgr() --- Take care of subtransaction commit. * - * Reassign all items in the pending-deletes list to the parent transaction. + * Reassign all items in the pending-operations list to the parent transaction. */ void AtSubCommit_smgr(void) { int nestLevel = GetCurrentTransactionNestLevel(); - PendingRelDelete *pending; + PendingRelOp *pending; for (pending = pendingDeletes; pending != NULL; pending = pending->next) { if (pending->nestLevel >= nestLevel) pending->nestLevel = nestLevel - 1; } + + for (pending = pendingSyncs; pending != NULL; pending = pending->next) + { + if (pending->nestLevel >= nestLevel) + pending->nestLevel = nestLevel - 1; + } } /* diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index a23128d7a0..fba44de88a 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1034,12 +1034,36 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, if (OidIsValid(relfilenode1) && OidIsValid(relfilenode2)) { + Relation rel1; + Relation rel2; + /* * Normal non-mapped relations: swap relfilenodes, reltablespaces, * relpersistence */ Assert(!target_is_pg_class); + /* Update creation subid hints of relcache */ + rel1 = relation_open(r1, ExclusiveLock); + rel2 = relation_open(r2, ExclusiveLock); + + /* + * New relation's relfilenode is created in the current transaction + * and used as old ralation's new relfilenode. So its + * newRelfilenodeSubid as new relation's createSubid. We don't fix + * rel2 since it would be deleted soon. + */ + Assert(rel2->rd_createSubid != InvalidSubTransactionId); + rel1->rd_newRelfilenodeSubid = rel2->rd_createSubid; + + /* record the first relfilenode change in the current transaction */ + if (rel1->rd_firstRelfilenodeSubid == InvalidSubTransactionId) + rel1->rd_firstRelfilenodeSubid = GetCurrentSubTransactionId(); + + relation_close(rel1, ExclusiveLock); + relation_close(rel2, ExclusiveLock); + + /* swap relfilenodes, reltablespaces, relpersistence */ swaptemp = relform1->relfilenode; relform1->relfilenode = relform2->relfilenode; relform2->relfilenode = swaptemp; diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 3aeef30b28..3ce04f7efc 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2534,9 +2534,6 @@ CopyMultiInsertBufferCleanup(CopyMultiInsertInfo *miinfo, for (i = 0; i < MAX_BUFFERED_TUPLES && buffer->slots[i] != NULL; i++) ExecDropSingleTupleTableSlot(buffer->slots[i]); - table_finish_bulk_insert(buffer->resultRelInfo->ri_RelationDesc, - miinfo->ti_options); - pfree(buffer); } @@ -2725,28 +2722,9 @@ CopyFrom(CopyState cstate) * If it does commit, we'll have done the table_finish_bulk_insert() 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 - * can be cleared before the end of the transaction. The exact case is - * when a relation sets a new relfilenode twice in same transaction, yet - * the second one fails in an aborted subtransaction, e.g. - * - * BEGIN; - * TRUNCATE t; - * SAVEPOINT save; - * TRUNCATE t; - * ROLLBACK TO save; - * COPY ... - * - * Also, if the target file is new-in-transaction, we assume that checking - * FSM for free space is a waste of time, even if we must use WAL because - * of archiving. This could possibly be wrong, but it's unlikely. - * - * The comments for table_tuple_insert and RelationGetBufferForTuple - * specify that skipping WAL logging is only safe if we ensure that our - * tuples do not go into pages containing tuples from any other - * transactions --- but this must be the case if we have a new table or - * new relfilenode, so we need no additional work to enforce that. + * If the target file is new-in-transaction, we assume that checking FSM + * for free space is a waste of time, even if we must use WAL because of + * archiving. This could possibly be wrong, but it's unlikely. * * We currently don't support this optimization if the COPY target is a * partitioned table as we currently only lazily initialize partition @@ -2762,15 +2740,14 @@ CopyFrom(CopyState cstate) * are not supported as per the description above. *---------- */ - /* createSubid is creation check, newRelfilenodeSubid is truncation check */ + /* + * createSubid is creation check, firstRelfilenodeSubid is truncation and + * cluster check. Partitioned table doesn't have storage. + */ if (RELKIND_HAS_STORAGE(cstate->rel->rd_rel->relkind) && (cstate->rel->rd_createSubid != InvalidSubTransactionId || - cstate->rel->rd_newRelfilenodeSubid != InvalidSubTransactionId)) - { + cstate->rel->rd_firstRelfilenodeSubid != InvalidSubTransactionId)) ti_options |= TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; - } /* * Optimize if new relfilenode was created in this subxact or one of its diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index b7d220699f..8a91d946e3 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -558,8 +558,7 @@ 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->ti_options = TABLE_INSERT_SKIP_FSM | - (XLogIsNeeded() ? 0 : TABLE_INSERT_SKIP_WAL); + myState->ti_options = TABLE_INSERT_SKIP_FSM; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -604,8 +603,6 @@ intorel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->rel, myState->ti_options); - /* close rel, but keep lock until commit */ table_close(myState->rel, NoLock); myState->rel = NULL; diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 537d0e8cef..1c854dcebf 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -463,8 +463,6 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) * replication is in use. We can skip the FSM in any case. */ myState->ti_options = TABLE_INSERT_SKIP_FSM | TABLE_INSERT_FROZEN; - if (!XLogIsNeeded()) - myState->ti_options |= TABLE_INSERT_SKIP_WAL; myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ @@ -509,8 +507,6 @@ transientrel_shutdown(DestReceiver *self) FreeBulkInsertState(myState->bistate); - table_finish_bulk_insert(myState->transientrel, myState->ti_options); - /* close transientrel, but keep lock until commit */ table_close(myState->transientrel, NoLock); myState->transientrel = NULL; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 8d25d14772..54c8b0fb04 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4764,9 +4764,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) /* * Prepare a BulkInsertState and options for table_tuple_insert. Because - * we're building a new heap, we can skip WAL-logging and fsync it to disk - * at the end instead (unless WAL-logging is required for archiving or - * streaming replication). The FSM is empty too, so don't bother using it. + * we're building a new heap, the underlying table AM can skip WAL-logging + * and smgr will sync the relation to disk at the end of the current + * transaction instead. The FSM is empty too, so don't bother using it. */ if (newrel) { @@ -4774,8 +4774,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) bistate = GetBulkInsertState(); ti_options = TABLE_INSERT_SKIP_FSM; - if (!XLogIsNeeded()) - ti_options |= TABLE_INSERT_SKIP_WAL; } else { @@ -5070,8 +5068,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { FreeBulkInsertState(bistate); - table_finish_bulk_insert(newrel, ti_options); - table_close(newrel, NoLock); } } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 483f705305..827626b330 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -171,6 +171,7 @@ static HTAB *PrivateRefCountHash = NULL; static int32 PrivateRefCountOverflowed = 0; static uint32 PrivateRefCountClock = 0; static PrivateRefCountEntry *ReservedRefCountEntry = NULL; +static void FlushRelationBuffers_common(SMgrRelation smgr, bool islocal); static void ReservePrivateRefCountEntry(void); static PrivateRefCountEntry *NewPrivateRefCountEntry(Buffer buffer); @@ -675,10 +676,10 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, * ReadBufferWithoutRelcache -- like ReadBufferExtended, but doesn't require * a relcache entry for the relation. * - * NB: At present, this function may only be used on permanent relations, which - * is OK, because we only use it during XLOG replay. If in the future we - * want to use it on temporary or unlogged relations, we could pass additional - * parameters. + * NB: At present, this function may only be used on permanent relations, + * which is OK, because we only use it during XLOG replay and processing + * pending syncs. If in the future we want to use it on temporary or unlogged + * relations, we could pass additional parameters. */ Buffer ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum, @@ -3203,20 +3204,32 @@ 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)); +} + +void +FlushRelationBuffersWithoutRelcache(RelFileNode rnode, bool islocal) +{ + FlushRelationBuffers_common(smgropen(rnode, InvalidBackendId), islocal); +} + +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)) { @@ -3233,7 +3246,7 @@ FlushRelationBuffers(Relation rel) PageSetChecksumInplace(localpage, bufHdr->tag.blockNum); - smgrwrite(rel->rd_smgr, + smgrwrite(smgr, bufHdr->tag.forkNum, bufHdr->tag.blockNum, localpage, @@ -3263,18 +3276,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/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 07f3c93d3f..514c6098e6 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -994,6 +994,36 @@ ForgetDatabaseSyncRequests(Oid dbid) RegisterSyncRequest(&tag, SYNC_FILTER_REQUEST, true /* retryOnError */ ); } +/* + * SyncRelationFiles -- sync files of all given relations + * + * This function is assumed to be called only when skipping WAL-logging and + * emits no xlog records. + */ +void +SyncRelationFiles(RelFileNode *syncrels, int nsyncrels) +{ + int i; + + for (i = 0; i < nsyncrels; i++) + { + SMgrRelation srel; + ForkNumber fork; + + /* sync all existing forks of the relation */ + FlushRelationBuffersWithoutRelcache(syncrels[i], false); + srel = smgropen(syncrels[i], InvalidBackendId); + + for (fork = 0; fork <= MAX_FORKNUM; fork++) + { + if (smgrexists(srel, fork)) + smgrimmedsync(srel, fork); + } + + smgrclose(srel); + } +} + /* * DropRelationFiles -- drop files of all given relations */ diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 585dcee5db..892462873f 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -1096,6 +1096,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) relation->rd_isnailed = false; relation->rd_createSubid = InvalidSubTransactionId; relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; switch (relation->rd_rel->relpersistence) { case RELPERSISTENCE_UNLOGGED: @@ -1829,6 +1830,7 @@ formrdesc(const char *relationName, Oid relationReltype, relation->rd_isnailed = true; relation->rd_createSubid = InvalidSubTransactionId; relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; relation->rd_backend = InvalidBackendId; relation->rd_islocaltemp = false; @@ -2094,7 +2096,7 @@ RelationClose(Relation relation) #ifdef RELCACHE_FORCE_RELEASE if (RelationHasReferenceCountZero(relation) && relation->rd_createSubid == InvalidSubTransactionId && - relation->rd_newRelfilenodeSubid == InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId) RelationClearRelation(relation, false); #endif } @@ -2510,8 +2512,8 @@ RelationClearRelation(Relation relation, bool rebuild) * problem. * * When rebuilding an open relcache entry, we must preserve ref count, - * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also - * attempt to preserve the pg_class entry (rd_rel), tupledesc, + * rd_createSubid/rd_new/firstRelfilenodeSubid, and rd_toastoid state. + * Also attempt to preserve the pg_class entry (rd_rel), tupledesc, * rewrite-rule, partition key, and partition descriptor substructures * in place, because various places assume that these structures won't * move while they are working with an open relcache entry. (Note: @@ -2600,6 +2602,7 @@ RelationClearRelation(Relation relation, bool rebuild) /* creation sub-XIDs must be preserved */ SWAPFIELD(SubTransactionId, rd_createSubid); SWAPFIELD(SubTransactionId, rd_newRelfilenodeSubid); + SWAPFIELD(SubTransactionId, rd_firstRelfilenodeSubid); /* un-swap rd_rel pointers, swap contents instead */ SWAPFIELD(Form_pg_class, rd_rel); /* ... but actually, we don't have to update newrel->rd_rel */ @@ -2667,7 +2670,7 @@ static void RelationFlushRelation(Relation relation) { if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) { /* * New relcache entries are always rebuilt, not flushed; else we'd @@ -2807,7 +2810,7 @@ RelationCacheInvalidate(void) * pending invalidations. */ if (relation->rd_createSubid != InvalidSubTransactionId || - relation->rd_newRelfilenodeSubid != InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid != InvalidSubTransactionId) continue; relcacheInvalsReceived++; @@ -3064,6 +3067,7 @@ AtEOXact_cleanup(Relation relation, bool isCommit) * Likewise, reset the hint about the relfilenode being new. */ relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; } /* @@ -3155,7 +3159,7 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, } /* - * Likewise, update or drop any new-relfilenode-in-subtransaction hint. + * Likewise, update or drop any new-relfilenode-in-subtransaction hints. */ if (relation->rd_newRelfilenodeSubid == mySubid) { @@ -3164,6 +3168,14 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, else relation->rd_newRelfilenodeSubid = InvalidSubTransactionId; } + + if (relation->rd_firstRelfilenodeSubid == mySubid) + { + if (isCommit) + relation->rd_firstRelfilenodeSubid = parentSubid; + else + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; + } } @@ -3253,6 +3265,7 @@ RelationBuildLocalRelation(const char *relname, /* it's being created in this transaction */ rel->rd_createSubid = GetCurrentSubTransactionId(); rel->rd_newRelfilenodeSubid = InvalidSubTransactionId; + rel->rd_firstRelfilenodeSubid = InvalidSubTransactionId; /* * create a new tuple descriptor from the one passed in. We do this @@ -3556,6 +3569,8 @@ RelationSetNewRelfilenode(Relation relation, char persistence) * operations on the rel in the same transaction. */ relation->rd_newRelfilenodeSubid = GetCurrentSubTransactionId(); + if (relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId) + relation->rd_firstRelfilenodeSubid = relation->rd_newRelfilenodeSubid; /* Flag relation as needing eoxact cleanup (to remove the hint) */ EOXactListAdd(relation); @@ -5592,6 +5607,7 @@ load_relcache_init_file(bool shared) rel->rd_fkeylist = NIL; rel->rd_createSubid = InvalidSubTransactionId; rel->rd_newRelfilenodeSubid = InvalidSubTransactionId; + rel->rd_firstRelfilenodeSubid = InvalidSubTransactionId; rel->rd_amcache = NULL; MemSet(&rel->pgstat_info, 0, sizeof(rel->pgstat_info)); diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 31a5ef0474..559f96a6dc 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -36,6 +36,7 @@ #include "access/xlog_internal.h" #include "catalog/namespace.h" #include "catalog/pg_authid.h" +#include "catalog/storage.h" #include "commands/async.h" #include "commands/prepare.h" #include "commands/user.h" @@ -2774,6 +2775,18 @@ static struct config_int ConfigureNamesInt[] = check_effective_io_concurrency, assign_effective_io_concurrency, NULL }, + { + {"wal_skip_threshold", PGC_USERSET, RESOURCES_DISK, + gettext_noop("Size of file that can be fsync'ed in the minimum required duration."), + NULL, + GUC_UNIT_KB + }, + &wal_skip_threshold, + 64, + 0, MAX_KILOBYTES, + NULL, NULL, NULL + }, + { {"backend_flush_after", PGC_USERSET, RESOURCES_ASYNCHRONOUS, gettext_noop("Number of pages after which previously performed writes are flushed to disk."), diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 858bcb6bc9..80c2e1bafc 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -29,7 +29,6 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL #define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM #define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN #define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 8056253916..7f9736e294 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -23,7 +23,7 @@ typedef struct RewriteStateData *RewriteState; extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap, TransactionId OldestXmin, TransactionId FreezeXid, - MultiXactId MultiXactCutoff, bool use_wal); + MultiXactId MultiXactCutoff); extern void end_heap_rewrite(RewriteState state); extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple, HeapTuple newTuple); diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 7f81703b78..b652cd6cef 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -407,22 +407,6 @@ typedef struct TableAmRoutine uint8 flags, TM_FailureData *tmfd); - /* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * may for example be used to flush the relation, when the - * TABLE_INSERT_SKIP_WAL option was used. - * - * Typically callers of tuple_insert and multi_insert will just pass all - * the flags that apply to them, and each AM has to decide which of them - * make sense for it, and then only take actions in finish_bulk_insert for - * those flags, and ignore others. - * - * Optional callback. - */ - void (*finish_bulk_insert) (Relation rel, int options); - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ @@ -1087,10 +1071,6 @@ table_compute_xid_horizon_for_tuples(Relation rel, * The options bitmask allows the caller to specify options that may change the * behaviour of the AM. The AM will ignore options that it does not support. * - * If the TABLE_INSERT_SKIP_WAL option is specified, the new tuple doesn't - * need to be logged to WAL, even for a non-temp relation. It is the AMs - * choice whether this optimization is supported. - * * If the TABLE_INSERT_SKIP_FSM option is specified, AMs are free to not reuse * free space in the relation. This can save some cycles when we know the * relation is new and doesn't contain useful amounts of free space. @@ -1112,8 +1092,7 @@ table_compute_xid_horizon_for_tuples(Relation rel, * heap's TOAST table, too, if the tuple requires any out-of-line data. * * The BulkInsertState object (if any; bistate can be NULL for default - * behavior) is also just passed through to RelationGetBufferForTuple. If - * `bistate` is provided, table_finish_bulk_insert() needs to be called. + * behavior) is also just passed through to RelationGetBufferForTuple. * * On return the slot's tts_tid and tts_tableOid are updated to reflect the * insertion. But note that any toasting of fields within the slot is NOT @@ -1248,6 +1227,8 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, * update was done. However, any TOAST changes in the new tuple's * data are not reflected into *newtup. * + * See table_insert about skipping WAL-logging feature. + * * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, * t_xmax, and, if possible, t_cmax. See comments for struct TM_FailureData * for additional info. @@ -1308,21 +1289,6 @@ table_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, flags, tmfd); } -/* - * Perform operations necessary to complete insertions made via - * tuple_insert and multi_insert with a BulkInsertState specified. This - * e.g. may e.g. used to flush the relation when inserting with - * TABLE_INSERT_SKIP_WAL specified. - */ -static inline void -table_finish_bulk_insert(Relation rel, int options) -{ - /* optional callback */ - if (rel->rd_tableam && rel->rd_tableam->finish_bulk_insert) - rel->rd_tableam->finish_bulk_insert(rel, options); -} - - /* ------------------------------------------------------------------------ * DDL related functionality. * ------------------------------------------------------------------------ diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h index 3579d3f3eb..24e71651c3 100644 --- a/src/include/catalog/storage.h +++ b/src/include/catalog/storage.h @@ -19,6 +19,16 @@ #include "storage/smgr.h" #include "utils/relcache.h" +/* enum for operation type of PendingDelete entries */ +typedef enum PendingOpType +{ + PENDING_DELETE, + PENDING_SYNC +} PendingOpType; + +/* GUC variables */ +extern int wal_skip_threshold; /* threshold for WAL-skipping */ + extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence); extern void RelationDropStorage(Relation rel); extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit); @@ -31,7 +41,9 @@ extern void RelationCopyStorage(SMgrRelation src, SMgrRelation dst, * naming */ extern void smgrDoPendingDeletes(bool isCommit); +extern void smgrDoPendingSyncs(bool isCommit, bool sync_all); extern int smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr); +extern int smgrGetPendingSyncs(bool forCommit, RelFileNode **ptr); 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 17b97f7e38..f31a36de17 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -189,6 +189,7 @@ 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, int nforks, BlockNumber *firstDelBlock); diff --git a/src/include/storage/md.h b/src/include/storage/md.h index c0f05e23ff..2bb2947bdb 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -42,6 +42,7 @@ extern void mdtruncate(SMgrRelation reln, ForkNumber forknum, extern void mdimmedsync(SMgrRelation reln, ForkNumber forknum); extern void ForgetDatabaseSyncRequests(Oid dbid); +extern void SyncRelationFiles(RelFileNode *syncrels, int nsyncrels); extern void DropRelationFiles(RelFileNode *delrels, int ndelrels, bool isRedo); /* md sync callbacks */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index a5cf804f9f..b2062efa63 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -75,10 +75,17 @@ typedef struct RelationData * transaction, with one of them occurring in a subsequently aborted * subtransaction, e.g. BEGIN; TRUNCATE t; SAVEPOINT save; TRUNCATE t; * ROLLBACK TO save; -- rd_newRelfilenodeSubid is now forgotten + * rd_firstRelfilenodeSubid is the ID of the first subtransaction the + * relfilenode change has took place in the current transaction. Unlike + * newRelfilenodeSubid, this won't be accidentially forgotten. A valid OID + * means that the currently active relfilenode is transaction-local and we + * sync the relation at commit instead of WAL-logging. */ SubTransactionId rd_createSubid; /* rel was created in current xact */ SubTransactionId rd_newRelfilenodeSubid; /* new relfilenode assigned in * current xact */ + SubTransactionId rd_firstRelfilenodeSubid; /* new relfilenode assigned + * first in current xact */ Form_pg_class rd_rel; /* RELATION tuple */ TupleDesc rd_att; /* tuple descriptor */ @@ -517,9 +524,15 @@ typedef struct ViewOptions /* * RelationNeedsWAL * True if relation needs WAL. - */ -#define RelationNeedsWAL(relation) \ - ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) + * + * Returns false if wal_level = minimal and this relation is created or + * truncated in the current transaction. + */ +#define RelationNeedsWAL(relation) \ + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT && \ + (XLogIsNeeded() || \ + (relation->rd_createSubid == InvalidSubTransactionId && \ + relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId))) /* * RelationUsesLocalBuffers -- 2.23.0 ----Next_Part(Fri_Oct_25_13_12_51_2019_465)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v21-0003-Fix-MarkBufferDirtyHint.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH v21 4/8] Row pattern recognition patch (planner). @ 2024-08-26 04:32 Tatsuo Ishii <[email protected]> 0 siblings, 0 replies; 19+ messages in thread From: Tatsuo Ishii @ 2024-08-26 04:32 UTC (permalink / raw) --- src/backend/optimizer/plan/createplan.c | 24 +++++++++++++++----- src/backend/optimizer/plan/planner.c | 3 +++ src/backend/optimizer/plan/setrefs.c | 27 ++++++++++++++++++++++- src/backend/optimizer/prep/prepjointree.c | 9 ++++++++ src/include/nodes/plannodes.h | 19 ++++++++++++++++ 5 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 8e0e5977a9..a1b2c71364 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -288,9 +288,11 @@ static WindowAgg *make_windowagg(List *tlist, Index winref, int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, Oid *ordCollations, int frameOptions, Node *startOffset, Node *endOffset, Oid startInRangeFunc, Oid endInRangeFunc, - Oid inRangeColl, bool inRangeAsc, bool inRangeNullsFirst, - List *runCondition, List *qual, bool topWindow, - Plan *lefttree); + Oid inRangeColl, bool inRangeAsc, bool inRangeNullsFirst, List *runCondition, + RPSkipTo rpSkipTo, List *patternVariable, List *patternRegexp, + List *defineClause, + List *defineInitial, + List *qual, bool topWindow, Plan *lefttree); static Group *make_group(List *tlist, List *qual, int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators, Oid *grpCollations, Plan *lefttree); @@ -2697,6 +2699,11 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path) wc->inRangeAsc, wc->inRangeNullsFirst, best_path->runCondition, + wc->rpSkipTo, + wc->patternVariable, + wc->patternRegexp, + wc->defineClause, + wc->defineInitial, best_path->qual, best_path->topwindow, subplan); @@ -6631,8 +6638,10 @@ make_windowagg(List *tlist, Index winref, int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, Oid *ordCollations, int frameOptions, Node *startOffset, Node *endOffset, Oid startInRangeFunc, Oid endInRangeFunc, - Oid inRangeColl, bool inRangeAsc, bool inRangeNullsFirst, - List *runCondition, List *qual, bool topWindow, Plan *lefttree) + Oid inRangeColl, bool inRangeAsc, bool inRangeNullsFirst, List *runCondition, + RPSkipTo rpSkipTo, List *patternVariable, List *patternRegexp, List *defineClause, + List *defineInitial, + List *qual, bool topWindow, Plan *lefttree) { WindowAgg *node = makeNode(WindowAgg); Plan *plan = &node->plan; @@ -6658,6 +6667,11 @@ make_windowagg(List *tlist, Index winref, node->inRangeAsc = inRangeAsc; node->inRangeNullsFirst = inRangeNullsFirst; node->topWindow = topWindow; + node->rpSkipTo = rpSkipTo, + node->patternVariable = patternVariable; + node->patternRegexp = patternRegexp; + node->defineClause = defineClause; + node->defineInitial = defineInitial; plan->targetlist = tlist; plan->lefttree = lefttree; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index b5827d3980..ccb66336a8 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -870,6 +870,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root, EXPRKIND_LIMIT); wc->endOffset = preprocess_expression(root, wc->endOffset, EXPRKIND_LIMIT); + wc->defineClause = (List *) preprocess_expression(root, + (Node *) wc->defineClause, + EXPRKIND_TARGET); } parse->limitOffset = preprocess_expression(root, parse->limitOffset, diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 7aed84584c..7d726729ae 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -210,7 +210,6 @@ static List *set_windowagg_runcondition_references(PlannerInfo *root, List *runcondition, Plan *plan); - /***************************************************************************** * * SUBPLAN REFERENCES @@ -2471,6 +2470,32 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset) NRM_EQUAL, NUM_EXEC_QUAL(plan)); + /* + * Modifies an expression tree in each DEFINE clause so that all Var + * nodes's varno refers to OUTER_VAR. + */ + if (IsA(plan, WindowAgg)) + { + WindowAgg *wplan = (WindowAgg *) plan; + + if (wplan->defineClause != NIL) + { + foreach(l, wplan->defineClause) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + + tle->expr = (Expr *) + fix_upper_expr(root, + (Node *) tle->expr, + subplan_itlist, + OUTER_VAR, + rtoffset, + NRM_EQUAL, + NUM_EXEC_QUAL(plan)); + } + } + } + pfree(subplan_itlist); } diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 969e257f70..3cedb5c8e5 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -2175,6 +2175,15 @@ perform_pullup_replace_vars(PlannerInfo *root, parse->returningList = (List *) pullup_replace_vars((Node *) parse->returningList, rvcontext); + foreach(lc, parse->windowClause) + { + WindowClause *wc = lfirst_node(WindowClause, lc); + + if (wc->defineClause != NIL) + wc->defineClause = (List *) + pullup_replace_vars((Node *) wc->defineClause, rvcontext); + } + if (parse->onConflict) { parse->onConflict->onConflictSet = (List *) diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 62cd6a6666..b7b2ac4aaf 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -20,6 +20,7 @@ #include "lib/stringinfo.h" #include "nodes/bitmapset.h" #include "nodes/lockoptions.h" +#include "nodes/parsenodes.h" #include "nodes/primnodes.h" @@ -1099,6 +1100,24 @@ typedef struct WindowAgg /* nulls sort first for in_range tests? */ bool inRangeNullsFirst; + /* Row Pattern Recognition AFTER MACH SKIP clause */ + RPSkipTo rpSkipTo; /* Row Pattern Skip To type */ + + /* Row Pattern PATTERN variable name (list of String) */ + List *patternVariable; + + /* + * Row Pattern RPATTERN regular expression quantifier ('+' or ''. list of + * String) + */ + List *patternRegexp; + + /* Row Pattern DEFINE clause (list of TargetEntry) */ + List *defineClause; + + /* Row Pattern DEFINE variable initial names (list of String) */ + List *defineInitial; + /* * false for all apart from the WindowAgg that's closest to the root of * the plan -- 2.25.1 ----Next_Part(Mon_Aug_26_13_39_47_2024_878)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v21-0005-Row-pattern-recognition-patch-executor.patch" ^ permalink raw reply [nested|flat] 19+ messages in thread
end of thread, other threads:[~2024-08-26 04:32 UTC | newest] Thread overview: 19+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2018-10-11 07:18 [PATCH 5/8] Fix WAL skipping feature. Kyotaro Horiguchi <[email protected]> 2018-10-11 07:18 [PATCH 5/7] Fix WAL skipping feature. Kyotaro Horiguchi <[email protected]> 2018-10-11 07:18 [PATCH 4/4] Fix WAL skipping feature. Kyotaro Horiguchi <[email protected]> 2018-10-11 07:18 [PATCH 4/4] Fix WAL skipping feature. Kyotaro Horiguchi <[email protected]> 2018-10-11 07:18 [PATCH 4/4] Fix WAL skipping feature. Kyotaro Horiguchi <[email protected]> 2018-10-11 07:18 [PATCH 4/4] Fix WAL skipping feature. Kyotaro Horiguchi <[email protected]> 2018-10-11 07:18 [PATCH 4/4] Fix WAL skipping feature. Kyotaro Horiguchi <[email protected]> 2019-04-02 04:29 [PATCH 6/7] Fix WAL skipping feature. Kyotaro Horiguchi <[email protected]> 2019-04-02 04:29 [PATCH 6/7] Fix WAL skipping feature. Kyotaro Horiguchi <[email protected]> 2019-05-20 06:38 [PATCH 2/2] Fix WAL skipping feature Kyotaro Horiguchi <[email protected]> 2019-05-20 06:38 [PATCH 2/2] Fix WAL skipping feature Kyotaro Horiguchi <[email protected]> 2019-05-20 06:38 [PATCH 2/2] Fix WAL skipping feature Kyotaro Horiguchi <[email protected]> 2019-05-27 07:06 [PATCH 2/3] Fix WAL skipping feature Kyotaro Horiguchi <[email protected]> 2019-05-27 07:06 [PATCH 2/3] Fix WAL skipping feature Kyotaro Horiguchi <[email protected]> 2019-08-21 04:57 [PATCH 2/3] Fix WAL skipping feature Kyotaro Horiguchi <[email protected]> 2019-08-21 04:57 [PATCH 2/4] Fix WAL skipping feature Kyotaro Horiguchi <[email protected]> 2019-10-25 03:07 [PATCH v23 2/5] Fix WAL skipping feature Kyotaro Horiguchi <[email protected]> 2019-10-25 03:07 [PATCH v21 2/5] Fix WAL skipping feature Kyotaro Horiguchi <[email protected]> 2024-08-26 04:32 [PATCH v21 4/8] Row pattern recognition patch (planner). Tatsuo Ishii <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox