PostgreSQL 源码解读(250)- PG 14(Improving connection scalability)#2

PostgreSQL 14在MVCC上面有所增强,以保证在大批量客户端连接场景下性能不会出现显著下降(详细可参见参考资料中的链接)。本节介绍了提交记录“Don’t compute global horizons while building snapshots.”中heapam_handler.c中的修改以及涉及的相关数据结构。

一、数据结构

SnapshotData
表示所有类型的快照结构体


/*
 * The different snapshot types.  We use SnapshotData structures to represent
 * both "regular" (MVCC) snapshots and "special" snapshots that have non-MVCC
 * semantics.  The specific semantics of a snapshot are encoded by its type.
 * 快照类型。
 * 使用SnapshotData结构体表示常规的(MVCC)的快照和特别的快照(非MVCC语义)。
 * 快照的特殊语义通过其类型进行编码。
 *
 * The behaviour of each type of snapshot should be documented alongside its
 * enum value, best in terms that are not specific to an individual table AM.
 * 每种快照类型的行为与其enum值一起记录而不是特定于单个表的AM。
 *
 * The reason the snapshot type rather than a callback as it used to be is
 * that that allows to use the same snapshot for different table AMs without
 * having one callback per AM.
 * 使用快照类型而不是回调函数的原因是对于不同的表AMs(访问方法)可使用相同的快照而不是每个AM使用一个回调函数
 */
typedef enum SnapshotType
{
    /*-------------------------------------------------------------------------
     * A tuple is visible iff the tuple is valid for the given MVCC snapshot.
     *
     * Here, we consider the effects of:
     * - all transactions committed as of the time of the given snapshot
     * - previous commands of this transaction
     *
     * Does _not_ include:
     * - transactions shown as in-progress by the snapshot
     * - transactions started after the snapshot was taken
     * - changes made by the current command
     * 元组对于给定的MVCC快照是有效的,则该元组可见
     * 考虑:
     *   给定快照时所有提交的事务
     *   该事务先前执行的命令
     * 不考虑:
     *   正在处理中的事务
     *   快照点之后的事务
     *   当前命令做出的修改
     * -------------------------------------------------------------------------
     */
    SNAPSHOT_MVCC = 0,//常规的MVCC
    /*-------------------------------------------------------------------------
     * A tuple is visible iff the tuple is valid "for itself".
     * 对于“自身”是有效,则元组可见
     *
     * Here, we consider the effects of:
     * - all committed transactions (as of the current instant)
     * - previous commands of this transaction
     * - changes made by the current command
     *
     * Does _not_ include:
     * - in-progress transactions (as of the current instant)
     * 考虑:
     *   所有已提交的事务(当前实例)
     *   本事务先前的命令
     *   当前命令的修改
     * 不考虑
     *   正在进行中的事务
     * -------------------------------------------------------------------------
     */
    SNAPSHOT_SELF,
    /*
     * Any tuple is visible.
     * 所有元组都是可见的
     */
    SNAPSHOT_ANY,
    /*
     * A tuple is visible iff the tuple is valid as a TOAST row.
     * 元组作为TOAST行为有效时,元组是可见的。
     */
    SNAPSHOT_TOAST,
    /*-------------------------------------------------------------------------
     * A tuple is visible iff the tuple is valid including effects of open
     * transactions.
     * 只要元组有效(包括开放事务的影响)则元组可见
     *
     * Here, we consider the effects of:
     * - all committed and in-progress transactions (as of the current instant)
     * - previous commands of this transaction
     * - changes made by the current command
     * 这里,考虑:
     *   所有已提交和处理中的事务
     *   该事务先前的命令
     *   当前命令作出的修改
     *
     * This is essentially like SNAPSHOT_SELF as far as effects of the current
     * transaction and committed/aborted xacts are concerned.  However, it
     * also includes the effects of other xacts still in progress.
     * 就当前和提交/中止事务的影响来看,本质上类似于SNAPSHOT_SELF。
     *
     * A special hack is that when a snapshot of this type is used to
     * determine tuple visibility, the passed-in snapshot struct is used as an
     * output argument to return the xids of concurrent xacts that affected
     * the tuple.  snapshot->xmin is set to the tuple's xmin if that is
     * another transaction that's still in progress; or to
     * InvalidTransactionId if the tuple's xmin is committed good, committed
     * dead, or my own xact.  Similarly for snapshot->xmax and the tuple's
     * xmax.  If the tuple was inserted speculatively, meaning that the
     * inserter might still back down on the insertion without aborting the
     * whole transaction, the associated token is also returned in
     * snapshot->speculativeToken.  See also InitDirtySnapshot().
     * 这种快照用于确定元组的可见性,传入的快照结构体用作输出参数返回当前影响元组的事务xids。
     * snapshot->xmin设置为元组的xmin,如果这是另外一个进行中的事务;
     *               设置为InvalidTransactionId如果元组的xmin对应的事务已完成提交/终止/或者是本事务
     * snapshot->xmax与元组xmax与此类似。
     * 如果tuple是speculatively插入的,这意味着插入事务仍可以继续插入,而不会中止整个事务,
     * 相关的令牌会通过snapshot—>speculativeToken返回。
     * -------------------------------------------------------------------------
     */
    SNAPSHOT_DIRTY,
    /*
     * A tuple is visible iff it follows the rules of SNAPSHOT_MVCC, but
     * supports being called in timetravel context (for decoding catalog
     * contents in the context of logical decoding).
     * 如遵循SNAPSHOT_MVCC规则,则元组是可见的,但支持在timetravel上下文中调用
     * (在逻辑解码上下文时解码catalog内容)
     */
    SNAPSHOT_HISTORIC_MVCC,
    /*
     * A tuple is visible iff the tuple might be visible to some transaction;
     * false if it's surely dead to everyone, i.e., vacuumable.
     *
     * For visibility checks snapshot->min must have been set up with the xmin
     * horizon to use.
     * 对于某些事务是可见的,则元组可见。
     * 如已确认“死亡”则为False,如需要被清理。
     * 对于可见性检查而言,snapshot->min必须使用xmin来配置
     */
    SNAPSHOT_NON_VACUUMABLE
} SnapshotType;
typedef struct SnapshotData *Snapshot;
#define InvalidSnapshot        ((Snapshot) NULL)
/*
 * Struct representing all kind of possible snapshots.
 * 对应所有可能快照的结构体
 * 
 * There are several different kinds of snapshots:
 * * Normal MVCC snapshots
 * * MVCC snapshots taken during recovery (in Hot-Standby mode)
 * * Historic MVCC snapshots used during logical decoding
 * * snapshots passed to HeapTupleSatisfiesDirty()
 * * snapshots passed to HeapTupleSatisfiesNonVacuumable()
 * * snapshots used for SatisfiesAny, Toast, Self where no members are
 *     accessed.
 * 快照类型包括(详见SnapshotType):
 *   普通的MVCC快照
 *   恢复期间的MVCC快照
 *   逻辑解码时的历史MVCC快照
 *   传递给HeapTupleSatisfiesDirty的快照
 *   传递给HeapTupleSatisfiesNonVacuumable的快照
 *   传递给SatisfiesAny,Toast,Self的快照
 *
 * TODO: It's probably a good idea to split this struct using a NodeTag
 * similar to how parser and executor nodes are handled, with one type for
 * each different kind of snapshot to avoid overloading the meaning of
 * individual fields.
 * TODO:通过NodeTag来拆分结构体
 */
typedef struct SnapshotData
{
    //快照类型
    SnapshotType snapshot_type; /* type of snapshot */
    /*
     * The remaining fields are used only for MVCC snapshots, and are normally
     * just zeroes in special snapshots.  (But xmin and xmax are used
     * specially by HeapTupleSatisfiesDirty, and xmin is used specially by
     * HeapTupleSatisfiesNonVacuumable.)
     * 余下字段用于MVCC快照,其他特殊的快照设置为0.
     * (xmin和xmax用于HeapTupleSatisfiesDirty,xmin用于HeapTupleSatisfiesNonVacuumable)
     *
     * An MVCC snapshot can never see the effects of XIDs >= xmax. It can see
     * the effects of all older XIDs except those listed in the snapshot. xmin
     * is stored as an optimization to avoid needing to search the XID arrays
     * for most tuples.
     * MVCC快照用于都看不到XIDs >= xmax的影响,但可以看到除了快照列表中的事务影响。
     * xmin作为优化的手段用于避免检索XID数组
     */
    TransactionId xmin;            /* all XID < xmin are visible to me */
    TransactionId xmax;            /* all XID >= xmax are invisible to me */
    /*
     * For normal MVCC snapshot this contains the all xact IDs that are in
     * progress, unless the snapshot was taken during recovery in which case
     * it's empty. For historic MVCC snapshots, the meaning is inverted, i.e.
     * it contains *committed* transactions between xmin and xmax.
     * xip用于存放所有正在进行中的事务,除了在恢复期间的快照。
     * 对于历史MVCC快照,意思的相反的,也就是说xip是xmin和xmax之间已提交的事务
     *
     * note: all ids in xip[] satisfy xmin <= xip[i] < xmax
     * xip数组中的元素满足:xmin <= xip[i] < xmax
     */
    TransactionId *xip;
    uint32        xcnt;            /* 数组大小,# of xact ids in xip[] */
    /*
     * For non-historic MVCC snapshots, this contains subxact IDs that are in
     * progress (and other transactions that are in progress if taken during
     * recovery). For historic snapshot it contains *all* xids assigned to the
     * replayed transaction, including the toplevel xid.
     * 对于非历史MVCC快照,包含正在进行中的子事务IDs。
     * 对于历史MVCC快照,包含所有分配给已回放事务的所有xids(包括顶层xid)
     *
     * note: all ids in subxip[] are >= xmin, but we don't bother filtering
     * out any that are >= xmax
     */
    TransactionId *subxip;
    int32        subxcnt;        /* 数组大小,# of xact ids in subxip[] */
    bool        suboverflowed;    /* 数组溢出?has the subxip array overflowed? */
    bool        takenDuringRecovery;    /* recovery-shaped snapshot? */
    bool        copied;            /* false if it's a static snapshot */
    CommandId    curcid;            /* 在本事务中,CID < curcid则可见,in my xact, CID < curcid are visible */
    /*
     * An extra return value for HeapTupleSatisfiesDirty, not used in MVCC
     * snapshots.
     * HeapTupleSatisfiesDirty额外的返回值
     */
    uint32        speculativeToken;
    /*
     * For SNAPSHOT_NON_VACUUMABLE (and hopefully more in the future) this is
     * used to determine whether row could be vacuumed.
     * 对于SNAPSHOT_NON_VACUUMABLE,用于确定哪些行可以被清理
     */
    struct GlobalVisState *vistest;
    /*
     * Book-keeping information, used by the snapshot manager
     * 用于快照管理器的信息
     */
    uint32        active_count;    /* refcount on ActiveSnapshot stack */
    uint32        regd_count;        /* refcount on RegisteredSnapshots */
    pairingheap_node ph_node;    /* link in the RegisteredSnapshots heap */
    TimestampTz whenTaken;        /* timestamp when snapshot was taken */
    XLogRecPtr    lsn;            /* position in the WAL stream when taken */
    /*
     * The transaction completion count at the time GetSnapshotData() built
     * this snapshot. Allows to avoid re-computing static snapshots when no
     * transactions completed since the last GetSnapshotData().
     */
    uint64        snapXactCompletionCount;
} SnapshotData;

二、源码解读

--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -1203,7 +1203,7 @@ heapam_index_build_range_scan(Relation heapRelation,
    /* okay to ignore lazy VACUUMs here */
    if (!IsBootstrapProcessingMode() && !indexInfo->ii_Concurrent)
    /*
        在常规的创建索引时,由于必须检索所有的元组并执行时间表达式检查,因此必须使用SnapshotAny
        而,使用SnapshotAny(所有元组均可见),必须调用GetOldestXmin,改为调用GetOldestNonRemovableTransactionId
        在bootstrap或者同步创建索引时,则使用常规的MVCC快照
    */
-       OldestXmin = GetOldestXmin(heapRelation, PROCARRAY_FLAGS_VACUUM);
+       OldestXmin = GetOldestNonRemovableTransactionId(heapRelation);
    if (!scan)
    {
@@ -1244,6 +1244,17 @@ heapam_index_build_range_scan(Relation heapRelation,
    hscan = (HeapScanDesc) scan;
+   /*
+    * Must have called GetOldestNonRemovableTransactionId() if using
+    * SnapshotAny.  Shouldn't have for an MVCC snapshot. (It's especially
+    * worth checking this for parallel builds, since ambuild routines that
+    * support parallel builds must work these details out for themselves.)
+    */
+   Assert(snapshot == SnapshotAny || IsMVCCSnapshot(snapshot));
+   Assert(snapshot == SnapshotAny ? TransactionIdIsValid(OldestXmin) :
+          !TransactionIdIsValid(OldestXmin));
+   Assert(snapshot == SnapshotAny || !anyvisible);
+
    /* Publish number of blocks to scan */
    if (progress)
    {
@@ -1263,17 +1274,6 @@ heapam_index_build_range_scan(Relation heapRelation,
                                     nblocks);
    }
     /*
         这部分逻辑往上移
     */
-   /*
-    * Must call GetOldestXmin() with SnapshotAny.  Should never call
-    * GetOldestXmin() with MVCC snapshot. (It's especially worth checking
-    * this for parallel builds, since ambuild routines that support parallel
-    * builds must work these details out for themselves.)
-    */
-   Assert(snapshot == SnapshotAny || IsMVCCSnapshot(snapshot));
-   Assert(snapshot == SnapshotAny ? TransactionIdIsValid(OldestXmin) :
-          !TransactionIdIsValid(OldestXmin));
-   Assert(snapshot == SnapshotAny || !anyvisible);
-
    /* set our scan endpoints */
    if (!allow_sync)
        heap_setscanlimits(scan, start_blockno, numblocks);

三、跟踪分析

N/A

四、参考资料

1. Improving connection scalability: GetSnapshotData()
2. snapshot scalability: Don’t compute global horizons while building snapshots.

请使用浏览器的分享功能分享到微信等