列存储(CStore)(二)
声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》一书以及OpenGauss社区学习文档和一些学习资料
概述
在【 OpenGauss源码学习 —— 列存储(CStore)(一)】中我们介绍了部分 CStore 类中的静态成员函数。本节我们将介绍 CStore 类中的部分公有成员函数。
CStore 类中的公有成员提供了一系列用于处理列存储表的扫描、数据获取、元组处理和性能优化等操作的函数,为数据管理和查询提供了必要的功能支持。
以下是其主要功能和作用的总结:(详细函数功能描述可以查看【 OpenGauss源码学习 —— 列存储(CStore)(一)】中的函数功能说明表)
- 初始化扫描相关函数:InitScan, InitReScan, InitPartReScan, IsEndScan, SetTiming 等函数用于初始化扫描状态,管理扫描进度和时间性能。
- 扫描函数:ScanByTids, CStoreScanWithCU 等函数用于执行列存储表的扫描操作,获取数据并返回结果。
- CUDesc 相关函数:LoadCUDesc, GetCUDesc, GetCUDeleteMaskIfNeed, GetCURowCount 等函数用于加载和获取列的压缩单元描述信息,以及获取行数和元组删除信息。
- 数据获取函数:GetCUData, GetUnCompressCUData 等函数用于获取列存储中的压缩单元数据。
- 填充 Vector 函数:FillVecBatch, FillVector, FillVectorByTids, FillVectorLateRead, FillVectorByIndex, FillSysColVector, FillSysVecByTid, FillTidForLateRead, FillScanBatchLateIfNeed 等函数用于填充向量数据结构,用于结果集的构建和处理。
- 扫描范围设置函数:SetScanRange 用于在重分发操作中设置扫描的 CU 范围。
- 元组生死判断函数:IsDeadRow 用于判断指定的行是否为死亡行。
- 预取函数:CUListPrefetch, CUPrefetch 用于执行预取操作,提高数据读取性能。
- 扫描执行函数:RunScan 用于执行列存储表的扫描操作,返回结果。
- 辅助函数:GetLateReadCtid, IncLoadCuDescCursor 用于辅助扫描操作和数据加载。
CStore::InitScan 函数
CStore::InitScan 函数主要用于初始化列存储扫描的上下文、内存管理和其他必要的状态信息,以便后续执行列存储扫描操作。该函数的作用是初始化用于扫描列存储表的数据结构和环境。首先,它创建了用于扫描的内存上下文和每个扫描的内存上下文,以便有效地管理内存分配。然后,它设置了实际扫描操作的函数指针,并根据表的属性信息初始化压缩单元(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;
}
函数的执行过程包括以下步骤:
- 检查传递的扫描状态和投影信息是否非空。
- 创建用于扫描的内存上下文和每个扫描的内存上下文。
- 设置扫描函数的指针为实际的列存储扫描函数。
- 根据表的属性信息初始化 CU 数据存储结构。
- 分配和初始化用于跟踪已加载 CU 描述的数组。
- 初始化其他扫描相关的参数。
- 设置范围扫描标志。
- 初始化扫描范围。
- 初始化环境并记录计划节点的 ID。
CStore::ScanByTids 函数
该函数用于执行按行扫描列存储表的操作,根据传入的TID(元组标识符)信息,填充输出的列存储数据。函数的执行过程如下:
- 首先,进行断言检查,确保传递的扫描状态 state、输入向量 idxOut 和输出向量 vbout 不为空,并确保输入向量 idxOut 中至少包含一个列。
- 在开始实际扫描操作之前,根据传入的 TID 信息,设置输出行数 vbout->m_rows 为输入向量 idxOut 中的行数。
- 遍历要扫描的所有列,分别进行以下操作:
- 对于索引表中的列,将索引表的扫描结果复制到输出向量 vbout 中。
- 对于非索引列,调用相应的填充函数进行数据填充,并更新输出向量 vbout 的行数。
- 如果需要填充系统列,分别填充以下系统列信息:
- SelfItemPointerAttributeNumber:元组自身的 TID。
- TableOidAttributeNumber:表的 OID。
- XC_NodeIdAttributeNumber:节点 ID。
- MinTransactionIdAttributeNumber:最小事务 ID。
- 如果只需要填充常量列,根据 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 操作,通过加载和处理 CU 和 CUDesc 来实现列存储表的查询和操作,同时支持 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(列存储的控制信息)信息。该函数的具体功能包括:
- 首先,检查是否需要加载 CUDesc。加载 CUDesc 信息时,会根据列数来决定一次性加载还是分批加载,以免内存不足。
- 如果需要加载 CUDesc,会先重置内存上下文,以便分批加载。
- 接着,循环加载 CUDesc 信息,每次加载一个CUDesc,并将其记录在内存中。
- 如果 ADIO(Asynchronous Direct I/O)功能启用,会检查是否需要预取更多的 CUDesc 信息,并根据需求进行加载。
- 最后,根据加载的 CUDesc 信息,决定是否需要进行粗略检查(Rough Check)。
需要注意的是,CUDesc 信息用于控制列存储中的列数据的读取,因此加载 CUDesc 信息是扫描过程中的一个重要步骤,有助于提高列存储扫描的效率和性能。
此外,代码中包括了一些条件编译的宏(ADIO_RUN、ADIO_ELSE、ADIO_END、BFIO_RUN、BFIO_END),这些宏用于根据不同的编译选项来控制ADIO 和 BFIO(Buffered 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 函数的执行过程:
- 首先,函数检查传入的参数,包括列索引(col)和加载 CUDesc 信息的控制结构(LoadCUDescCtl),确保它们有效。
- 针对列的逻辑 ID,确定相应的物理 ID,以访问 CUDesc 表中的相关信息。
- 设置合适的扫描键(ScanKey),以通过索引访问 CUDesc 表中的行。这些扫描键通常包括列 ID、CUID 范围和其他条件。
- 打开 CUDesc 表以及相关的索引,准备进行有序扫描。
- 通过扫描 CUDesc 表,按 CUID 范围和列 ID 筛选出相关的 CUDesc 信息。函数将 CUDesc 信息加载到内存中的 CUDesc 结构中,包括 CUID、CUBlock 地址、大小、行数、最小值、最大值和其他属性。
- 如果执行了 ADIO 操作(根据参数 prefetch_control),则该函数在加载单个 CUDesc 后就返回,用于计算预取数量。否则,它会继续加载 CUDesc 信息,直到满足预取条件。
- 函数会根据加载的 CUDesc 信息,更新相应的控制结构和状态信息,以确保后续的 CStore 扫描操作能够使用这些加载的 CUDesc 信息。
- 执行完加载操作后,函数返回布尔值,指示是否成功加载了 CUDesc 信息。