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;