diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08353cb343..f937b8df14 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -1100,6 +1100,10 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser Waiting to execute txid_status or update the oldest transaction id available to it. + + IndexHintXMinDbHashLock + Waiting to read or update information about lastest index hint LSN. + clog Waiting for I/O on a clog (transaction status) buffer. @@ -2722,6 +2726,12 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i Number of queries in this database that have been canceled due to old snapshots + + confl_indexhint + bigint + Number of queries in this database that have been canceled due to + too new index hint bits + confl_bufferpin bigint diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c index a4edcc77e9..3ea5fa15e9 100644 --- a/src/backend/access/gist/gistget.c +++ b/src/backend/access/gist/gistget.c @@ -22,6 +22,7 @@ #include "pgstat.h" #include "storage/lmgr.h" #include "storage/predicate.h" +#include "storage/standby.h" #include "utils/float.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -87,6 +88,7 @@ gistkillitems(IndexScanDesc scan) if (killedsomething) { GistMarkPageHasGarbage(page); + LogIndexHintIfNeeded(scan->indexRelation, so->killedItemsXmax); MarkBufferDirtyHint(buffer, true); } @@ -666,8 +668,13 @@ gistgettuple(IndexScanDesc scan, ScanDirection dir) MemoryContextSwitchTo(oldCxt); } if (so->numKilled < MaxIndexTuplesPerPage) + { so->killedItems[so->numKilled++] = so->pageData[so->curPageData - 1].offnum; + + if (!TransactionIdFollowsOrEquals(so->killedItemsXmax, scan->kill_prior_tuple_xmax)) + so->killedItemsXmax = scan->kill_prior_tuple_xmax; + } } /* continuing to return tuples from a leaf page */ scan->xs_heaptid = so->pageData[so->curPageData].heapPtr; @@ -703,8 +710,13 @@ gistgettuple(IndexScanDesc scan, ScanDirection dir) MemoryContextSwitchTo(oldCxt); } if (so->numKilled < MaxIndexTuplesPerPage) + { so->killedItems[so->numKilled++] = so->pageData[so->curPageData - 1].offnum; + + if (!TransactionIdFollowsOrEquals(so->killedItemsXmax, scan->kill_prior_tuple_xmax)) + so->killedItemsXmax = scan->kill_prior_tuple_xmax; + } } /* find and process the next index page */ do diff --git a/src/backend/access/gist/gistscan.c b/src/backend/access/gist/gistscan.c index b8aa77f70f..b4e69b44c9 100644 --- a/src/backend/access/gist/gistscan.c +++ b/src/backend/access/gist/gistscan.c @@ -107,6 +107,7 @@ gistbeginscan(Relation r, int nkeys, int norderbys) } so->killedItems = NULL; /* until needed */ + so->killedItemsXmax = InvalidTransactionId; so->numKilled = 0; so->curBlkno = InvalidBlockNumber; so->curPageLSN = InvalidXLogRecPtr; diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 4871b7ff4d..9bbb6f40c5 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -308,7 +308,12 @@ hashgettuple(IndexScanDesc scan, ScanDirection dir) palloc(MaxIndexTuplesPerPage * sizeof(int)); if (so->numKilled < MaxIndexTuplesPerPage) + { so->killedItems[so->numKilled++] = so->currPos.itemIndex; + + if (!TransactionIdFollowsOrEquals(so->killedItemsXmax, scan->kill_prior_tuple_xmax)) + so->killedItemsXmax = scan->kill_prior_tuple_xmax; + } } /* @@ -377,6 +382,7 @@ hashbeginscan(Relation rel, int nkeys, int norderbys) so->killedItems = NULL; so->numKilled = 0; + so->killedItemsXmax = InvalidTransactionId; scan->opaque = so; diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index 9efc8016bc..82154c43a9 100644 --- a/src/backend/access/hash/hashutil.c +++ b/src/backend/access/hash/hashutil.c @@ -18,6 +18,7 @@ #include "access/reloptions.h" #include "access/relscan.h" #include "storage/buf_internals.h" +#include "storage/standby.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -625,6 +626,7 @@ _hash_kill_items(IndexScanDesc scan) if (killedsomething) { opaque->hasho_flag |= LH_PAGE_HAS_DEAD_TUPLES; + LogIndexHintIfNeeded(rel, so->killedItemsXmax); MarkBufferDirtyHint(buf, true); } diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index db6fad76bc..8f8cb99075 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -1527,7 +1527,7 @@ heap_fetch(Relation relation, bool heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, Snapshot snapshot, HeapTuple heapTuple, - bool *all_dead, bool first_call) + bool *all_dead, bool first_call, TransactionId *all_dead_xmax) { Page dp = (Page) BufferGetPage(buffer); TransactionId prev_xmax = InvalidTransactionId; @@ -1539,7 +1539,10 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, /* If this is not the first call, previous call returned a (live!) tuple */ if (all_dead) + { *all_dead = first_call; + if (all_dead_xmax) *all_dead_xmax = InvalidTransactionId; + } blkno = ItemPointerGetBlockNumber(tid); offnum = ItemPointerGetOffsetNumber(tid); @@ -1553,6 +1556,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, for (;;) { ItemId lp; + TransactionId xmax = InvalidTransactionId; /* check for bogus TID */ if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp)) @@ -1571,6 +1575,16 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, at_chain_start = false; continue; } + else if (ItemIdIsDead(lp) && all_dead && all_dead_xmax) + { + if (ItemIdHasStorage(lp)) + { + Assert((HeapTupleHeader)PageGetItem(dp, lp) != FrozenTransactionId); + *all_dead_xmax = HeapTupleHeaderGetXmin((HeapTupleHeader)PageGetItem(dp, lp)); + } + else + *all_dead_xmax = InvalidTransactionId; + } /* else must be end of chain */ break; } @@ -1635,9 +1649,13 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, * Note: if you change the criterion here for what is "dead", fix the * planner's get_actual_variable_range() function to match. */ - if (all_dead && *all_dead && - !HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin)) - *all_dead = false; + if (all_dead && *all_dead) + { + if (!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin, &xmax)) + *all_dead = false; + else if (all_dead_xmax && !TransactionIdFollowsOrEquals(*all_dead_xmax, xmax)) + *all_dead_xmax = xmax; + } /* * Check to see if HOT chain continues past this tuple; if so fetch diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 3fa4b766db..6e97b0e9f5 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -112,7 +112,8 @@ heapam_index_fetch_tuple(struct IndexFetchTableData *scan, ItemPointer tid, Snapshot snapshot, TupleTableSlot *slot, - bool *call_again, bool *all_dead) + bool *call_again, bool *all_dead, + TransactionId *all_dead_xmax) { IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan; BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; @@ -145,7 +146,8 @@ heapam_index_fetch_tuple(struct IndexFetchTableData *scan, snapshot, &bslot->base.tupdata, all_dead, - !*call_again); + !*call_again, + all_dead_xmax); bslot->base.tupdata.t_self = *tid; LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_UNLOCK); @@ -2140,7 +2142,7 @@ heapam_scan_bitmap_next_block(TableScanDesc scan, ItemPointerSet(&tid, page, offnum); if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot, - &heapTuple, NULL, true)) + &heapTuple, NULL, true, NULL)) hscan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid); } } diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c index dba10890aa..84ee006d40 100644 --- a/src/backend/access/heap/heapam_visibility.c +++ b/src/backend/access/heap/heapam_visibility.c @@ -1418,7 +1418,7 @@ HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot, * if the tuple is removable. */ bool -HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin) +HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin, TransactionId *TupleXmax) { HeapTupleHeader tuple = htup->t_data; @@ -1459,7 +1459,12 @@ HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin) return false; /* Deleter committed, so tuple is dead if the XID is old enough. */ - return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin); + if (TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin)) + { + if (TupleXmax) *TupleXmax = HeapTupleHeaderGetRawXmax(tuple); + return true; + } + else return false; } /* diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c index c16eb05416..03fbf537e7 100644 --- a/src/backend/access/index/genam.c +++ b/src/backend/access/index/genam.c @@ -28,6 +28,7 @@ #include "lib/stringinfo.h" #include "miscadmin.h" #include "storage/bufmgr.h" +#include "storage/proc.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -115,8 +116,23 @@ RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys) * should not be altered by index AMs. */ scan->kill_prior_tuple = false; - scan->xactStartedInRecovery = TransactionStartedDuringRecovery(); - scan->ignore_killed_tuples = !scan->xactStartedInRecovery; + scan->kill_prior_tuple_xmax = InvalidTransactionId; + if (!RecoveryInProgress()) + { + scan->ignore_killed_tuples = true; + } + else + { + if (LWLockConditionalAcquire(ProcArrayLock, LW_SHARED)) // TODO: is it really required? + { + scan->ignore_killed_tuples = MyProc->indexIgnoreKilledTuples; + LWLockRelease(ProcArrayLock); + } + else + { + scan->ignore_killed_tuples = false; + } + } scan->opaque = NULL; diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index a5210d0b34..b3b6e78702 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -301,6 +301,7 @@ index_rescan(IndexScanDesc scan, table_index_fetch_reset(scan->xs_heapfetch); scan->kill_prior_tuple = false; /* for safety */ + scan->kill_prior_tuple_xmax = InvalidTransactionId; scan->xs_heap_continue = false; scan->indexRelation->rd_indam->amrescan(scan, keys, nkeys, @@ -378,6 +379,7 @@ index_restrpos(IndexScanDesc scan) table_index_fetch_reset(scan->xs_heapfetch); scan->kill_prior_tuple = false; /* for safety */ + scan->kill_prior_tuple_xmax = InvalidTransactionId; scan->xs_heap_continue = false; scan->indexRelation->rd_indam->amrestrpos(scan); @@ -525,6 +527,7 @@ index_getnext_tid(IndexScanDesc scan, ScanDirection direction) /* Reset kill flag immediately for safety */ scan->kill_prior_tuple = false; + scan->kill_prior_tuple_xmax = InvalidTransactionId; scan->xs_heap_continue = false; /* If we're out of index entries, we're done */ @@ -567,10 +570,11 @@ index_fetch_heap(IndexScanDesc scan, TupleTableSlot *slot) { bool all_dead = false; bool found; + TransactionId kill_prior_tuple_xmax = InvalidTransactionId; found = table_index_fetch_tuple(scan->xs_heapfetch, &scan->xs_heaptid, scan->xs_snapshot, slot, - &scan->xs_heap_continue, &all_dead); + &scan->xs_heap_continue, &all_dead, &kill_prior_tuple_xmax); if (found) pgstat_count_heap_fetch(scan->indexRelation); @@ -578,12 +582,13 @@ index_fetch_heap(IndexScanDesc scan, TupleTableSlot *slot) /* * If we scanned a whole HOT chain and found only dead tuples, tell index * AM to kill its entry for that TID (this will take effect in the next - * amgettuple call, in index_getnext_tid). We do not do this when in - * recovery because it may violate MVCC to do so. See comments in - * RelationGetIndexScan(). + * amgettuple call, in index_getnext_tid). */ - if (!scan->xactStartedInRecovery) + if (scan->ignore_killed_tuples) + { scan->kill_prior_tuple = all_dead; + scan->kill_prior_tuple_xmax = kill_prior_tuple_xmax; + } return found; } diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c index 4e5849ab8e..290d476926 100644 --- a/src/backend/access/nbtree/nbtinsert.c +++ b/src/backend/access/nbtree/nbtinsert.c @@ -24,6 +24,7 @@ #include "storage/lmgr.h" #include "storage/predicate.h" #include "storage/smgr.h" +#include "storage/standby.h" /* Minimum tree height for application of fastpath optimization */ #define BTREE_FASTPATH_MIN_LEVEL 2 @@ -424,6 +425,7 @@ _bt_check_unique(Relation rel, BTInsertState insertstate, Relation heapRel, { ItemPointerData htid; bool all_dead; + TransactionId allHeadHtupAmax; if (_bt_compare(rel, itup_key, page, offset) != 0) break; /* we're past all the equal tuples */ @@ -451,7 +453,7 @@ _bt_check_unique(Relation rel, BTInsertState insertstate, Relation heapRel, */ else if (table_index_fetch_tuple_check(heapRel, &htid, &SnapshotDirty, - &all_dead)) + &all_dead, &allHeadHtupAmax)) { TransactionId xwait; @@ -508,7 +510,7 @@ _bt_check_unique(Relation rel, BTInsertState insertstate, Relation heapRel, */ htid = itup->t_tid; if (table_index_fetch_tuple_check(heapRel, &htid, - SnapshotSelf, NULL)) + SnapshotSelf, NULL, NULL)) { /* Normal case --- it's still live */ } @@ -579,6 +581,7 @@ _bt_check_unique(Relation rel, BTInsertState insertstate, Relation heapRel, * Mark buffer with a dirty hint, since state is not * crucial. Be sure to mark the proper buffer dirty. */ + LogIndexHintIfNeeded(rel, allHeadHtupAmax); if (nbuf != InvalidBuffer) MarkBufferDirtyHint(nbuf, true); else diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 5254bc7ef5..a0fcb27bcf 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -266,7 +266,12 @@ btgettuple(IndexScanDesc scan, ScanDirection dir) so->killedItems = (int *) palloc(MaxIndexTuplesPerPage * sizeof(int)); if (so->numKilled < MaxIndexTuplesPerPage) + { so->killedItems[so->numKilled++] = so->currPos.itemIndex; + + if (!TransactionIdFollowsOrEquals(so->killedItemsXmax, scan->kill_prior_tuple_xmax)) + so->killedItemsXmax = scan->kill_prior_tuple_xmax; + } } /* @@ -372,6 +377,7 @@ btbeginscan(Relation rel, int nkeys, int norderbys) so->arrayContext = NULL; so->killedItems = NULL; /* until needed */ + so->killedItemsXmax = InvalidTransactionId; so->numKilled = 0; /* diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index 5ab4e712f1..5321b72f29 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -28,6 +28,7 @@ #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "storage/standby.h" typedef struct BTSortArrayContext @@ -1794,6 +1795,8 @@ _bt_killitems(IndexScanDesc scan) if (killedsomething) { opaque->btpo_flags |= BTP_HAS_GARBAGE; + + LogIndexHintIfNeeded(scan->indexRelation, so->killedItemsXmax); MarkBufferDirtyHint(so->currPos.buf, true); } diff --git a/src/backend/access/rmgrdesc/standbydesc.c b/src/backend/access/rmgrdesc/standbydesc.c index 1ce2664014..6a265a1afb 100644 --- a/src/backend/access/rmgrdesc/standbydesc.c +++ b/src/backend/access/rmgrdesc/standbydesc.c @@ -36,6 +36,14 @@ standby_desc_running_xacts(StringInfo buf, xl_running_xacts *xlrec) appendStringInfoString(buf, "; subxid ovf"); } +static void +standby_desc_index_hint(StringInfo buf, xl_index_hint* xlrec) +{ + appendStringInfo(buf, "latestIndexHintXid %u db %u", + xlrec->latestIndexHintXid, + xlrec->dbId); +} + void standby_desc(StringInfo buf, XLogReaderState *record) { @@ -66,6 +74,12 @@ standby_desc(StringInfo buf, XLogReaderState *record) xlrec->dbId, xlrec->tsId, xlrec->relcacheInitFileInval); } + else if (info == XLOG_INDEX_HINT) + { + xl_index_hint* xlrec = (xl_index_hint*)rec; + + standby_desc_index_hint(buf, xlrec); + } } const char * @@ -84,6 +98,9 @@ standby_identify(uint8 info) case XLOG_INVALIDATIONS: id = "INVALIDATIONS"; break; + case XLOG_INDEX_HINT: + id = "INDEX_HINT"; + break; } return id; diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c index c814733b22..0d84ddcd5a 100644 --- a/src/backend/access/table/tableam.c +++ b/src/backend/access/table/tableam.c @@ -201,7 +201,8 @@ bool table_index_fetch_tuple_check(Relation rel, ItemPointer tid, Snapshot snapshot, - bool *all_dead) + bool *all_dead, + TransactionId *all_dead_xmax) { IndexFetchTableData *scan; TupleTableSlot *slot; @@ -211,7 +212,7 @@ table_index_fetch_tuple_check(Relation rel, slot = table_slot_create(rel, NULL); scan = table_index_fetch_begin(rel); found = table_index_fetch_tuple(scan, tid, snapshot, slot, &call_again, - all_dead); + all_dead, all_dead_xmax); table_index_fetch_end(scan); ExecDropSingleTupleTableSlot(slot); diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 5adf956f41..be55c3bdd0 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -477,6 +477,7 @@ MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid, const char *gid, proc->lwWaitMode = 0; proc->waitLock = NULL; proc->waitProcLock = NULL; + proc->indexIgnoreKilledTuples = true; for (i = 0; i < NUM_LOCK_PARTITIONS; i++) SHMQueueInit(&(proc->myProcLocks[i])); /* subxid data must be filled later by GXactLoadSubxactData */ diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index f681aafcf9..789a82c201 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -906,6 +906,7 @@ CREATE VIEW pg_stat_database_conflicts AS pg_stat_get_db_conflict_tablespace(D.oid) AS confl_tablespace, pg_stat_get_db_conflict_lock(D.oid) AS confl_lock, pg_stat_get_db_conflict_snapshot(D.oid) AS confl_snapshot, + pg_stat_get_db_conflict_indexhint(D.oid) AS confl_indexhint, pg_stat_get_db_conflict_bufferpin(D.oid) AS confl_bufferpin, pg_stat_get_db_conflict_startup_deadlock(D.oid) AS confl_deadlock FROM pg_database D; diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c index 20dee11909..3653ddd7ae 100644 --- a/src/backend/commands/constraint.c +++ b/src/backend/commands/constraint.c @@ -112,7 +112,7 @@ unique_key_recheck(PG_FUNCTION_ARGS) bool call_again = false; if (!table_index_fetch_tuple(scan, &tmptid, SnapshotSelf, slot, - &call_again, NULL)) + &call_again, NULL, NULL)) { /* * All rows referenced by the index entry are dead, so skip the diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 7169509a79..9f6e26d6f0 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -4695,6 +4695,7 @@ reset_dbentry_counters(PgStat_StatDBEntry *dbentry) dbentry->n_conflict_tablespace = 0; dbentry->n_conflict_lock = 0; dbentry->n_conflict_snapshot = 0; + dbentry->n_conflict_indexhint = 0; dbentry->n_conflict_bufferpin = 0; dbentry->n_conflict_startup_deadlock = 0; dbentry->n_temp_files = 0; @@ -6319,6 +6320,9 @@ pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len) case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT: dbentry->n_conflict_snapshot++; break; + case PROCSIG_RECOVERY_CONFLICT_INDEXHINT: + dbentry->n_conflict_indexhint++; + break; case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN: dbentry->n_conflict_bufferpin++; break; diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index 5e1dc8a651..39ad9b53f6 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -330,6 +330,7 @@ DecodeStandbyOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) } break; case XLOG_STANDBY_LOCK: + case XLOG_INDEX_HINT: break; case XLOG_INVALIDATIONS: { diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index 3089f0d5dd..d1ac98553d 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -576,6 +576,7 @@ SnapBuildInitialSnapshot(SnapBuild *builder) #endif MyPgXact->xmin = snap->xmin; + MyProc->indexIgnoreKilledTuples = true; /* allocate in transaction context */ newxip = (TransactionId *) diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c index a5e85d32f3..5e662dce55 100644 --- a/src/backend/replication/walreceiver.c +++ b/src/backend/replication/walreceiver.c @@ -227,6 +227,9 @@ WalReceiverMain(void) /* Advertise our PID so that the startup process can kill us */ walrcv->pid = MyProcPid; walrcv->walRcvState = WALRCV_STREAMING; + /* initially true so we always send at least one feedback message */ + walrcv->sender_has_standby_xmin = true; + walrcv->sender_reports_feedback_to_master = false; /* Fetch information required to start streaming */ walrcv->ready_to_display = false; @@ -833,6 +836,7 @@ XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len) XLogRecPtr walEnd; TimestampTz sendTime; bool replyRequested; + bool senderReportsFeedbackToMaster; resetStringInfo(&incoming_message); @@ -862,7 +866,7 @@ XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len) case 'k': /* Keepalive */ { /* copy message to StringInfo */ - hdrlen = sizeof(int64) + sizeof(int64) + sizeof(char); + hdrlen = sizeof(int64) + sizeof(int64) + sizeof(char) + sizeof(char); if (len != hdrlen) ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), @@ -872,8 +876,11 @@ XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len) /* read the fields */ walEnd = pq_getmsgint64(&incoming_message); sendTime = pq_getmsgint64(&incoming_message); + senderReportsFeedbackToMaster = pq_getmsgbyte(&incoming_message); replyRequested = pq_getmsgbyte(&incoming_message); + WalRcv->sender_reports_feedback_to_master = senderReportsFeedbackToMaster; + ProcessWalSndrMessage(walEnd, sendTime); /* If the primary requested a reply, send one immediately */ @@ -1155,15 +1162,13 @@ XLogWalRcvSendHSFeedback(bool immed) catalog_xmin; static TimestampTz sendTime = 0; - /* initially true so we always send at least one feedback message */ - static bool master_has_standby_xmin = true; /* * If the user doesn't want status to be reported to the master, be sure * to exit before doing anything at all. */ if ((wal_receiver_status_interval <= 0 || !hot_standby_feedback) && - !master_has_standby_xmin) + !WalRcv->sender_has_standby_xmin) return; /* Get current timestamp. */ @@ -1248,9 +1253,9 @@ XLogWalRcvSendHSFeedback(bool immed) pq_sendint32(&reply_message, catalog_xmin_epoch); walrcv_send(wrconn, reply_message.data, reply_message.len); if (TransactionIdIsValid(xmin) || TransactionIdIsValid(catalog_xmin)) - master_has_standby_xmin = true; + WalRcv->sender_has_standby_xmin = true; else - master_has_standby_xmin = false; + WalRcv->sender_has_standby_xmin = false; } /* diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index abb533b9d0..cd641c1d27 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1906,6 +1906,7 @@ PhysicalReplicationSlotNewXmin(TransactionId feedbackXmin, TransactionId feedbac SpinLockAcquire(&slot->mutex); MyPgXact->xmin = InvalidTransactionId; + MyProc->indexIgnoreKilledTuples = true; /* * For physical replication we don't need the interlock provided by xmin @@ -2092,6 +2093,8 @@ ProcessStandbyHSFeedbackMessage(void) else MyPgXact->xmin = feedbackXmin; } + + WalSndKeepalive(false); } /* @@ -3374,12 +3377,14 @@ static void WalSndKeepalive(bool requestReply) { elog(DEBUG2, "sending replication keepalive"); + bool am_report_feedback_to_master = !am_cascading_walsender || (WalRcv->sender_has_standby_xmin && WalRcv->sender_reports_feedback_to_master); /* construct the message... */ resetStringInfo(&output_message); pq_sendbyte(&output_message, 'k'); pq_sendint64(&output_message, sentPtr); pq_sendint64(&output_message, GetCurrentTimestamp()); + pq_sendbyte(&output_message, am_report_feedback_to_master ? 1 : 0); pq_sendbyte(&output_message, requestReply ? 1 : 0); /* ... and send it wrapped in CopyData */ diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 427b0d59cd..a97a92e7fb 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -263,6 +263,7 @@ CreateSharedMemoryAndSemaphores(void) BTreeShmemInit(); SyncScanShmemInit(); AsyncShmemInit(); + StandByShmemInit(); #ifdef EXEC_BACKEND diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 4a5b26c23d..f4f4ec40b2 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -62,6 +62,7 @@ #include "utils/builtins.h" #include "utils/rel.h" #include "utils/snapmgr.h" +#include "replication/walreceiver.h" #define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var)))) @@ -433,6 +434,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid) proc->lxid = InvalidLocalTransactionId; pgxact->xmin = InvalidTransactionId; + proc->indexIgnoreKilledTuples = true; /* must be cleared with xid/xmin: */ pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; pgxact->delayChkpt = false; /* be sure this is cleared in abort */ @@ -455,6 +457,7 @@ ProcArrayEndTransactionInternal(PGPROC *proc, PGXACT *pgxact, pgxact->xid = InvalidTransactionId; proc->lxid = InvalidLocalTransactionId; pgxact->xmin = InvalidTransactionId; + proc->indexIgnoreKilledTuples = true; /* must be cleared with xid/xmin: */ pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; pgxact->delayChkpt = false; /* be sure this is cleared in abort */ @@ -611,6 +614,7 @@ ProcArrayClearTransaction(PGPROC *proc) pgxact->xid = InvalidTransactionId; proc->lxid = InvalidLocalTransactionId; pgxact->xmin = InvalidTransactionId; + proc->indexIgnoreKilledTuples = true; proc->recoveryConflictPending = false; /* redundant, but just in case */ @@ -1707,7 +1711,15 @@ GetSnapshotData(Snapshot snapshot) replication_slot_catalog_xmin = procArray->replication_slot_catalog_xmin; if (!TransactionIdIsValid(MyPgXact->xmin)) + { MyPgXact->xmin = TransactionXmin = xmin; + /* + * Always use and set LP_DEAD bits on primary. In case of stand by + * only if hot_standby_feedback is enabled (to avoid unnecessary cancelations). + */ + Assert(!RecoveryInProgress() || WalRcv); + MyProc->indexIgnoreKilledTuples = !RecoveryInProgress() || (WalRcv->sender_reports_feedback_to_master && WalRcv->sender_has_standby_xmin); + } LWLockRelease(ProcArrayLock); @@ -2578,7 +2590,7 @@ GetCurrentVirtualXIDs(TransactionId limitXmin, bool excludeXmin0, * this array sufficiently often that we use malloc for the result. */ VirtualTransactionId * -GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid) +GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid, bool onlyIndexIgnoreKilledTuples) { static VirtualTransactionId *vxids; ProcArrayStruct *arrayP = procArray; @@ -2617,6 +2629,7 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid) { /* Fetch xmin just once - can't change on us, but good coding */ TransactionId pxmin = UINT32_ACCESS_ONCE(pgxact->xmin); + bool indexIgnoreKilledTuples = proc->indexIgnoreKilledTuples; /* * We ignore an invalid pxmin because this means that backend has @@ -2627,7 +2640,7 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid) * test here. */ if (!TransactionIdIsValid(limitXmin) || - (TransactionIdIsValid(pxmin) && !TransactionIdFollows(pxmin, limitXmin))) + (TransactionIdIsValid(pxmin) && !TransactionIdFollows(pxmin, limitXmin) && (!onlyIndexIgnoreKilledTuples || indexIgnoreKilledTuples))) { VirtualTransactionId vxid; diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c index 65d3946386..a2d7630dbf 100644 --- a/src/backend/storage/ipc/procsignal.c +++ b/src/backend/storage/ipc/procsignal.c @@ -558,6 +558,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS) if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT)) RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT); + if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_INDEXHINT)) + RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_INDEXHINT); + if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK)) RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK); diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c index 3090e57fa4..9a3dd4df85 100644 --- a/src/backend/storage/ipc/standby.c +++ b/src/backend/storage/ipc/standby.c @@ -41,6 +41,7 @@ int max_standby_archive_delay = 30 * 1000; int max_standby_streaming_delay = 30 * 1000; static HTAB *RecoveryLockLists; +static HTAB *IndexHintXMinDb; static void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist, ProcSignalReason reason); @@ -57,6 +58,12 @@ typedef struct RecoveryLockListsEntry List *locks; } RecoveryLockListsEntry; +typedef struct IndexHintXMinDbEntry +{ + Oid dbOid; + TransactionId oldestXMin; +} IndexHintXMinDbEntry; + /* * InitRecoveryTransactionEnvironment * Initialize tracking of in-progress transactions in master @@ -290,6 +297,43 @@ ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist, } } +static bool +IsNewerIndexHintXid(Oid dbOid, TransactionId latestIndexHintXid) +{ + bool found, result; + IndexHintXMinDbEntry* entry; + + LWLockAcquire(IndexHintXMinDbHashLock, LW_SHARED); + entry = (IndexHintXMinDbEntry*)hash_search(IndexHintXMinDb, + &dbOid, + HASH_FIND, + &found); + result = !found || TransactionIdPrecedes(entry->oldestXMin, latestIndexHintXid); + LWLockRelease(IndexHintXMinDbHashLock); + + return result; +} + +static void +UpsertLatestIndexHintXid(Oid dbOid, TransactionId latestIndexHintXid) +{ + + bool found; + IndexHintXMinDbEntry* entry; + + LWLockAcquire(IndexHintXMinDbHashLock, LW_EXCLUSIVE); + + entry = (IndexHintXMinDbEntry*)hash_search(IndexHintXMinDb, + &dbOid, + HASH_ENTER, + &found); + + if (!found || TransactionIdPrecedes(entry->oldestXMin, latestIndexHintXid)) + entry->oldestXMin = latestIndexHintXid; + + LWLockRelease(IndexHintXMinDbHashLock); +} + void ResolveRecoveryConflictWithSnapshot(TransactionId latestRemovedXid, RelFileNode node) { @@ -308,10 +352,12 @@ ResolveRecoveryConflictWithSnapshot(TransactionId latestRemovedXid, RelFileNode return; backends = GetConflictingVirtualXIDs(latestRemovedXid, - node.dbNode); + node.dbNode, false); ResolveRecoveryConflictWithVirtualXIDs(backends, PROCSIG_RECOVERY_CONFLICT_SNAPSHOT); + + UpsertLatestIndexHintXid(node.dbNode, latestRemovedXid); } void @@ -337,7 +383,7 @@ ResolveRecoveryConflictWithTablespace(Oid tsid) * We don't wait for commit because drop tablespace is non-transactional. */ temp_file_users = GetConflictingVirtualXIDs(InvalidTransactionId, - InvalidOid); + InvalidOid, false); ResolveRecoveryConflictWithVirtualXIDs(temp_file_users, PROCSIG_RECOVERY_CONFLICT_TABLESPACE); } @@ -827,6 +873,22 @@ standby_redo(XLogReaderState *record) xlrec->dbId, xlrec->tsId); } + else if (info == XLOG_INDEX_HINT) + { + xl_index_hint* xlrec = (xl_index_hint*)XLogRecGetData(record); + if (InHotStandby) + { + if (IsNewerIndexHintXid(xlrec->dbId, xlrec->latestIndexHintXid)) + { + VirtualTransactionId* backends = GetConflictingVirtualXIDs(xlrec->latestIndexHintXid, + xlrec->dbId, + true); + ResolveRecoveryConflictWithVirtualXIDs(backends, PROCSIG_RECOVERY_CONFLICT_INDEXHINT); + + UpsertLatestIndexHintXid(xlrec->dbId, xlrec->latestIndexHintXid); + } + } + } else elog(PANIC, "standby_redo: unknown op code %u", info); } @@ -1094,3 +1156,46 @@ LogStandbyInvalidations(int nmsgs, SharedInvalidationMessage *msgs, nmsgs * sizeof(SharedInvalidationMessage)); XLogInsert(RM_STANDBY_ID, XLOG_INVALIDATIONS); } + +void +LogIndexHint(Oid dbId, TransactionId latestIndexHintXid) +{ + xl_index_hint xlrec; + + xlrec.dbId = dbId; + xlrec.latestIndexHintXid = latestIndexHintXid; + + XLogBeginInsert(); + XLogRegisterData((char*)&xlrec, sizeof(xl_index_hint)); + + XLogInsert(RM_STANDBY_ID, XLOG_INDEX_HINT); +} + +extern void LogIndexHintIfNeeded(Relation rel, TransactionId oldestXmin) +{ + if (!RecoveryInProgress() && XLogStandbyInfoActive() && TransactionIdIsValid(oldestXmin)) { + if (IsNewerIndexHintXid(rel->rd_node.dbNode, oldestXmin)) + { + LogIndexHint(rel->rd_node.dbNode, oldestXmin); + UpsertLatestIndexHintXid(rel->rd_node.dbNode, oldestXmin); + } + } +} + +void +StandByShmemInit(void) +{ + HASHCTL info; + + MemSet(&info, 0, sizeof(info)); + info.keysize = sizeof(Oid); + info.entrysize = sizeof(IndexHintXMinDbEntry); + + LWLockAcquire(IndexHintXMinDbHashLock, LW_EXCLUSIVE); + + IndexHintXMinDb = ShmemInitHash("IndexHintXMinDb", + 64, 64, + &info, HASH_ELEM | HASH_BLOBS); + + LWLockRelease(IndexHintXMinDbHashLock); +} diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt index db47843229..d13ac7a512 100644 --- a/src/backend/storage/lmgr/lwlocknames.txt +++ b/src/backend/storage/lmgr/lwlocknames.txt @@ -49,3 +49,4 @@ MultiXactTruncationLock 41 OldSnapshotTimeMapLock 42 LogicalRepWorkerLock 43 CLogTruncationLock 44 +IndexHintXMinDbHashLock 45 diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index eb321f72ea..81a6e4b49c 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -389,6 +389,7 @@ InitProcess(void) MyProc->fpLocalTransactionId = InvalidLocalTransactionId; MyPgXact->xid = InvalidTransactionId; MyPgXact->xmin = InvalidTransactionId; + MyProc->indexIgnoreKilledTuples = true; MyProc->pid = MyProcPid; /* backendId, databaseId and roleId will be filled in later */ MyProc->backendId = InvalidBackendId; @@ -573,6 +574,7 @@ InitAuxiliaryProcess(void) MyProc->fpLocalTransactionId = InvalidLocalTransactionId; MyPgXact->xid = InvalidTransactionId; MyPgXact->xmin = InvalidTransactionId; + MyProc->indexIgnoreKilledTuples = true; MyProc->backendId = InvalidBackendId; MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 0a6f80963b..4e4bdb4420 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -2441,6 +2441,9 @@ errdetail_recovery_conflict(void) case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT: errdetail("User query might have needed to see row versions that must be removed."); break; + case PROCSIG_RECOVERY_CONFLICT_INDEXHINT: + errdetail("User query might have see index hint bits that are newer."); + break; case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK: errdetail("User transaction caused buffer deadlock with recovery."); break; @@ -2909,6 +2912,7 @@ RecoveryConflictInterrupt(ProcSignalReason reason) case PROCSIG_RECOVERY_CONFLICT_LOCK: case PROCSIG_RECOVERY_CONFLICT_TABLESPACE: case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT: + case PROCSIG_RECOVERY_CONFLICT_INDEXHINT: /* * If we aren't in a transaction any longer then ignore. diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 7e6a3c1774..b25dc41553 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -1469,6 +1469,21 @@ pg_stat_get_db_conflict_snapshot(PG_FUNCTION_ARGS) PG_RETURN_INT64(result); } +Datum +pg_stat_get_db_conflict_indexhint(PG_FUNCTION_ARGS) +{ + Oid dbid = PG_GETARG_OID(0); + int64 result; + PgStat_StatDBEntry* dbentry; + + if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL) + result = 0; + else + result = (int64)(dbentry->n_conflict_indexhint); + + PG_RETURN_INT64(result); +} + Datum pg_stat_get_db_conflict_bufferpin(PG_FUNCTION_ARGS) { @@ -1512,6 +1527,7 @@ pg_stat_get_db_conflict_all(PG_FUNCTION_ARGS) result = (int64) (dbentry->n_conflict_tablespace + dbentry->n_conflict_lock + dbentry->n_conflict_snapshot + + dbentry->n_conflict_indexhint + dbentry->n_conflict_bufferpin + dbentry->n_conflict_startup_deadlock); diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index 1c063c592c..fcac777d59 100644 --- a/src/backend/utils/time/snapmgr.c +++ b/src/backend/utils/time/snapmgr.c @@ -1016,6 +1016,7 @@ SnapshotResetXmin(void) if (pairingheap_is_empty(&RegisteredSnapshots)) { + MyProc->indexIgnoreKilledTuples = true; MyPgXact->xmin = InvalidTransactionId; return; } diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h index 18f2b0d98e..d6630b25c2 100644 --- a/src/include/access/gist_private.h +++ b/src/include/access/gist_private.h @@ -167,6 +167,7 @@ typedef struct GISTScanOpaqueData /* info about killed items if any (killedItems is NULL if never used) */ OffsetNumber *killedItems; /* offset numbers of killed items */ int numKilled; /* number of currently stored items */ + TransactionId killedItemsXmax; BlockNumber curBlkno; /* current number of block */ GistNSN curPageLSN; /* pos in the WAL stream when page was read */ diff --git a/src/include/access/hash.h b/src/include/access/hash.h index 9fc0696096..7b2fd9f84a 100644 --- a/src/include/access/hash.h +++ b/src/include/access/hash.h @@ -179,6 +179,7 @@ typedef struct HashScanOpaqueData /* info about killed items if any (killedItems is NULL if never used) */ int *killedItems; /* currPos.items indexes of killed items */ int numKilled; /* number of currently stored items */ + TransactionId killedItemsXmax; /* * Identify all the matching items on a page and save them in diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 47fda28daa..8c04800194 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -127,7 +127,7 @@ extern bool heap_fetch(Relation relation, Snapshot snapshot, HeapTuple tuple, Buffer *userbuf); extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, Snapshot snapshot, HeapTuple heapTuple, - bool *all_dead, bool first_call); + bool *all_dead, bool first_call, TransactionId* all_dead_xmax); extern void heap_get_latest_tid(TableScanDesc scan, ItemPointer tid); extern void setLastTid(const ItemPointer tid); @@ -208,7 +208,7 @@ extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer, uint16 infomask, TransactionId xid); extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple); extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot); -extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin); +extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin, TransactionId *TupleXmax); /* * To avoid leaking too much knowledge about reorderbuffer implementation diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 20ace69dab..f7e218eea9 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -644,6 +644,7 @@ typedef struct BTScanOpaqueData /* info about killed items if any (killedItems is NULL if never used) */ int *killedItems; /* currPos.items indexes of killed items */ int numKilled; /* number of currently stored items */ + TransactionId killedItemsXmax; /* * If we are doing an index-only scan, these are the tuple storage diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h index 6f0258831f..a5c3f9fcd6 100644 --- a/src/include/access/relscan.h +++ b/src/include/access/relscan.h @@ -110,10 +110,10 @@ typedef struct IndexScanDescData bool xs_temp_snap; /* unregister snapshot at scan end? */ /* signaling to index AM about killing index tuples */ - bool kill_prior_tuple; /* last-returned tuple is dead */ - bool ignore_killed_tuples; /* do not return killed entries */ - bool xactStartedInRecovery; /* prevents killing/seeing killed - * tuples */ + bool kill_prior_tuple; /* last-returned tuple is dead */ + TransactionId kill_prior_tuple_xmax; /* last-returned tuple xmax */ + bool ignore_killed_tuples; /* enables killing tuples and + * prevents seeing killed tuples */ /* index access method's private state */ void *opaque; /* access-method-specific info */ diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 5a663975b9..31a40929de 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -302,7 +302,8 @@ typedef struct TableAmRoutine ItemPointer tid, Snapshot snapshot, TupleTableSlot *slot, - bool *call_again, bool *all_dead); + bool *call_again, bool *all_dead, + TransactionId* all_dead_xmax); /* ------------------------------------------------------------------------ @@ -1014,12 +1015,12 @@ table_index_fetch_tuple(struct IndexFetchTableData *scan, ItemPointer tid, Snapshot snapshot, TupleTableSlot *slot, - bool *call_again, bool *all_dead) + bool *call_again, bool *all_dead, TransactionId *all_dead_xmax) { return scan->rel->rd_tableam->index_fetch_tuple(scan, tid, snapshot, slot, call_again, - all_dead); + all_dead, all_dead_xmax); } /* @@ -1031,7 +1032,8 @@ table_index_fetch_tuple(struct IndexFetchTableData *scan, extern bool table_index_fetch_tuple_check(Relation rel, ItemPointer tid, Snapshot snapshot, - bool *all_dead); + bool *all_dead, + TransactionId *all_dead_xmax); /* ------------------------------------------------------------------------ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 226c904c04..76a59a98af 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5320,6 +5320,11 @@ proname => 'pg_stat_get_db_conflict_snapshot', provolatile => 's', proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', prosrc => 'pg_stat_get_db_conflict_snapshot' }, +{ oid => '8469', + descr => 'statistics: recovery conflicts in database caused by index hint bits', + proname => 'pg_stat_get_db_conflict_indexhint', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_db_conflict_indexhint' }, { oid => '3068', descr => 'statistics: recovery conflicts in database caused by shared buffer pin', proname => 'pg_stat_get_db_conflict_bufferpin', provolatile => 's', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index aecb6013f0..029ecd43a0 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -603,6 +603,7 @@ typedef struct PgStat_StatDBEntry PgStat_Counter n_conflict_tablespace; PgStat_Counter n_conflict_lock; PgStat_Counter n_conflict_snapshot; + PgStat_Counter n_conflict_indexhint; PgStat_Counter n_conflict_bufferpin; PgStat_Counter n_conflict_startup_deadlock; PgStat_Counter n_temp_files; diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h index e08afc6548..399855be3e 100644 --- a/src/include/replication/walreceiver.h +++ b/src/include/replication/walreceiver.h @@ -148,6 +148,10 @@ typedef struct * store semantics, so use sig_atomic_t. */ sig_atomic_t force_reply; /* used as a bool */ + + sig_atomic_t sender_has_standby_xmin; /* used as a bool */ + + sig_atomic_t sender_reports_feedback_to_master; /* used as a bool */ } WalRcvData; extern WalRcvData *WalRcv; diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index d21780108b..c6c5d4079b 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -125,6 +125,7 @@ struct PGPROC * though not required. Accessed without lock, if needed. */ bool recoveryConflictPending; + bool indexIgnoreKilledTuples; /* Info about LWLock the process is currently waiting for, if any. */ bool lwWaiting; /* true if waiting for an LW lock */ diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h index a5c7d0c064..5a125fa604 100644 --- a/src/include/storage/procarray.h +++ b/src/include/storage/procarray.h @@ -103,7 +103,8 @@ extern bool IsBackendPid(int pid); extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin, bool excludeXmin0, bool allDbs, int excludeVacuum, int *nvxids); -extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid); +extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid, + bool onlyIndexIgnoreKilledTuples); extern pid_t CancelVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode); extern bool MinimumActiveBackends(int min); diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h index 90607df106..33dcb62e90 100644 --- a/src/include/storage/procsignal.h +++ b/src/include/storage/procsignal.h @@ -39,6 +39,7 @@ typedef enum PROCSIG_RECOVERY_CONFLICT_TABLESPACE, PROCSIG_RECOVERY_CONFLICT_LOCK, PROCSIG_RECOVERY_CONFLICT_SNAPSHOT, + PROCSIG_RECOVERY_CONFLICT_INDEXHINT, PROCSIG_RECOVERY_CONFLICT_BUFFERPIN, PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK, diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h index cfbe426e5a..0d2d5c6af9 100644 --- a/src/include/storage/standby.h +++ b/src/include/storage/standby.h @@ -18,6 +18,7 @@ #include "storage/procsignal.h" #include "storage/relfilenode.h" #include "storage/standbydefs.h" +#include "utils/relcache.h" /* User-settable GUC parameters */ extern int vacuum_defer_cleanup_age; @@ -81,9 +82,12 @@ typedef struct RunningTransactionsData typedef RunningTransactionsData *RunningTransactions; +extern void StandByShmemInit(void); extern void LogAccessExclusiveLock(Oid dbOid, Oid relOid); extern void LogAccessExclusiveLockPrepare(void); +extern void LogIndexHintIfNeeded(Relation rel, TransactionId oldestXmin); + extern XLogRecPtr LogStandbySnapshot(void); extern void LogStandbyInvalidations(int nmsgs, SharedInvalidationMessage *msgs, bool relcacheInitFileInval); diff --git a/src/include/storage/standbydefs.h b/src/include/storage/standbydefs.h index 4876d2eeea..97e1f4fb6d 100644 --- a/src/include/storage/standbydefs.h +++ b/src/include/storage/standbydefs.h @@ -34,6 +34,7 @@ extern void standby_desc_invalidations(StringInfo buf, #define XLOG_STANDBY_LOCK 0x00 #define XLOG_RUNNING_XACTS 0x10 #define XLOG_INVALIDATIONS 0x20 +#define XLOG_INDEX_HINT 0x30 typedef struct xl_standby_locks { @@ -71,4 +72,11 @@ typedef struct xl_invalidations #define MinSizeOfInvalidations offsetof(xl_invalidations, msgs) +typedef struct xl_index_hint +{ + TransactionId latestIndexHintXid; + Oid dbId; +} xl_index_hint; + + #endif /* STANDBYDEFS_H */ diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 634f8256f7..7be8e99303 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1845,6 +1845,7 @@ pg_stat_database_conflicts| SELECT d.oid AS datid, pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace, pg_stat_get_db_conflict_lock(d.oid) AS confl_lock, pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot, + pg_stat_get_db_conflict_indexhint(d.oid) AS confl_indexhint, pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin, pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock FROM pg_database d;