@@ -241,6 +241,12 @@ typedef struct ComputeXidHorizonsResult
241241 * session's temporary tables.
242242 */
243243 TransactionId temp_oldest_nonremovable ;
244+
245+ /* Identify what forced each of the horizons above. */
246+ OldestXminInfo shared_oldest_nonremovable_info ;
247+ OldestXminInfo catalog_oldest_nonremovable_info ;
248+ OldestXminInfo data_oldest_nonremovable_info ;
249+ OldestXminInfo temp_oldest_nonremovable_info ;
244250} ComputeXidHorizonsResult ;
245251
246252/*
@@ -1622,6 +1628,46 @@ TransactionIdIsInProgress(TransactionId xid)
16221628 return false;
16231629}
16241630
1631+ /*
1632+ * Store horizon provenance in *info if caller requested it.
1633+ *
1634+ * Callers pass NULL when they are not interested in tracking the source.
1635+ */
1636+ static inline void
1637+ OldestXminInfoSet (OldestXminInfo * info , OldestXminSource source ,
1638+ int backend_pid )
1639+ {
1640+ if (info == NULL )
1641+ return ;
1642+
1643+ info -> source = source ;
1644+ info -> backend_pid = backend_pid ;
1645+ }
1646+
1647+ /*
1648+ * Update a tracked OldestXmin horizon with a candidate xid and source.
1649+ *
1650+ * If the candidate is older than *target, adopt it and remember why.
1651+ */
1652+ static inline void
1653+ UpdateOldestXmin (TransactionId * target , OldestXminInfo * info ,
1654+ TransactionId candidate , OldestXminSource source ,
1655+ int backend_pid )
1656+ {
1657+ TransactionId old ;
1658+ TransactionId new_horizon ;
1659+
1660+ if (!TransactionIdIsValid (candidate ))
1661+ return ;
1662+
1663+ old = * target ;
1664+ new_horizon = TransactionIdOlder (old , candidate );
1665+ if (TransactionIdEquals (new_horizon , old ))
1666+ return ;
1667+
1668+ * target = new_horizon ;
1669+ OldestXminInfoSet (info , source , backend_pid );
1670+ }
16251671
16261672/*
16271673 * Determine XID horizons.
@@ -1689,6 +1735,8 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
16891735
16901736 /* inferred after ProcArrayLock is released */
16911737 h -> catalog_oldest_nonremovable = InvalidTransactionId ;
1738+ OldestXminInfoSet (& h -> catalog_oldest_nonremovable_info ,
1739+ OLDESTXMIN_SOURCE_OTHER , 0 );
16921740
16931741 LWLockAcquire (ProcArrayLock , LW_SHARED );
16941742
@@ -1710,6 +1758,10 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
17101758 h -> oldest_considered_running = initial ;
17111759 h -> shared_oldest_nonremovable = initial ;
17121760 h -> data_oldest_nonremovable = initial ;
1761+ OldestXminInfoSet (& h -> shared_oldest_nonremovable_info ,
1762+ OLDESTXMIN_SOURCE_OTHER , 0 );
1763+ OldestXminInfoSet (& h -> data_oldest_nonremovable_info ,
1764+ OLDESTXMIN_SOURCE_OTHER , 0 );
17131765
17141766 /*
17151767 * Only modifications made by this backend affect the horizon for
@@ -1724,9 +1776,17 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
17241776 * latestCompletedXid.
17251777 */
17261778 if (TransactionIdIsValid (MyProc -> xid ))
1779+ {
17271780 h -> temp_oldest_nonremovable = MyProc -> xid ;
1781+ OldestXminInfoSet (& h -> temp_oldest_nonremovable_info ,
1782+ OLDESTXMIN_SOURCE_ACTIVE_TRANSACTION , MyProcPid );
1783+ }
17281784 else
1785+ {
17291786 h -> temp_oldest_nonremovable = initial ;
1787+ OldestXminInfoSet (& h -> temp_oldest_nonremovable_info ,
1788+ OLDESTXMIN_SOURCE_OTHER , 0 );
1789+ }
17301790 }
17311791
17321792 /*
@@ -1744,6 +1804,8 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
17441804 int8 statusFlags = ProcGlobal -> statusFlags [index ];
17451805 TransactionId xid ;
17461806 TransactionId xmin ;
1807+ OldestXminSource candidate_source ;
1808+ int candidate_pid ;
17471809
17481810 /* Fetch xid just once - see GetNewTransactionId */
17491811 xid = UINT32_ACCESS_ONCE (other_xids [index ]);
@@ -1768,8 +1830,8 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
17681830 * backends are protected even without this check, it can't hurt to
17691831 * include them here as well..
17701832 */
1771- h -> oldest_considered_running =
1772- TransactionIdOlder ( h -> oldest_considered_running , xmin );
1833+ UpdateOldestXmin ( & h -> oldest_considered_running , NULL , xmin ,
1834+ OLDESTXMIN_SOURCE_OTHER , 0 );
17731835
17741836 /*
17751837 * Skip over backends either vacuuming (which is ok with rows being
@@ -1780,8 +1842,17 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
17801842 continue ;
17811843
17821844 /* shared tables need to take backends in all databases into account */
1783- h -> shared_oldest_nonremovable =
1784- TransactionIdOlder (h -> shared_oldest_nonremovable , xmin );
1845+ candidate_pid = proc -> pid ;
1846+ if (proc -> pid == 0 )
1847+ candidate_source = OLDESTXMIN_SOURCE_PREPARED_TRANSACTION ;
1848+ else if (statusFlags & PROC_AFFECTS_ALL_HORIZONS )
1849+ candidate_source = OLDESTXMIN_SOURCE_HOT_STANDBY_FEEDBACK ;
1850+ else
1851+ candidate_source = OLDESTXMIN_SOURCE_ACTIVE_TRANSACTION ;
1852+
1853+ UpdateOldestXmin (& h -> shared_oldest_nonremovable ,
1854+ & h -> shared_oldest_nonremovable_info ,
1855+ xmin , candidate_source , candidate_pid );
17851856
17861857 /*
17871858 * Normally sessions in other databases are ignored for anything but
@@ -1807,8 +1878,9 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
18071878 (statusFlags & PROC_AFFECTS_ALL_HORIZONS ) ||
18081879 in_recovery )
18091880 {
1810- h -> data_oldest_nonremovable =
1811- TransactionIdOlder (h -> data_oldest_nonremovable , xmin );
1881+ UpdateOldestXmin (& h -> data_oldest_nonremovable ,
1882+ & h -> data_oldest_nonremovable_info ,
1883+ xmin , candidate_source , candidate_pid );
18121884 }
18131885 }
18141886
@@ -1827,12 +1899,14 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
18271899
18281900 if (in_recovery )
18291901 {
1830- h -> oldest_considered_running =
1831- TransactionIdOlder (h -> oldest_considered_running , kaxmin );
1832- h -> shared_oldest_nonremovable =
1833- TransactionIdOlder (h -> shared_oldest_nonremovable , kaxmin );
1834- h -> data_oldest_nonremovable =
1835- TransactionIdOlder (h -> data_oldest_nonremovable , kaxmin );
1902+ UpdateOldestXmin (& h -> oldest_considered_running , NULL ,
1903+ kaxmin , OLDESTXMIN_SOURCE_OTHER , 0 );
1904+ UpdateOldestXmin (& h -> shared_oldest_nonremovable ,
1905+ & h -> shared_oldest_nonremovable_info ,
1906+ kaxmin , OLDESTXMIN_SOURCE_OTHER , 0 );
1907+ UpdateOldestXmin (& h -> data_oldest_nonremovable ,
1908+ & h -> data_oldest_nonremovable_info ,
1909+ kaxmin , OLDESTXMIN_SOURCE_OTHER , 0 );
18361910 /* temp relations cannot be accessed in recovery */
18371911 }
18381912
@@ -1844,10 +1918,12 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
18441918 /*
18451919 * Check whether there are replication slots requiring an older xmin.
18461920 */
1847- h -> shared_oldest_nonremovable =
1848- TransactionIdOlder (h -> shared_oldest_nonremovable , h -> slot_xmin );
1849- h -> data_oldest_nonremovable =
1850- TransactionIdOlder (h -> data_oldest_nonremovable , h -> slot_xmin );
1921+ UpdateOldestXmin (& h -> shared_oldest_nonremovable ,
1922+ & h -> shared_oldest_nonremovable_info ,
1923+ h -> slot_xmin , OLDESTXMIN_SOURCE_REPLICATION_SLOT , 0 );
1924+ UpdateOldestXmin (& h -> data_oldest_nonremovable ,
1925+ & h -> data_oldest_nonremovable_info ,
1926+ h -> slot_xmin , OLDESTXMIN_SOURCE_REPLICATION_SLOT , 0 );
18511927
18521928 /*
18531929 * The only difference between catalog / data horizons is that the slot's
@@ -1857,13 +1933,16 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
18571933 * that also can contain catalogs.
18581934 */
18591935 h -> shared_oldest_nonremovable_raw = h -> shared_oldest_nonremovable ;
1860- h -> shared_oldest_nonremovable =
1861- TransactionIdOlder (h -> shared_oldest_nonremovable ,
1862- h -> slot_catalog_xmin );
1936+ UpdateOldestXmin (& h -> shared_oldest_nonremovable ,
1937+ & h -> shared_oldest_nonremovable_info ,
1938+ h -> slot_catalog_xmin ,
1939+ OLDESTXMIN_SOURCE_REPLICATION_SLOT , 0 );
18631940 h -> catalog_oldest_nonremovable = h -> data_oldest_nonremovable ;
1864- h -> catalog_oldest_nonremovable =
1865- TransactionIdOlder (h -> catalog_oldest_nonremovable ,
1866- h -> slot_catalog_xmin );
1941+ h -> catalog_oldest_nonremovable_info = h -> data_oldest_nonremovable_info ;
1942+ UpdateOldestXmin (& h -> catalog_oldest_nonremovable ,
1943+ & h -> catalog_oldest_nonremovable_info ,
1944+ h -> slot_catalog_xmin ,
1945+ OLDESTXMIN_SOURCE_REPLICATION_SLOT , 0 );
18671946
18681947 /*
18691948 * It's possible that slots backed up the horizons further than
@@ -1951,25 +2030,53 @@ GlobalVisHorizonKindForRel(Relation rel)
19512030 */
19522031TransactionId
19532032GetOldestNonRemovableTransactionId (Relation rel )
2033+ {
2034+ /* Delegate to the WithReason variant to avoid duplicated logic */
2035+ return GetOldestNonRemovableTransactionIdWithReason (rel , NULL );
2036+ }
2037+
2038+ /*
2039+ * Return horizon like GetOldestNonRemovableTransactionId(), and also classify
2040+ * the reason that determined that horizon at the time of computation.
2041+ */
2042+ TransactionId
2043+ GetOldestNonRemovableTransactionIdWithReason (Relation rel ,
2044+ OldestXminInfo * out_info )
19542045{
19552046 ComputeXidHorizonsResult horizons ;
2047+ TransactionId target = InvalidTransactionId ;
2048+ GlobalVisHorizonKind kind ;
2049+ const OldestXminInfo * source_info = NULL ;
2050+
2051+ if (out_info != NULL )
2052+ OldestXminInfoSet (out_info , OLDESTXMIN_SOURCE_OTHER , 0 );
19562053
19572054 ComputeXidHorizons (& horizons );
19582055
1959- switch (GlobalVisHorizonKindForRel (rel ))
2056+ kind = GlobalVisHorizonKindForRel (rel );
2057+ switch (kind )
19602058 {
19612059 case VISHORIZON_SHARED :
1962- return horizons .shared_oldest_nonremovable ;
2060+ target = horizons .shared_oldest_nonremovable ;
2061+ source_info = & horizons .shared_oldest_nonremovable_info ;
2062+ break ;
19632063 case VISHORIZON_CATALOG :
1964- return horizons .catalog_oldest_nonremovable ;
2064+ target = horizons .catalog_oldest_nonremovable ;
2065+ source_info = & horizons .catalog_oldest_nonremovable_info ;
2066+ break ;
19652067 case VISHORIZON_DATA :
1966- return horizons .data_oldest_nonremovable ;
2068+ target = horizons .data_oldest_nonremovable ;
2069+ source_info = & horizons .data_oldest_nonremovable_info ;
2070+ break ;
19672071 case VISHORIZON_TEMP :
1968- return horizons .temp_oldest_nonremovable ;
2072+ target = horizons .temp_oldest_nonremovable ;
2073+ source_info = & horizons .temp_oldest_nonremovable_info ;
2074+ break ;
19692075 }
19702076
1971- /* just to prevent compiler warnings */
1972- return InvalidTransactionId ;
2077+ if (out_info != NULL && TransactionIdIsValid (target ) && source_info != NULL )
2078+ * out_info = * source_info ;
2079+ return target ;
19732080}
19742081
19752082/*
0 commit comments