PostgreSQL 14在MVCC上面有所增强,以保证在大批量客户端连接场景下性能不会出现显著下降(详细可参见参考资料中的链接)。本节介绍了提交记录“Don’t compute global horizons while building snapshots.”中pruneheap.c中的修改以及涉及的相关数据结构。
一、数据结构
PruneState
用于heap_page_prune和子函数的结构体
/* Working data for heap_page_prune and subroutines */
typedef struct
{
Relation rel;
/* tuple visibility test, initialized for the relation */
//元组可见性验证,为relation而准备
GlobalVisState *vistest;
/*
* Thresholds set by TransactionIdLimitedForOldSnapshots() if they have
* been computed (done on demand, and only if
* OldSnapshotThresholdActive()). The first time a tuple is about to be
* removed based on the limited horizon, old_snap_used is set to true, and
* SetOldSnapshotThresholdTimestamp() is called. See
* heap_prune_satisfies_vacuum().
* 如果已计算并且OldSnapshotThresholdActive()返回为真,则通过函数TransactionIdLimitedForOldSnapshots设置阈值,
* 第一次某个元组基于有限边界将要被删除时,old_snap_used设置为真,同时调用SetOldSnapshotThresholdTimestamp函数,
* 详细参见heap_prune_satisfies_vacuum
*/
TimestampTz old_snap_ts;//拍照时的时间点
TransactionId old_snap_xmin;//拍照时的xmin
bool old_snap_used;//有元组因此而被删除
TransactionId new_prune_xid; /* page中新的清除标记值,new prune hint value for page */
TransactionId latestRemovedXid; /* 该清除动作最后的xid,latest xid to be removed by this prune */
int nredirected; /* 下面数组的元素个数,numbers of entries in arrays below */
int ndead;//
int nunused;
/* arrays that accumulate indexes of items to be changed */
OffsetNumber redirected[MaxHeapTuplesPerPage * 2];
OffsetNumber nowdead[MaxHeapTuplesPerPage];
OffsetNumber nowunused[MaxHeapTuplesPerPage];
/* marked[i] is true if item i is entered in one of the above arrays */
bool marked[MaxHeapTuplesPerPage + 1];
} PruneState;
二、源码解读
/*
diff:
1.PruneState结构体增加rel、vistest等成员变量
2.参数列表中存在OldestXmin的,均删除该参数
3.函数体中存在OldestXmin、RecentGlobalXmin的,修改为GlobalVisXXX函数调用
4.新增heap_prune_satisfies_vacuum函数
*/
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -23,12 +23,30 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
+#include "utils/snapmgr.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
/* Working data for heap_page_prune and subroutines */
// 用于heap_page_prune和子例程的结构体
typedef struct
{
+ Relation rel;
+
+ /* tuple visibility test, initialized for the relation */
+ GlobalVisState *vistest;
+
+ /*
+ * Thresholds set by TransactionIdLimitedForOldSnapshots() if they have
+ * been computed (done on demand, and only if
+ * OldSnapshotThresholdActive()). The first time a tuple is about to be
+ * removed based on the limited horizon, old_snap_used is set to true, and
+ * SetOldSnapshotThresholdTimestamp() is called. See
+ * heap_prune_satisfies_vacuum().
+ */
+ TimestampTz old_snap_ts;
+ TransactionId old_snap_xmin;
+ bool old_snap_used;
+
/* 参见数据结构PruneState */
TransactionId new_prune_xid; /* new prune hint value for page */
TransactionId latestRemovedXid; /* latest xid to be removed by this prune */
int nredirected; /* numbers of entries in arrays below */
@@ -43,9 +61,8 @@ typedef struct
} PruneState;
/* Local functions */
/*
heap_prune_chain函数参数删除了relation和OldestXmin
该函数用于清理指定的line pointer或者HOT链
*/
-static int heap_prune_chain(Relation relation, Buffer buffer,
+static int heap_prune_chain(Buffer buffer,
OffsetNumber rootoffnum,
- TransactionId OldestXmin,
PruneState *prstate);
static void heap_prune_record_prunable(PruneState *prstate, TransactionId xid);
static void heap_prune_record_redirect(PruneState *prstate,
@@ -65,16 +82,16 @@ static void heap_prune_record_unused(PruneState *prstate, OffsetNumber offnum);
* if there's not any use in pruning.
*
* Caller must have pin on the buffer, and must *not* have a lock on it.
/* 不再使用OldestXmin */
- *
- * OldestXmin is the cutoff XID used to distinguish whether tuples are DEAD
- * or RECENTLY_DEAD (see HeapTupleSatisfiesVacuum).
*/
void
heap_page_prune_opt(Relation relation, Buffer buffer)
/*
heap_page_prune_opt函数用于清除page和清理page中的碎片
该函数执行得很频繁,只要一有机会就会执行,在执行前调用者必须pin缓存且没有锁
输入参数是relation和buffer
*/
{
Page page = BufferGetPage(buffer);//获取相应的page
+ TransactionId prune_xid;//page上存储的事务id
+ GlobalVisState *vistest;//详见GlobalVisState结构体
+ TransactionId limited_xmin = InvalidTransactionId;//
+ TimestampTz limited_ts = 0;//
Size minfree;
- TransactionId OldestXmin;//删除OldestXmin
/*
* We can't write WAL in recovery mode, so there's no point trying to
@@ -85,37 +102,55 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
return;
/*
- * Use the appropriate xmin horizon for this relation. If it's a proper
- * catalog relation or a user defined, additional, catalog relation, we
- * need to use the horizon that includes slots, otherwise the data-only
- * horizon can be used. Note that the toast relation of user defined
- * relations are *not* considered catalog relations.
+ * XXX: Magic to keep old_snapshot_threshold tests appear "working". They
+ * currently are broken, and discussion of what to do about them is
+ * ongoing. See
+ * https://www.postgresql.org/message-id/20200403001235.e6jfdll3gh2ygbuc%40alap3.anarazel.de
+ */
+ if (old_snapshot_threshold == 0)//快照阈值
+ SnapshotTooOldMagicForTest();
+
+ /*
+ * First check whether there's any chance there's something to prune,
+ * determining the appropriate horizon is a waste if there's no prune_xid
+ * (i.e. no updates/deletes left potentially dead tuples around).
+ */
+ prune_xid = ((PageHeader) page)->pd_prune_xid;
+ if (!TransactionIdIsValid(prune_xid))//非有效的事务id,退出
+ return;
+
+ /*
+ * Check whether prune_xid indicates that there may be dead rows that can
+ * be cleaned up.
* 检查prune_xid是否存在可清除的数据行
*
- * It is OK to apply the old snapshot limit before acquiring the cleanup
+ * It is OK to check the old snapshot limit before acquiring the cleanup
* lock because the worst that can happen is that we are not quite as
* aggressive about the cleanup (by however many transaction IDs are
* consumed between this point and acquiring the lock). This allows us to
* save significant overhead in the case where the page is found not to be
* prunable.
+ *
+ * Even if old_snapshot_threshold is set, we first check whether the page
+ * can be pruned without. Both because
+ * TransactionIdLimitedForOldSnapshots() is not cheap, and because not
+ * unnecessarily relying on old_snapshot_threshold avoids causing
+ * conflicts.
* old_snapshot_threshold 是GUC参数,用于判断某个快照多长时间后too old,默认为-1(禁用该特性)
*/
- if (IsCatalogRelation(relation) ||
- RelationIsAccessibleInLogicalDecoding(relation))
- OldestXmin = RecentGlobalXmin;
- else
- OldestXmin =
- TransactionIdLimitedForOldSnapshots(RecentGlobalDataXmin,
- relation);
+ vistest = GlobalVisTestFor(relation);//vistest中会有确实需要的xid和不再需要的xid
- Assert(TransactionIdIsValid(OldestXmin));
+ if (!GlobalVisTestIsRemovableXid(vistest, prune_xid))//是否可被清除?返回false,表示不能清除
+ {
+ if (!OldSnapshotThresholdActive())//禁用快照过久特性,则返回
+ return;
- /*
- * Let's see if we really need pruning.
- *
- * Forget it if page is not hinted to contain something prunable that's
- * older than OldestXmin.
- */
- if (!PageIsPrunable(page, OldestXmin))
- return;
+ if (!TransactionIdLimitedForOldSnapshots(GlobalVisTestNonRemovableHorizon(vistest),
+ relation,
+ &limited_xmin, &limited_ts))
+ return;//快照过久也需要不限制事务id,返回
+
+ if (!TransactionIdPrecedes(prune_xid, limited_xmin))
+ return;//prune_xid在limited_xmin之后,返回
+ }
/*
* We prune when a previous UPDATE failed to find enough space on the page
@@ -151,7 +186,9 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
* needed */
/* OK to prune */
- (void) heap_page_prune(relation, buffer, OldestXmin, true, &ignore);
+ (void) heap_page_prune(relation, buffer, vistest,
+ limited_xmin, limited_ts,
+ true, &ignore);//执行清理,输入参数删除OldestXmin,新增了vistest/limited_xmin/limited_ts
}
/* And release buffer lock */
@@ -165,8 +202,11 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
*
* Caller must have pin and buffer cleanup lock on the page.
*
- * OldestXmin is the cutoff XID used to distinguish whether tuples are DEAD
- * or RECENTLY_DEAD (see HeapTupleSatisfiesVacuum).
+ * vistest is used to distinguish whether tuples are DEAD or RECENTLY_DEAD
+ * (see heap_prune_satisfies_vacuum and
+ * HeapTupleSatisfiesVacuum). old_snap_xmin / old_snap_ts need to
+ * either have been set by TransactionIdLimitedForOldSnapshots, or
+ * InvalidTransactionId/0 respectively.
*
* If report_stats is true then we send the number of reclaimed heap-only
* tuples to pgstats. (This must be false during vacuum, since vacuum will
@@ -177,7 +217,10 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
* latestRemovedXid.
*/
/*
heap_page_prune函数用于清除page和清理page中的碎片
vistest用于区分元组是DEAD还是RECENTLY_DEAD
old_snap_xmin / old_snap_ts应由TransactionIdLimitedForOldSnapshots函数设置或者为InvalidTransactionId/0
如report_stats为真,则发送heap-only元组给统计进程(pg_stats)
*/
int
-heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
+heap_page_prune(Relation relation, Buffer buffer,
+ GlobalVisState *vistest,
+ TransactionId old_snap_xmin,
+ TimestampTz old_snap_ts,
bool report_stats, TransactionId *latestRemovedXid)
{
int ndeleted = 0;
@@ -198,6 +241,11 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
* initialize the rest of our working state.
*/
prstate.new_prune_xid = InvalidTransactionId;
+ prstate.rel = relation;//PruneState结构体赋值
+ prstate.vistest = vistest;
+ prstate.old_snap_xmin = old_snap_xmin;
+ prstate.old_snap_ts = old_snap_ts;
+ prstate.old_snap_used = false;
prstate.latestRemovedXid = *latestRemovedXid;
prstate.nredirected = prstate.ndead = prstate.nunused = 0;
memset(prstate.marked, 0, sizeof(prstate.marked));
@@ -220,9 +268,7 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
continue;
/* Process this item or chain of items */
//清理行指针line pointer
- ndeleted += heap_prune_chain(relation, buffer, offnum,
- OldestXmin,
- &prstate);
+ ndeleted += heap_prune_chain(buffer, offnum, &prstate);
}
/* Any error while applying the changes is critical */
@@ -323,6 +369,85 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
}
+/*
+ * Perform visiblity checks for heap pruning.
* 执行可见性检查
+ *
+ * This is more complicated than just using GlobalVisTestIsRemovableXid()
+ * because of old_snapshot_threshold. We only want to increase the threshold
+ * that triggers errors for old snapshots when we actually decide to remove a
+ * row based on the limited horizon.
* 由于old_snapshot_threshold的存在,该过程比GlobalVisTestIsRemovableXid要复杂
* 基于受限的范围,在确定要删除行时,我们希望增加由于过久快照而会触发错误的阈值
+ *
+ * Due to its cost we also only want to call
+ * TransactionIdLimitedForOldSnapshots() if necessary, i.e. we might not have
+ * done so in heap_hot_prune_opt() if pd_prune_xid was old enough. But we
+ * still want to be able to remove rows that are too new to be removed
+ * according to prstate->vistest, but that can be removed based on
+ * old_snapshot_threshold. So we call TransactionIdLimitedForOldSnapshots() on
+ * demand in here, if appropriate.
* 成本上面的考虑,我们希望在需要时才调用TransactionIdLimitedForOldSnapshots
* 在pd_prune_xid过久的情况下,不会考虑在heap_hot_prune_opt中处理这事情
* 但我们仍然希望可以清理对于prstate->vistest来说过新的行,但基于old_snapshot_threshold可被清除
* 因此我们在需要时调用TransactionIdLimitedForOldSnapshots
+ */
+static HTSV_Result
+heap_prune_satisfies_vacuum(PruneState *prstate, HeapTuple tup, Buffer buffer)
+{
+ HTSV_Result res;
+ TransactionId dead_after;
+
+ res = HeapTupleSatisfiesVacuumHorizon(tup, buffer, &dead_after);//调用HeapTupleSatisfiesVacuumHorizon
+
+ if (res != HEAPTUPLE_RECENTLY_DEAD)//只关注HEAPTUPLE_RECENTLY_DEAD
+ return res;
+
+ /*
+ * If we are already relying on the limited xmin, there is no need to
+ * delay doing so anymore.
+ */
+ if (prstate->old_snap_used)//使用快照过旧特性
+ {
+ Assert(TransactionIdIsValid(prstate->old_snap_xmin));
+
+ if (TransactionIdPrecedes(dead_after, prstate->old_snap_xmin))
+ res = HEAPTUPLE_DEAD;//dead_after在old_snap_xmin之前,则可清除
+ return res;
+ }
+
+ /*
+ * First check if GlobalVisTestIsRemovableXid() is sufficient to find the
+ * row dead. If not, and old_snapshot_threshold is enabled, try to use the
+ * lowered horizon.
* 首先检查GlobalVisTestIsRemovableXid是否足以找到已死亡的行,
* 否则如old_snapshot_threshold参数有效,则尝试使用更低的区间
+ */
+ if (GlobalVisTestIsRemovableXid(prstate->vistest, dead_after))
+ res = HEAPTUPLE_DEAD;//可清除
+ else if (OldSnapshotThresholdActive())//启用快照过旧特性
+ {
+ /* haven't determined limited horizon yet, requests */
// 还没有确定范围,则确定之
+ if (!TransactionIdIsValid(prstate->old_snap_xmin))
+ {
+ TransactionId horizon =
+ GlobalVisTestNonRemovableHorizon(prstate->vistest);
+
+ TransactionIdLimitedForOldSnapshots(horizon, prstate->rel,
+ &prstate->old_snap_xmin,
+ &prstate->old_snap_ts);
+ }
+
+ if (TransactionIdIsValid(prstate->old_snap_xmin) && //已确定范围
+ TransactionIdPrecedes(dead_after, prstate->old_snap_xmin)) //快照过旧
+ {
+ /*
+ * About to remove row based on snapshot_too_old. Need to raise
+ * the threshold so problematic accesses would error.
+ */
+ Assert(!prstate->old_snap_used);
+ SetOldSnapshotThresholdTimestamp(prstate->old_snap_ts,
+ prstate->old_snap_xmin);
+ prstate->old_snap_used = true;
+ res = HEAPTUPLE_DEAD;//可以清除
+ }
+ }
+
+ return res;
+}
+
+
/*
* Prune specified line pointer or a HOT chain originating at line pointer.
*
@@ -349,9 +474,7 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
* Returns the number of tuples (to be) deleted from the page.
*/
static int
-heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
- TransactionId OldestXmin,//去掉OldestXmin
- PruneState *prstate)
+heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
{
int ndeleted = 0;
Page dp = (Page) BufferGetPage(buffer);
@@ -366,7 +489,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
i;
HeapTupleData tup;
- tup.t_tableOid = RelationGetRelid(relation);
+ tup.t_tableOid = RelationGetRelid(prstate->rel);
rootlp = PageGetItemId(dp, rootoffnum);
@@ -401,7 +524,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
* either here or while following a chain below. Whichever path
* gets there first will mark the tuple unused.
*/
- if (HeapTupleSatisfiesVacuum(&tup, OldestXmin, buffer)
+ if (heap_prune_satisfies_vacuum(prstate, &tup, buffer)//改为使用heap_prune_satisfies_vacuum
== HEAPTUPLE_DEAD && !HeapTupleHeaderIsHotUpdated(htup))
{
heap_prune_record_unused(prstate, rootoffnum);
@@ -485,7 +608,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
*/
tupdead = recent_dead = false;
- switch (HeapTupleSatisfiesVacuum(&tup, OldestXmin, buffer))
+ switch (heap_prune_satisfies_vacuum(prstate, &tup, buffer))
{
case HEAPTUPLE_DEAD:
tupdead = true;
三、跟踪分析
N/A
四、参考资料
1.
Improving connection scalability: GetSnapshotData()
2.
snapshot scalability: Don’t compute global horizons while building snapshots.