【 OpenGauss源码学习 —— 列存储(CStore)(二)】

列存储(CStore)(二)

  • 概述
    • CStore::CreateStorage 函数
    • CStore::ScanByTids 函数
    • CStore::CStoreScanWithCU 函数
      • CStore::LoadCUDescIfNeed 函数
    • CStore::LoadCUDesc 函数

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》一书以及OpenGauss社区学习文档和一些学习资料

概述

  在【 OpenGauss源码学习 —— 列存储(CStore)(一)】中我们介绍了部分 CStore 类中的静态成员函数。本节我们将介绍 CStore 类中的部分公有成员函数。
   CStore 类中的公有成员提供了一系列用于处理列存储表的扫描数据获取元组处理性能优化等操作的函数,为数据管理和查询提供了必要的功能支持。
  以下是其主要功能和作用的总结:(详细函数功能描述可以查看【 OpenGauss源码学习 —— 列存储(CStore)(一)】中的函数功能说明表)

  1. 初始化扫描相关函数:InitScan, InitReScan, InitPartReScan, IsEndScan, SetTiming 等函数用于初始化扫描状态管理扫描进度时间性能
  2. 扫描函数:ScanByTids, CStoreScanWithCU 等函数用于执行列存储表的扫描操作获取数据并返回结果
  3. CUDesc 相关函数:LoadCUDesc, GetCUDesc, GetCUDeleteMaskIfNeed, GetCURowCount 等函数用于加载和获取列的压缩单元描述信息,以及获取行数和元组删除信息
  4. 数据获取函数:GetCUData, GetUnCompressCUData 等函数用于获取列存储中的压缩单元数据
  5. 填充 Vector 函数:FillVecBatch, FillVector, FillVectorByTids, FillVectorLateRead, FillVectorByIndex, FillSysColVector, FillSysVecByTid, FillTidForLateRead, FillScanBatchLateIfNeed 等函数用于填充向量数据结构,用于结果集的构建和处理
  6. 扫描范围设置函数:SetScanRange 用于在重分发操作中设置扫描的 CU 范围
  7. 元组生死判断函数:IsDeadRow 用于判断指定的行是否为死亡行
  8. 预取函数:CUListPrefetch, CUPrefetch 用于执行预取操作,提高数据读取性能。
  9. 扫描执行函数:RunScan 用于执行列存储表的扫描操作,返回结果。
  10. 辅助函数:GetLateReadCtid, IncLoadCuDescCursor 用于辅助扫描操作数据加载

CStore::CreateStorage 函数

  CStore::CreateStorage 函数主要用于初始化列存储扫描的上下文内存管理其他必要的状态信息,以便后续执行列存储扫描操作。该函数的作用是初始化用于扫描列存储表的数据结构和环境。首先,它创建了用于扫描的内存上下文每个扫描的内存上下文,以便有效地管理内存分配。然后,它设置了实际扫描操作的函数指针,并根据表的属性信息初始化压缩单元(CU)数据存储结构。接下来,它初始化用于跟踪已加载 CU 描述的数组和其他扫描相关的参数。最后,它设置了范围扫描标志,初始化扫描范围,并记录当前计划节点的 ID。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

void CStore::InitScan(CStoreScanState* state, Snapshot snapshot)
{
    // 断言确保传递的扫描状态 `state` 和投影信息不为空
    Assert(state && state->ps.ps_ProjInfo);

    // 创建用于扫描的内存上下文(memory context)
    m_scanMemContext = AllocSetContextCreate(CurrentMemoryContext,
                                             "cstore scan memory context",
                                             ALLOCSET_DEFAULT_MINSIZE,
                                             ALLOCSET_DEFAULT_INITSIZE,
                                             ALLOCSET_DEFAULT_MAXSIZE);
    
    // 创建每个扫描的内存上下文,这些内存上下文将用于分配和管理扫描期间的内存
    m_perScanMemCnxt = AllocSetContextCreate(CurrentMemoryContext,
                                             "cstore scan per scan memory context",
                                             ALLOCSET_DEFAULT_MINSIZE,
                                             ALLOCSET_DEFAULT_INITSIZE,
                                             ALLOCSET_DEFAULT_MAXSIZE);

    // 设置扫描函数指针为 &CStore::CStoreScan,用于执行列存储的实际扫描操作
    m_scanFunc = &CStore::CStoreScan;

    // 初始化用于存储压缩单元(CU)数据的数据结构
    // 根据表的属性信息,分配和初始化 CU 存储结构
    m_relation = state->ss_currentRelation;
    int attNo = m_relation->rd_att->natts;
    m_cuStorage = (CUStorage**)palloc(sizeof(CUStorage*) * attNo);

    for (int i = 0; i < attNo; ++i) {
        if (m_relation->rd_att->attrs[i]->attisdropped) {
            m_cuStorage[i] = NULL;
            continue;
        }
        m_firstColIdx = i;
        CFileNode cFileNode(m_relation->rd_node, m_relation->rd_att->attrs[i]->attnum, MAIN_FORKNUM);
        m_cuStorage[i] = New(CurrentMemoryContext) CUStorage(cFileNode);
    }

    // 分配并初始化用于跟踪已加载 CU 描述的数组
    m_CUDescIdx = (int*)palloc(sizeof(int) * u_sess->attr.attr_storage.max_loaded_cudesc);
    errno_t rc = memset_s((char*)m_CUDescIdx,
                          sizeof(int) * u_sess->attr.attr_storage.max_loaded_cudesc,
                          0xFF,
                          sizeof(int) * u_sess->attr.attr_storage.max_loaded_cudesc);
    securec_check(rc, "\0", "\0");

    // 初始化一些其他参数,如 m_cursor、m_colNum、m_NumCUDescIdx、m_rowCursorInCU、m_prefetch_quantity 等
    m_cursor = 0;
    m_colNum = 0;
    m_NumCUDescIdx = 0;
    m_rowCursorInCU = 0;
    
    // 初始化用于预取操作的参数 m_prefetch_quantity 和 m_prefetch_threshold
    m_prefetch_quantity = 0;
    m_prefetch_threshold = Min(CUCache->m_cstoreMaxSize / 4, u_sess->attr.attr_storage.cstore_prefetch_quantity * 1024LL);

    // 保存传递的快照信息
    m_snapshot = snapshot;

    // 设置范围扫描标志 m_rangeScanInRedis
    m_rangeScanInRedis = state->rangeScanInRedis;

    // 调用 SetScanRange 初始化扫描范围
    SetScanRange();

    // 调用初始化函数 InitFillVecEnv 和 InitRoughCheckEnv 初始化环境
    InitFillVecEnv(state);
    InitRoughCheckEnv(state);

    // 记录当前计划节点的 ID
    m_plan_node_id = state->ps.plan->plan_node_id;
}

函数的执行过程包括以下步骤:

  1. 检查传递的扫描状态投影信息是否非空
  2. 创建用于扫描的内存上下文和每个扫描的内存上下文
  3. 设置扫描函数的指针为实际的列存储扫描函数。
  4. 根据表的属性信息初始化 CU 数据存储结构。
  5. 分配和初始化用于跟踪已加载 CU 描述的数组。
  6. 初始化其他扫描相关的参数。
  7. 设置范围扫描标志
  8. 初始化扫描范围。
  9. 初始化环境并记录计划节点的 ID

CStore::ScanByTids 函数

  该函数用于执行按行扫描列存储表的操作,根据传入的TID元组标识符)信息,填充输出的列存储数据。函数的执行过程如下:

  1. 首先,进行断言检查,确保传递的扫描状态 state输入向量 idxOut输出向量 vbout 不为空,并确保输入向量 idxOut至少包含一个列
  2. 在开始实际扫描操作之前,根据传入的 TID 信息,设置输出行数 vbout->m_rows 为输入向量 idxOut 中的行数。
  3. 遍历要扫描的所有列,分别进行以下操作:
  • 对于索引表中的列,将索引表的扫描结果复制到输出向量 vbout 中。
  • 对于非索引列,调用相应的填充函数进行数据填充,并更新输出向量 vbout 的行数。
  1. 如果需要填充系统列,分别填充以下系统列信息:
  • SelfItemPointerAttributeNumber:元组自身的 TID
  • TableOidAttributeNumber:表的 OID
  • XC_NodeIdAttributeNumber:节点 ID
  • MinTransactionIdAttributeNumber:最小事务 ID
  1. 如果只需要填充常量列,根据 TID 信息计算出存活行数,并设置输出向量 vbout 的行数。

  总之,该函数的主要功能是根据传入的 TID 信息,填充列存储表的数据,并将结果存储在输出向量 vbout 中。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

void CStore::ScanByTids(_in_ CStoreIndexScanState* state, _in_ VectorBatch* idxOut, _out_ VectorBatch* vbout)
{
    // 断言确保传递的扫描状态 `state`、输入向量 `idxOut` 和输出向量 `vbout` 不为空
    Assert(state && idxOut && vbout);
    // 断言确保输入向量 `idxOut` 中至少包含一个列
    Assert(idxOut->m_cols >= 1);

    // 开始记录扫描操作的性能跟踪信息
    CSTORESCAN_TRACE_START(SCAN_BY_TID);

    // 获取索引输出向量的基表属性列信息和属性列数量
    int* indexOutBaseTabAttr = state->m_indexOutBaseTabAttr;
    int indexOutAttrNo = state->m_indexOutAttrNo;

    // 根据是否存在索引表判断是否使用B树索引
    m_useBtreeIndex = (state->m_indexScan == NULL) ? true : false;

    /*
     * Pre-Step: 对于常量目标列表,设置输出行数。
     */
    vbout->m_rows = idxOut->m_rows;

    // 获取TID列的向量
    ScalarVector* tids = idxOut->m_arr + idxOut->m_cols - 1;

    // Step 1: 根据TID填充普通列向量
    CSTORESCAN_TRACE_START(FILL_VECTOR_BATCH_BY_TID);
    for (int i = 0; i < m_colNum; i++) {
        int idx = m_colId[i];

        // 判断该列是否已在索引表扫描中处理
        bool isInIndexOut = false;
        for (int j = 0; j < indexOutAttrNo; j++) {
            if (idx == indexOutBaseTabAttr[j] - 1) {
                // 将索引表的扫描结果复制到输出向量 `vbout` 中
                // 注意:这是浅拷贝操作
                FillVectorByIndex(idx, tids, idxOut->m_arr + j, vbout->m_arr + idx);
                vbout->m_rows = (vbout->m_arr + idx)->m_rows;
                isInIndexOut = true;
                break;
            }
        }

        if (isInIndexOut)
            continue;

        // 断言确保 `m_fillVectorByTids` 不为空
        Assert(m_fillVectorByTids[i]);
        // 根据TID填充列向量
        (this->*m_fillVectorByTids[i])(idx, tids, &vbout->m_arr[idx]);
        vbout->m_rows = vbout->m_arr[idx].m_rows;
    }
    CSTORESCAN_TRACE_END(FILL_VECTOR_BATCH_BY_TID);

    // Step 2: 填充系统列(如TID、表OID、节点ID、最小事务ID等)
    for (int i = 0; i < m_sysColNum; i++) {
        int sysColIdx = m_sysColId[i];
        ScalarVector* sysVec = vbout->GetSysVector(sysColIdx);

        switch (sysColIdx) {
            case SelfItemPointerAttributeNumber: {
                FillSysVecByTid<SelfItemPointerAttributeNumber>(tids, sysVec);
                break;
            }
            case TableOidAttributeNumber: {
                FillSysVecByTid<TableOidAttributeNumber>(tids, sysVec);
                break;
            }
            case XC_NodeIdAttributeNumber: {
                FillSysVecByTid<XC_NodeIdAttributeNumber>(tids, sysVec);
                break;
            }
            case MinTransactionIdAttributeNumber: {
                FillSysVecByTid<MinTransactionIdAttributeNumber>(tids, sysVec);
                break;
            }
            default: {
                ereport(ERROR,
                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                         (errmsg("无法填充不支持的系统列 %d 到列存储表", sysColIdx))));
                break;
            }
        }

        vbout->m_rows = sysVec->m_rows;
    }

    // Step 3: 如果只需要填充常量列
    if (unlikely(m_onlyConstCol)) {
        // 仅设置行数,不进行具体数据填充
        int liveRows = 0;
        ScalarVector* vec = vbout->m_arr;
        ScalarValue* tidValue = tids->m_vals;
        uint32 curCUId = InValidCUID;
        uint32 thisCUId = InValidCUID;
        uint32 rowOffset = 0;

        for (int i = 0; i < tids->m_rows; i++) {
            ItemPointer tidPtr = (ItemPointer)&tidValue[i];
            thisCUId = ItemPointerGetBlockNumber(tidPtr);

            // 注意:TID的rowOffset从1开始
            rowOffset = ItemPointerGetOffsetNumber(tidPtr) - 1;

            // 获取CU描述信息和删除掩码(如果需要)
            if (curCUId != thisCUId) {
                curCUId = thisCUId;
                GetCUDeleteMaskIfNeed(curCUId, m_snapshot);
            }
            // 如果是存活行而不是已删除行,增加存活行数
            if (m_delMaskCUId != InValidCUID && !IsDeadRow(curCUId, rowOffset))
                ++liveRows;
        }

        // 设置输出向量 `vec` 的行数
        vec->m_rows = liveRows;
        vbout->m_rows = vec->m_rows;
    }

    // 结束性能跟踪
    CSTORESCAN_TRACE_END(SCAN_BY_TID);
}

CStore::CStoreScanWithCU 函数

  CStore::CStoreScanWithCU 函数的主要作用是扫描列存储表中的数据,但是与通常的 CStoreScan 不同,它将整个"行"(具有相同的 CUID)的 CU 以及相应的 CUDesc位图一次性加载处理,通常用于 CStore 分区合并支持 ADIO 操作,通过加载和处理 CUCUDesc 来实现列存储表的查询和操作,同时支持 CU 的验证。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/*
 * @Description: 类似于CStoreScan,但是移动整个CU的"行"(相同的CUID),以及相应的CUDesc和位图。
 *			   到目前为止,主要用于CStore分区合并。
 *			   支持ADIO。
 *			   注意:通过使用CUStorage->LoadCU,我们使用CStoreMemAlloc::Palloc来分配分配给BatchCUData->CUptrData的CU_ptr(s)。
 *			   因此,它们在完成后必须在CStoreMemAlloc::Pfree中释放。
 * @See also: ATExecCStoreMergePartition
 */
void CStore::CStoreScanWithCU(_in_ CStoreScanState* state, __inout BatchCUData* batchCUData, _in_ bool isVerify)
{
    // 步骤1: 保持的CUDesc的数量是max_loaded_cudesc
    // 如果我们一次加载所有CUDesc,内存不够。
    // 因此,我们一次加载max_loaded_cudesc的CUDesc
    LoadCUDescIfNeed();

    // 步骤2: 如果需要,进行粗略检查
    // 通过CU的最小/最大值消除CU。
    // 对ADIO非常重要。
    RoughCheckIfNeed(state);

    /*
     * 步骤3: 是否有CU命中
     * 我们将不填充向量,因为没有CU命中
     */
    ADIO_RUN()
    {
        if (unlikely(m_cursor == m_NumCUDescIdx)) {
            return;
        }
    }
    ADIO_ELSE()
    {
        if (unlikely(m_NumLoadCUDesc == 0)) {
            return;
        }
    }
    ADIO_END();

    /*
     * 步骤4:
     * 加载当前正在处理的行的CUDescs和CUs
     *
     *   1. 带有CUDescs的已处理行的数量
     *   2. 已死行的数量。因为我们不处理复制CU中的死行
     */
    int cuDescIdx = m_CUDescIdx[m_cursor];
    Form_pg_attribute* attrs = m_relation->rd_att->attrs;
    for (int i = 0; i < m_colNum; ++i) {
        /* colIdx是物理列ID */
        int colIdx = m_colId[i];
        if (attrs[colIdx]->attisdropped) {
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_OPERATION),
                     (errmsg("不能加载表 \"%s\" 的已删除列 \"%s\" 的CUDesc和CU",
                             RelationGetRelationName(m_relation),
                             NameStr(attrs[colIdx]->attname))));
        }

        CUDesc* cuDescPtr = &(m_CUDescInfo[i]->cuDescArray[cuDescIdx]);
        CU* cuDataPtr =
            New(CurrentMemoryContext) CU(attrs[colIdx]->attlen, attrs[colIdx]->atttypmod, attrs[colIdx]->atttypid);

        /* 在LoadCU中使用CStoreMemAlloc::Palloc,需要在完成后调用CStoreMemAlloc::Pfree */
        if (cuDescPtr->cu_size > 0) {
            m_cuStorage[colIdx]->LoadCU(cuDataPtr,
                                        cuDescPtr->cu_pointer,
                                        cuDescPtr->cu_size,
                                        g_instance.attr.attr_storage.enable_adio_function,
                                        false);

            if (cuDataPtr->IsVerified(cuDescPtr->magic) == false) {
                addBadBlockStat(
                    &m_cuStorage[colIdx]->m_cnode.m_rnode, ColumnId2ColForkNum(m_cuStorage[colIdx]->m_cnode.m_attid));

                if (RelationNeedsWAL(m_relation) && CanRemoteRead()) {
                    ereport(WARNING,
                            (errcode(ERRCODE_DATA_CORRUPTED),
                             (errmsg("表 \"%s\" 的CU文件 %s 偏移 %lu 中的CU %u 无效,尝试进行远程读取",
                                     RelationGetRelationName(m_relation),
                                     relcolpath(m_cuStorage[colIdx]),
                                     cuDescPtr->cu_pointer,
                                     cuDescPtr->cu_id)),
                             handle_in_client(true)));

                    m_cuStorage[colIdx]->RemoteLoadCU(cuDataPtr,
                                                      cuDescPtr->cu_pointer,
                                                      cuDescPtr->cu_size,
                                                      g_instance.attr.attr_storage.enable_adio_function,
                                                      false);

                    if (cuDataPtr->IsVerified(cuDescPtr->magic)) {
                        m_cuStorage[colIdx]->OverwriteCU(
                            cuDataPtr->m_compressedBuf, cuDescPtr->cu_pointer, cuDescPtr->cu_size, false);
                    } else {
                        ereport(ERROR,
                                (errcode(ERRCODE_DATA_CORRUPTED),
                                 (errmsg("远程读取CU失败,网络数据损坏")));
                    }
                } else {
                    int elevel = ERROR;
                    if (isVerify) {
                        elevel = WARNING;
                    }
                    ereport(elevel,
                            (errcode(ERRCODE_DATA_CORRUPTED),
                             (errmsg("CU验证失败。节点是 %s,表 %s 中CU %u 的CU文件 %s 偏移 %lu 无效",
                                     g_instance.attr.attr_common.PGXCNodeName,
                                     RelationGetRelationName(m_relation),
                                     cuDescPtr->cu_id,
                                     relcolpath(m_cuStorage[colIdx]),
                                     cuDescPtr->cu_pointer)),
                             handle_in_client(true)));
                }
            }
        }

        *batchCUData->CUDescData[colIdx] = *cuDescPtr;
        batchCUData->CUptrData[colIdx] = cuDataPtr;
        GetCUDeleteMaskIfNeed(cuDescPtr->cu_id, m_snapshot);
    }

    batchCUData->hasValue = true;

    batchCUData->CopyDelMask(m_hasDeadRow ? m_cuDelMask : NULL);

    // 步骤5: 刷新游标
    // 由于我们整体移动CU,因此只需要移动游标
    IncLoadCuDescIdx(m_cursor);

    // 在此函数中,我们不应该进入CU中的行
    Assert(m_rowCursorInCU == 0);

    // 步骤6: 如果需要,进行预取
    ADIO_RUN()
    {
        CUListPrefetch();
    }
    ADIO_END();
}

CStore::LoadCUDescIfNeed 函数

  这段代码是 CStore 数据库存储引擎的一个函数,主要用于在扫描过程中加载CUDesc(列存储的控制信息)信息。该函数的具体功能包括:

  1. 首先,检查是否需要加载 CUDesc。加载 CUDesc 信息时,会根据列数来决定一次性加载还是分批加载,以免内存不足。
  2. 如果需要加载 CUDesc,会先重置内存上下文,以便分批加载
  3. 接着,循环加载 CUDesc 信息每次加载一个CUDesc,并将其记录在内存中。
  4. 如果 ADIOAsynchronous Direct I/O)功能启用,会检查是否需要预取更多的 CUDesc 信息,并根据需求进行加载。
  5. 最后,根据加载的 CUDesc 信息,决定是否需要进行粗略检查Rough Check)。

  需要注意的是,CUDesc 信息用于控制列存储中的列数据的读取,因此加载 CUDesc 信息是扫描过程中的一个重要步骤,有助于提高列存储扫描的效率和性能
  此外,代码中包括了一些条件编译的宏ADIO_RUNADIO_ELSEADIO_ENDBFIO_RUNBFIO_END),这些宏用于根据不同的编译选项来控制ADIOBFIOBuffered I/O)功能的相关逻辑,以优化 I/O 操作
  函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

// The number of holding CUDesc is max_loaded_cudesc
// if we load all CUDesc once, the memory will not enough.
// So we load CUdesc once for max_loaded_cudesc
void CStore::LoadCUDescIfNeed()
{
    uint32 last_load_num = 0; // 上一次加载的CUDesc数量
    int32 cudesc_idx = 0;  // cudesc_idx在缓冲IO时设置为0,在ADIO时设置为m_NumCUDescIdx

    if (!NeedLoadCUDesc(cudesc_idx)) { // 如果不需要加载CUDesc信息,直接返回
        return;
    }

    m_NumLoadCUDesc = 0; // 已加载的CUDesc数量,初始化为0

    Assert(m_perScanMemCnxt); // 断言确保内存上下文m_perScanMemCnxt存在
    // 当一批CU已经扫描和处理完时,重置内存上下文
    MemoryContextReset(m_perScanMemCnxt);
#ifdef MEMORY_CONTEXT_CHECKING
    MemoryContextCheck(m_perScanMemCnxt->parent, m_perScanCnxt->parent->session_id > 0);
#endif

    // 加载CUDesc信息到m_cuDescInfo中,用于所有访问的列
    if (m_colNum > 0) {
        last_load_num = m_CUDescInfo[0]->curLoadNum; // 记录第一列的已加载CUDesc数量
    }

    do {
        bool found = false; // 标志是否找到CUDesc
        for (int i = 0; i < m_colNum; ++i) { // 遍历所有访问的列
            Assert(m_colId[i] >= 0); // 断言确保列ID大于等于0
            // 如果启用ADIO,加载一个CU用于计算预取数量
            found = LoadCUDesc(m_colId[i], m_CUDescInfo[i], g_instance.attr.attr_storage.enable_adio_function, m_snapshot);
        }

        if (likely(m_colNum > 1 && m_CUDescInfo[0]->curLoadNum > 0)) {
            CheckConsistenceOfCUDescCtl(); // 检查CUDescCtl的一致性
            /* 检查所有列的第一个CUDesc */
            CheckConsistenceOfCUDesc(0);
            /* 检查所有列的最后一个CUDesc */
            if (m_CUDescInfo[0]->curLoadNum > 1) {
                CheckConsistenceOfCUDesc(m_CUDescInfo[0]->curLoadNum - 1);
            }
        }

        if (m_colNum > 0) {
            for (int j = (int)m_CUDescInfo[0]->lastLoadNum; j != (int)m_CUDescInfo[0]->curLoadNum;
                 IncLoadCuDescIdx(j)) {
                m_CUDescIdx[cudesc_idx] = j; // 记录已加载CUDesc的索引
                IncLoadCuDescIdx(cudesc_idx);
            }
            m_NumLoadCUDesc += LoadCudescMinus(m_CUDescInfo[0]->lastLoadNum, m_CUDescInfo[0]->curLoadNum);
        }

        ADIO_RUN()
        {
            // 如果找到CUDesc,需要检查预取数量并决定是否需要加载更多CUDesc
            if (found && m_prefetch_quantity < m_prefetch_threshold &&
                HasEnoughCuDescSlot(last_load_num, m_CUDescInfo[0]->curLoadNum)) {
                continue;
            }
            // 加载完成,设置lastLoadNum为备份值
            for (int i = 0; i < m_colNum; ++i) {
                m_CUDescInfo[i]->lastLoadNum = last_load_num;
            }
            if (m_colNum > 0) {
                // 这里设置一个最小的预取计数,因为我们需要预取窗口来控制是否需要预取
                t_thrd.cstore_cxt.cstore_prefetch_count = Max(m_NumLoadCUDesc, CSTORE_MIN_PREFETCH_COUNT);
                ereport(DEBUG1,
                        (errmodule(MOD_ADIO),
                         errmsg("LoadCUDesc: columns(%d), count(%d), quantity(%d)",
                                m_colNum,
                                m_NumLoadCUDesc,
                                m_prefetch_quantity)));
            }
            break;
        }
        ADIO_ELSE()
        {
            break;
        }
        ADIO_END();
    } while (1);

    // sys列和const列
    if (m_colNum > 0 && m_sysColNum != 0) {
        // 访问普通列和sys列,使用普通列的CUDesc
        m_virtualCUDescInfo = m_CUDescInfo[0];
    } else if (OnlySysOrConstCol()) {
        // 只有系统列或常数列,使用第一列的CUDesc
        Assert(m_virtualCUDescInfo);
        LoadCUDesc(m_firstColIdx, m_virtualCUDescInfo, false, m_snapshot);

        for (int j = (int)m_virtualCUDescInfo->lastLoadNum; j != (int)m_virtualCUDescInfo->curLoadNum;
             IncLoadCuDescIdx(j)) {
            m_CUDescIdx[cudesc_idx] = j;
            IncLoadCuDescIdx(cudesc_idx);
        }
        m_NumLoadCUDesc = LoadCudescMinus(m_virtualCUDescInfo->lastLoadNum, m_virtualCUDescInfo->curLoadNum);
        // ADIO使用了它,但无需添加ADIO_RUN(),因为对于缓冲IO,它是无用的
        t_thrd.cstore_cxt.cstore_prefetch_count = m_NumLoadCUDesc;
    }

    // 加载新的CU需要进行粗略检查
    m_needRCheck = true;

    // 在进行粗略检查之前,m_NumCUDescIdx表示已加载的CUDesc信息的长度
    BFIO_RUN()
    {
        m_NumCUDescIdx = m_NumLoadCUDesc;
    }
    BFIO_END();
    return;
}

CStore::LoadCUDesc 函数

  CStore::LoadCUDesc 函数用于从 CStore 列存储中加载指定列的 CUDesc(列式数据块描述)信息,根据给定的加载信息控制结构 LoadCUDescCtl 和快照信息。它允许进行精细的 ADIO(自适应数据管理器)操作,以加载适当数量的 CUDesc 并进行预取控制。这个函数会扫描 CUDesc 表,按 CUID(列式数据块 ID)和属性 ID 筛选相关信息,将这些信息加载到内存中的 CUDesc 结构中,以供后续查询和操作。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/*
 * 根据loadInfoPtr加载列的CUDesc信息
 * LoadCUDescCtrl包含此加载的maxCUDescNum,因为如果加载所有信息,将需要大量内存
 * 这个函数专为ADIO设计,第三个参数adio_work用于控制类似enable_adio_function的ADIO操作,
 * 因为GetLivedRowNumbers不应在ADIO模式下工作
 */
bool CStore::LoadCUDesc(
    _in_ int col, __inout LoadCUDescCtl* loadCUDescInfoPtr, _in_ bool prefetch_control, _in_ Snapshot snapShot)
{
    ScanKeyData key[3];
    HeapTuple tup;
    errno_t rc = EOK;
    bool found = false;
    int loadNum = 0;

    Assert(col >= 0);
    Assert(loadCUDescInfoPtr);
    if (col >= m_relation->rd_att->natts) {
        ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR),
            errmsg("列索引超过列数,列:%d,列数:%d", col, m_relation->rd_att->natts)));
    }
    /*
     * 切换到下一批cudesc数据时,我们将重置m_perScanMemCnxt。
     * 因此,仅用于此批次的空间应由m_perScanMemCnxt进行管理。
     */
    AutoContextSwitch newMemCnxt(m_perScanMemCnxt);

    ADIO_RUN()
    {
        loadCUDescInfoPtr->lastLoadNum = loadCUDescInfoPtr->curLoadNum;
    }
    ADIO_ELSE()
    {
        loadCUDescInfoPtr->lastLoadNum = 0;
        loadCUDescInfoPtr->curLoadNum = 0;
    }
    ADIO_END();

    CUDesc* cuDescArray = loadCUDescInfoPtr->cuDescArray;
    /*
     * 打开CUDesc关系及其索引
     */
    Relation cudesc_rel = heap_open(m_relation->rd_rel->relcudescrelid, AccessShareLock);
    TupleDesc cudesc_tupdesc = cudesc_rel->rd_att;
    Relation idx_rel = index_open(cudesc_rel->rd_rel->relcudescidx, AccessShareLock);
    bool needLengthInfo = m_relation->rd_att->attrs[col]->attlen < 0;
    /* 转换逻辑ID为属性的物理ID */
    int attid = m_relation->rd_att->attrs[col]->attnum;

    /*
     * 设置扫描键,以通过属性ID和CU ID范围从索引中获取数据。
     */
    ScanKeyInit(&key[0], (AttrNumber)CUDescColIDAttr, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(attid));

    ScanKeyInit(&key[1],
                (AttrNumber)CUDescCUIDAttr,
                BTGreaterEqualStrategyNumber,
                F_OIDGE,
                UInt32GetDatum(loadCUDescInfoPtr->nextCUID));

    ScanKeyInit(&key[2], (AttrNumber)CUDescCUIDAttr, BTLessEqualStrategyNumber, F_OIDLE, UInt32GetDatum(m_endCUID));

    snapShot = (snapShot == NULL) ? GetActiveSnapshot() : snapShot;

    Assert(snapShot != NULL);

    SysScanDesc cudesc_scan = systable_beginscan_ordered(cudesc_rel, idx_rel, snapShot, 3, key);
    /* 按照CUID升序顺序扫描cudesc元组 */
    while ((tup = systable_getnext_ordered(cudesc_scan, ForwardScanDirection)) != NULL) {
        Datum values[CUDescCUExtraAttr] = {0};
        bool isnull[CUDescCUExtraAttr] = {0};
        char* valPtr = NULL;

        /* 在这里使用heap_deform_tuple(),因为cudesc tupe的存储方式不合适。
         * min和max是可变长度的,但存储在元组的中间。如果在这里使用fastgetattr()
         * 可能会导致高额的CPU开销。
         * 顺便说一下,最好的存储方式是以以下形式存储元组:
         *   属性1:固定长度
         *   属性2:固定长度
         *   ......        :固定长度
         *   属性n:可变长度
         *   属性n+1:可变长度
         *    ......        :可变长度
         */
        heap_deform_tuple(tup, cudesc_tupdesc, values, isnull);

        uint32 cu_id = DatumGetUInt32(values[CUDescCUIDAttr - 1]);
        Assert(!isnull[CUDescCUIDAttr - 1]);

        if (IsDicVCU(cu_id))
            continue;

        /* 将cusize放入cudesc->cu_size中 */
        int32 cu_size = DatumGetInt32(values[CUDescSizeAttr - 1]);
        Assert(!isnull[CUDescSizeAttr - 1]);

        ADIO_RUN()
        {
            loadNum = (int)loadCUDescInfoPtr->curLoadNum;
            IncLoadCuDescIdx(loadNum);
            /* case1: 检查是否可以加载更多; case 2: m_virtualCUDescInfo在此检查是否数组溢出 */
            if (m_CUDescIdx[m_cursor] == loadNum || !HasEnoughCuDescSlot(loadCUDescInfoPtr->lastLoadNum, loadNum)) {
                break;
            }
            m_prefetch_quantity += cu_size;
        }
        ADIO_ELSE()
        {
            if (!loadCUDescInfoPtr->HasFreeSlot())
                break;
        }
        ADIO_END();

        cuDescArray[loadCUDescInfoPtr->curLoadNum].cu_size = cu_size;
        cuDescArray[loadCUDescInfoPtr->curLoadNum].xmin = HeapTupleGetRawXmin(tup);
        cuDescArray[loadCUDescInfoPtr->curLoadNum].cu_id = cu_id;
        loadCUDescInfoPtr->nextCUID = cu_id;

        /* 并行扫描CU划分。 */
        if (u_sess->stream_cxt.producer_dop > 1 &&
            (cu_id % u_sess->stream_cxt.producer_dop != (uint32)u_sess->stream_cxt.smp_id))
            continue;

        /* 将min值放入cudesc->min中 */
        if (!isnull[CUDescMinAttr - 1]) {
            char* minPtr = cuDescArray[loadCUDescInfoPtr->curLoadNum].cu_min;
            int len_1 = MIN_MAX_LEN;
            valPtr = DatumGetPointer(values[CUDescMinAttr - 1]);
            if (needLengthInfo) {
                *minPtr = VARSIZE_ANY_EXHDR(valPtr);
                minPtr = minPtr + 1;
                len_1 -= 1;
            }
            rc = memcpy_s(minPtr, len_1, VARDATA_ANY(valPtr), VARSIZE_ANY_EXHDR(valPtr));
            securec_check(rc, "", "");
        }
        /* 将max值放入cudesc->max中 */
        if (!isnull[CUDescMaxAttr - 1]) {
            char* maxPtr = cuDescArray[loadCUDescInfoPtr->curLoadNum].cu_max;
            int len_2 = MIN_MAX_LEN;
            valPtr = DatumGetPointer(values[CUDescMaxAttr - 1]);
            if needLengthInfo {
                *maxPtr = VARSIZE_ANY_EXHDR(valPtr);
                maxPtr = maxPtr + 1;
                len_2 -= 1;
            }
            rc = memcpy_s(maxPtr, len_2, VARDATA_ANY(valPtr), VARSIZE_ANY_EXHDR(valPtr));
            securec_check(rc, "", "");
        }

        cuDescArray[loadCUDescInfoPtr->curLoadNum].row_count = DatumGetInt32(values[CUDescRowCountAttr - 1]);
        Assert(!isnull[CUDescRowCountAttr - 1]);

        /* 将CUMode放入cudesc->cumode中 */
        cuDescArray[loadCUDescInfoPtr->curLoadNum].cu_mode = DatumGetInt32(values[CUDescCUModeAttr - 1]);
        Assert(!isnull[CUDescCUModeAttr - 1]);

        /* 将CUPointer放入cudesc->cuPointer中 */
        Assert(col != VitrualDelColID);
        Assert(!isnull[CUDescCUPointerAttr - 1]);
        valPtr = DatumGetPointer(values[CUDescCUPointerAttr - 1]);
        rc = memcpy_s(&cuDescArray[loadCUDescInfoPtr->curLoadNum].cu_pointer,
                      sizeof(CUPointer),
                      VARDATA_ANY(valPtr),
                      sizeof(CUPointer));
        securec_check(rc, "", "");
        Assert(VARSIZE_ANY_EXHDR(valPtr) == sizeof(CUPointer));

        /* 将magic值放入cudesc->magic中 */
        cuDescArray[loadCUDescInfoPtr->curLoadNum].magic = DatumGetUInt32(values[CUDescCUMagicAttr - 1]);
        Assert(!isnull[CUDescCUMagicAttr - 1]);

        found = true;

        IncLoadCuDescIdx(*(int*)&loadCUDescInfoPtr->curLoadNum);
        /* 为ADIO仅加载一个CU,因为我们需要计算预取数量的CU大小 */
        if (prefetch_control) {
            break;
        }
    }

    systable_endscan_ordered(cudesc_scan);
    index_close(idx_rel, AccessShareLock);
    heap_close(cudesc_rel, AccessShareLock);

    ADIO_RUN()
    {
        if (tup == NULL) {
            /* 没有找到tup表示预取已完成 */
            m_load_finish = true;
        }
    }
    ADIO_END();

    if (found) {
        /* nextCUID必须大于已加载的cudesc */
        loadCUDescInfoPtr->nextCUID++;
        return true;
    }
    return false;
}

  以下简述函数 CStore::LoadCUDesc 函数的执行过程:

  1. 首先,函数检查传入的参数,包括列索引col)和加载 CUDesc 信息的控制结构LoadCUDescCtl),确保它们有效。
  2. 针对列的逻辑 ID,确定相应的物理 ID,以访问 CUDesc 表中的相关信息。
  3. 设置合适的扫描键ScanKey),以通过索引访问 CUDesc 表中的行。这些扫描键通常包括列 IDCUID 范围和其他条件。
  4. 打开 CUDesc 表以及相关的索引,准备进行有序扫描
  5. 通过扫描 CUDesc 表,按 CUID 范围和列 ID 筛选出相关的 CUDesc 信息。函数将 CUDesc 信息加载到内存中的 CUDesc 结构中,包括 CUIDCUBlock 地址大小行数最小值最大值和其他属性。
  6. 如果执行了 ADIO 操作(根据参数 prefetch_control),则该函数在加载单个 CUDesc 后就返回,用于计算预取数量。否则,它会继续加载 CUDesc 信息,直到满足预取条件。
  7. 函数会根据加载的 CUDesc 信息,更新相应的控制结构和状态信息,以确保后续的 CStore 扫描操作能够使用这些加载的 CUDesc 信息。
  8. 执行完加载操作后,函数返回布尔值,指示是否成功加载了 CUDesc 信息。

你可能感兴趣的:(OpenGauss,gaussdb,数据库)