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

列存储(CStore)(一)

  • 概述
  • CStore 类
    • CStore::CreateStorage 函数
      • CUStorage::CreateStorage 函数
    • UnlinkColDataFile 函数
      • CStoreUnlinkCuDataFiles 函数
    • CStore::FormCudescTuple 函数
    • DeformCudescTuple 函数
      • CUDesc 结构体
    • CStore::SaveCUDesc 函数
    • CStore::GetMaxCUID 函数

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

概述

  在【OpenGauss源码学习 —— 列存储(CU)(一)】中我们初步认识了 CU 的结构和作用,以及在【 OpenGauss源码学习 —— 列存储(CU)(二)】中学习了CU的压缩和解压方法。最后通过【 OpenGauss源码学习 —— 列存储(CU)(三)】学习了 CU 的加密和解密操作。
  CUColumn Unit)是用于管理列存储数据库中的列片段数据的类,每个 CU 代表一个列的数据片段,包括数据值空值位图等信息。CU 类允许对列数据进行压缩解压访问操作,以提高列存储系统的性能和存储效率。它为列级别的数据处理提供了精细的控制。
  然而,对于一个完整的列存储数据库系统,需要更高级别的管理和操作,例如表级别的数据文件创建删除截断,以及对整个列存储表的元数据的维护。这就是CStore 类发挥作用的地方。CStore 类提供了这些高级别的操作,用于管理整个列存储表,包括多个 CU 的集合。通过 CStore 类,可以执行诸如创建和删除列存储表处理数据文件维护表级元数据等任务,实现了对整个列存储系统的控制。
  综上所述,CStoreCU 的关系可以被总结为以下三点:

1. CStore 类用于管理列存储表的整体操作,而 CU 类用于管理具体的列片段数据
2. 在列存储系统中,一个表通常会被拆分为多个列片段(CU),每个列片段存储一个列的数据。
3. CStore 可能会使用 CU 类来处理特定列的数据,例如在保存或加载数据时,可能需要操作多个 CU 对象。

  因此,本文将围绕 CStore 类展开学习。

CStore 类

  CStore 类用于管理列存储数据库表的操作和元数据。它提供了一组方法,包括数据文件的创建删除截断,以及对整个列存储表的元数据信息的维护。该类用于实现表级别的管理,使得在列存储数据库系统中更容易管理操作表的数据,从而提高了数据存储和查询性能。
  CStore 类的源码如下所示:(路径:src/include/access/cstore_am.h

/*
 * CStore include a set of common API for ColStore.
 * In future, we can add more API.
 */
class CStore : public BaseObject {
    // public static area
public:
    // create data files
    static void CreateStorage(Relation rel, Oid newRelFileNode = InvalidOid);

    // unlink data files
    static void UnlinkColDataFile(const RelFileNode &rnode, AttrNumber attrnum, bool bcmIncluded);
    static void InvalidRelSpaceCache(RelFileNode *rnode);

    // trunccate data files which relation CREATE and TRUNCATE in same XACT block
    static void TruncateStorageInSameXact(Relation rel);

    // form and deform CU Desc tuple
    static HeapTuple FormCudescTuple(_in_ CUDesc *pCudesc, _in_ TupleDesc pCudescTupDesc,
                                     _in_ Datum values[CUDescMaxAttrNum], _in_ bool nulls[CUDescMaxAttrNum],
                                     _in_ Form_pg_attribute pColAttr);

    static void DeformCudescTuple(_in_ HeapTuple pCudescTup, _in_ TupleDesc pCudescTupDesc,
                                  _in_ Form_pg_attribute pColAttr, _out_ CUDesc *pCudesc);

    // Save CU description information into CUDesc table
    static void SaveCUDesc(_in_ Relation rel, _in_ CUDesc *cuDescPtr, _in_ int col, _in_ int options);

    // form and deform VC CU Desc tuple.
    // We add a virtual column for marking deleted rows.
    // The VC is divided into CUs.
    static HeapTuple FormVCCUDescTup(_in_ TupleDesc cudesc, _in_ const char *delMask, _in_ uint32 cuId,
                                     _in_ int32 rowCount, _in_ uint32 magic);

    static void SaveVCCUDesc(_in_ Oid cudescOid, _in_ uint32 cuId, _in_ int rowCount, _in_ uint32 magic,
                             _in_ int options, _in_ const char *delBitmap = NULL);

    static bool IsTheWholeCuDeleted(_in_ char *delBitmap, _in_ int rowsInCu);
    bool IsTheWholeCuDeleted(_in_ int rowsInCu);

    // get Min/Max value from given *pCudesc*.
    // *min* = true, return the Min value.
    // *min* = false, return the Max value.
    static Datum CudescTupGetMinMaxDatum(_in_ CUDesc *pCudesc, _in_ Form_pg_attribute pColAttr, _in_ bool min,
                                         _out_ bool *shouldFree);

    static bool SetCudescModeForMinMaxVal(_in_ bool fullNulls, _in_ bool hasMinMaxFunc, _in_ bool hasNull,
                                          _in_ int maxVarStrLen, _in_ int attlen, __inout CUDesc *cuDescPtr);

    static bool SetCudescModeForTheSameVal(_in_ bool fullNulls, _in_ FuncSetMinMax SetMinMaxFunc, _in_ int attlen,
                                           _in_ Datum attVal, __inout CUDesc *cuDescPtr);

    static uint32 GetMaxCUID(_in_ Oid cudescHeap, _in_ TupleDesc cstoreRelTupDesc, _in_ Snapshot snapshotArg = NULL);
    static uint32 GetMaxIndexCUID(_in_ Relation heapRel, _in_ List *btreeIndex);

    static CUPointer GetMaxCUPointerFromDesc(_in_ int attrno, _in_ Oid cudescHeap);

    static CUPointer GetMaxCUPointer(_in_ int attrno, _in_ Relation rel);

public:
    CStore();
    virtual ~CStore();
    virtual void Destroy();

    // Scan APIs
    void InitScan(CStoreScanState *state, Snapshot snapshot = NULL);
    void InitReScan();
    void InitPartReScan(Relation rel);
    bool IsEndScan() const;

    // late read APIs
    bool IsLateRead(int id) const;
    void ResetLateRead();

    // update cstore scan timing flag
    void SetTiming(CStoreScanState *state);

    // CStore scan : pass vector to VE.
    void ScanByTids(_in_ CStoreIndexScanState *state, _in_ VectorBatch *idxOut, _out_ VectorBatch *vbout);
    void CStoreScanWithCU(_in_ CStoreScanState *state, BatchCUData *tmpCUData, _in_ bool isVerify = false);

    // Load CUDesc information of column according to loadInfoPtr
    // LoadCUDescCtrl include maxCUDescNum for this load, because if we load all
    // it need big memory to hold
    // 
    bool LoadCUDesc(_in_ int col, __inout LoadCUDescCtl *loadInfoPtr, _in_ bool prefetch_control,
                    _in_ Snapshot snapShot = NULL);

    // Get CU description information from CUDesc table
    bool GetCUDesc(_in_ int col, _in_ uint32 cuid, _out_ CUDesc *cuDescPtr, _in_ Snapshot snapShot = NULL);

    // Get tuple deleted information from VC CU description.
    void GetCUDeleteMaskIfNeed(_in_ uint32 cuid, _in_ Snapshot snapShot);

    bool GetCURowCount(_in_ int col, __inout LoadCUDescCtl *loadCUDescInfoPtr, _in_ Snapshot snapShot);
    // Get live row numbers.
    int64 GetLivedRowNumbers(int64 *deadrows);

    // Get CU data.
    // Note that the CU is pinned
    CU *GetCUData(_in_ CUDesc *cuDescPtr, _in_ int colIdx, _in_ int valSize, _out_ int &slotId);

    CU *GetUnCompressCUData(Relation rel, int col, uint32 cuid, _out_ int &slotId, ForkNumber forkNum = MAIN_FORKNUM,
                            bool enterCache = true) const;

    // Fill Vector APIs
    int FillVecBatch(_out_ VectorBatch *vecBatchOut);

    // Fill Vector of column
    template <bool hasDeadRow, int attlen>
    int FillVector(_in_ int colIdx, _in_ CUDesc *cu_desc_ptr, _out_ ScalarVector *vec);

    template <int attlen>
    void FillVectorByTids(_in_ int colIdx, _in_ ScalarVector *tids, _out_ ScalarVector *vec);

    template <int attlen>
    void FillVectorLateRead(_in_ int seq, _in_ ScalarVector *tids, _in_ CUDesc *cuDescPtr, _out_ ScalarVector *vec);

    void FillVectorByIndex(_in_ int colIdx, _in_ ScalarVector *tids, _in_ ScalarVector *srcVec,
                           _out_ ScalarVector *destVec);

    // Fill system column into ScalarVector
    int FillSysColVector(_in_ int colIdx, _in_ CUDesc *cu_desc_ptr, _out_ ScalarVector *vec);

    template <int sysColOid>
    void FillSysVecByTid(_in_ ScalarVector *tids, _out_ ScalarVector *destVec);

    template <bool hasDeadRow>
    int FillTidForLateRead(_in_ CUDesc *cuDescPtr, _out_ ScalarVector *vec);

    void FillScanBatchLateIfNeed(__inout VectorBatch *vecBatch);

    /* Set CU range for scan in redistribute. */
    void SetScanRange();

    // Judge whether dead row
    bool IsDeadRow(uint32 cuid, uint32 row) const;

    void CUListPrefetch();
    void CUPrefetch(CUDesc *cudesc, int col, AioDispatchCUDesc_t **dList, int &count, File *vfdList);

    /* Point to scan function */
    typedef void (CStore::*ScanFuncPtr)(_in_ CStoreScanState *state, _out_ VectorBatch *vecBatchOut);
    void RunScan(_in_ CStoreScanState *state, _out_ VectorBatch *vecBatchOut);

    int GetLateReadCtid() const;
    void IncLoadCuDescCursor();

public:  // public vars
    // Inserted/Scan Relation
    Relation m_relation;

private:  // private methods.
    // CStore scan : pass vector to VE.
    void CStoreScan(CStoreScanState *state, VectorBatch *vecBatchOut);
    void CStoreMinMaxScan(CStoreScanState *state, VectorBatch *vecBatchOut);

    // 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 LoadCUDescIfNeed();

    // Do RoughCheck if need
    // elimiate CU by min/max value of CU.
    void RoughCheckIfNeed(_in_ CStoreScanState *state);

    // Refresh cursor
    void RefreshCursor(int row, int deadRows);

    void InitRoughCheckEnv(CStoreScanState *state);

    void BindingFp(CStoreScanState *state);
    void InitFillVecEnv(CStoreScanState *state);

    // indicate whether only accessing system column or const column.
    // true, means that m_virtualCUDescInfo is a new and single object.
    // false, means that m_virtualCUDescInfo just a pointer to m_CUDescInfo[0].
    // 
    inline bool OnlySysOrConstCol(void)
    {
        return ((m_colNum == 0 && m_sysColNum != 0) || m_onlyConstCol);
    }

    int LoadCudescMinus(int start, int end) const;
    bool HasEnoughCuDescSlot(int start, int end) const;
    bool NeedLoadCUDesc(int32 &cudesc_idx);
    void IncLoadCuDescIdx(int &idx) const;
    bool RoughCheck(CStoreScanKey scanKey, int nkeys, int cuDescIdx);

    void FillColMinMax(CUDesc *cuDescPtr, ScalarVector *vec, int pos);

    inline TransactionId GetCUXmin(uint32 cuid);

    // only called by GetCUData()
    CUUncompressedRetCode GetCUDataFromRemote(CUDesc *cuDescPtr, CU *cuPtr, int colIdx, int valSize, const int &slotId);

    /* defence functions */
    void CheckConsistenceOfCUDescCtl(void);
    void CheckConsistenceOfCUDesc(int cudescIdx) const;
    void CheckConsistenceOfCUData(CUDesc *cuDescPtr, CU *cu, AttrNumber col) const;

private:
    // control private memory used locally.
    // m_scanMemContext: for objects alive during the whole cstore-scan
    // m_perScanMemCnxt: for memory per heap table scan and temp space
    //    during decompression.
    MemoryContext m_scanMemContext;
    MemoryContext m_perScanMemCnxt;

    // current snapshot to use.
    Snapshot m_snapshot;

    // 1. Accessed user column id
    // 2. Accessed system column id
    // 3. flags for late read
    // 4. each CU storage fro each user column.
    int *m_colId;
    int *m_sysColId;
    bool *m_lateRead;
    CUStorage **m_cuStorage;

    // 1. The CUDesc info of accessed columns
    // 2. virtual CUDesc for sys or const columns
    LoadCUDescCtl **m_CUDescInfo;
    LoadCUDescCtl *m_virtualCUDescInfo;

    // Accessed CUDesc index array
    // After RoughCheck, which CU will be accessed
    // 
    int *m_CUDescIdx;

    // adio param
    int m_lastNumCUDescIdx;
    int m_prefetch_quantity;
    int m_prefetch_threshold;
    bool m_load_finish;

    // Current scan position inside CU
    // 
    int *m_scanPosInCU;

    // Rough Check Functions
    // 
    RoughCheckFunc *m_RCFuncs;

    typedef int (CStore::*m_colFillFun)(int seq, CUDesc *cuDescPtr, ScalarVector *vec);

    typedef struct {
        m_colFillFun colFillFun[2];
    } colFillArray;

    typedef void (CStore::*FillVectorByTidsFun)(_in_ int colIdx, _in_ ScalarVector *tids, _out_ ScalarVector *vec);

    typedef void (CStore::*FillVectorLateReadFun)(_in_ int seq, _in_ ScalarVector *tids, _in_ CUDesc *cuDescPtr,
                                                  _out_ ScalarVector *vec);

    FillVectorByTidsFun *m_fillVectorByTids;
    FillVectorLateReadFun *m_fillVectorLateRead;
    colFillArray *m_colFillFunArrary;

    typedef void (CStore::*fillMinMaxFuncPtr)(CUDesc *cuDescPtr, ScalarVector *vec, int pos);
    fillMinMaxFuncPtr *m_fillMinMaxFunc;

    ScanFuncPtr m_scanFunc;  // cstore scan function ptr

    // node id of this plan
    int m_plan_node_id;

    // 1. Number of accessed user columns
    // 2. Number of accessed system columns.
    int m_colNum;
    int m_sysColNum;

    // 1. length of loaded CUDesc info or virtual CUDesc info
    // 2. length of m_CUDescIdx
    int m_NumLoadCUDesc;
    int m_NumCUDescIdx;

    // 1. CU id of current deleted mask.
    // 2. Current access cursor in m_CUDescIdx
    // 3. Current access row cursor inside CU
    uint32 m_delMaskCUId;
    int m_cursor;
    int m_rowCursorInCU;

    uint32 m_startCUID; /* scan start CU ID. */
    uint32 m_endCUID;   /* scan end CU ID. */

    unsigned char m_cuDelMask[MaxDelBitmapSize];

    // whether dead rows exist
    bool m_hasDeadRow;
    // Is need do rough check
    bool m_needRCheck;
    // Only access const column
    bool m_onlyConstCol;

    bool m_timing_on; /* timing CStoreScan steps */

    RangeScanInRedis m_rangeScanInRedis; /* if it is a range scan at redistribution time */

    // cbtree index flag
    bool m_useBtreeIndex;

    // the first column index, start from 0
    int m_firstColIdx;

    // for late read
    // the cuDesc id of batch in the cuDescArray.
    int m_cuDescIdx;

    // for late read
    // the first late read column idx which is filled with ctid.
    int m_laterReadCtidColIdx;
};

  为了能够查阅起来更加直观,我通过以下表格汇总了 CStore 类中包含的成员函数极其作用。

函 数 作 用
CreateStorage 创建数据文件,通常用于创建与给定关系(表)相关的列存储数据文件。
UnlinkColDataFile 取消链接(删除)与给定关系、属性号和是否包括 BCM(块压缩信息)相关的数据文件。
InvalidRelSpaceCache 使与给定文件节点(RelFileNode)相关的关系空间缓存无效,通常在数据文件操作后使用。
TruncateStorageInSameXact 在同一事务块中,对于在创建和截断关系数据文件的情况,截断数据文件以释放空间。
FormCudescTuple 用于创建和解析 CU 描述元组(Tuple)的方法,CU 描述包含列片段的元数据信息。
SaveCUDesc CU 描述信息保存到 CU 描述表中,以维护列片段的元数据。
FormVCCUDescTup 用于创建和保存虚拟列 CU 描述元组,通常用于标记已删除的行。
IsTheWholeCuDeleted 用于检查列片段是否完全被删除,通常通过检查删除位图来确定。
CudescTupGetMinMaxDatum CU 描述中获取最小或最大值的数据,用于统计和查询操作。
SetCudescModeForMinMaxVal 用于设置 CU 描述的模式,以表示最小和最大值的值。
SetCudescModeForTheSameVal 用于设置 CU 描述的模式,以表示相同的值。
GetMaxCUID 用于获取最大的 CU ID,通常用于管理 CU 描述表。
GetMaxCUPointerFromDesc 用于获取最大的 CU 指针,通常用于查询和数据操作。
InitScan 初始化列存储扫描状态,通常与快照(Snapshot)相关联。
InitReScan 初始化重新扫描列存储数据的状态。
InitPartReScan 初始化部分重新扫描列存储数据的状态,通常与关系(Relation)相关联。
IsEndScan 判断列存储扫描是否已结束。
IsLateRead 和 ResetLateRead 用于支持延迟读取(late read)操作,以及重置延迟读取状态。
SetTiming 更新列存储扫描的时间标志。
ScanByTids 和 CStoreScanWithCU 用于执行列存储数据扫描操作,可能包括基于索引的扫描和 CU 数据的获取。
LoadCUDesc 加载列的 CU 描述信息,通常用于元数据的维护。
GetCUDesc 获取指定列的 CU 描述信息,通常用于查询操作。
GetCUDeleteMaskIfNeed 获取 CU 的删除掩码信息,通常用于标记已删除的行。
GetCURowCount 获取 CU 的行数信息,通常用于查询操作。
GetLivedRowNumbers 获取活跃行数,通常用于统计操作。
GetCUData 获取 CU 数据,通常用于填充向量或数据操作。
FillVecBatch 填充向量批次数据。
FillVector 和 FillVectorByTids 填充列向量,通常用于查询操作。
FillVectorLateRead 在延迟读取情况下填充列向量。
FillVectorByIndex 通过索引填充列向量。
FillSysColVector 和 FillSysVecByTid 填充系统列向量,通常用于系统列的查询操作。
FillTidForLateRead 和 FillScanBatchLateIfNeed 用于支持延迟读取操作。
SetScanRange 设置扫描范围,通常用于数据重分布。
CUListPrefetch 和 CUPrefetch 预取 CU 数据,以提高性能。
RunScan 运行列存储扫描操作。
GetLateReadCtid 和 IncLoadCuDescCursor 获取延迟读取的 CTID(行标识符)和增加加载 CU 描述游标。
CStoreScan 和 CStoreMinMaxScan 用于执行列存储数据扫描操作,包括通常扫描和最小/最大值扫描。
LoadCUDescIfNeed 加载 CU 描述信息,如果需要的话,通常用于元数据的管理。
RoughCheckIfNeed 执行粗略检查,根据 CU 的最小/最大值来排除不必要的 CU,以提高查询性能。
RefreshCursor 刷新扫描游标,通常在扫描时使用。
InitRoughCheckEnv 初始化粗略检查的环境,以准备执行粗略检查。
BindingFp 和 InitFillVecEnv 绑定扫描状态,通常在扫描时使用。
OnlySysOrConstCol 用于指示是否仅访问系统列或常量列,通常与 CU 描述相关。
LoadCudescMinus 加载 CU 描述信息,通常用于缓存。
HasEnoughCuDescSlot 检查是否有足够的 CU 描述槽,通常用于缓存。
NeedLoadCUDesc 和 IncLoadCuDescIdx 确定是否需要加载 CU 描述信息以及增加加载 CU 描述的索引。
RoughCheck 执行粗略检查,以确定是否需要访问指定 CU,通常用于查询操作。
FillColMinMax 填充列的最小和最大值,通常用于统计操作。
GetCUXmin 获取 CUXmin 值,通常用于元数据的查询。
GetCUDataFromRemote 从远程获取 CU 数据,通常用于跨节点数据访问。
CheckConsistenceOfCUDescCtl 和 CheckConsistenceOfCUDesc 用于检查 CU 描述控制和 CU 描述的一致性,通常用于数据完整性检查。

  以上表格中列举的这些函数是用于执行列存储数据库系统中的扫描元数据加载数据填充性能优化数据完整性检查等操作的关键功能,支持列存储数据的高效查询和操作。由于这里成员函数数量较多,在后续的学习中可能不能一一介绍,感兴趣的小伙伴可以自行阅读源码。

CStore::CreateStorage 函数

  这是 CStore 类的 CreateStorage 函数,用于在列存储关系(Relation)中为每个属性创建数据文件。函数首先获取关系的描述信息,然后遍历关系的每个属性跳过已经标记为被删除的属性。对于每个属性,它创建一个新的 CUStorage 对象,该对象用于管理列存储数据的存储,然后调用 CreateStorage 函数来创建实际的数据文件。最后,它记录日志并将相关信息插入到待删除列表中。
  该函数用于在初始化列存储关系时,为每个属性创建相应的数据文件,以准备存储列存储数据。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/* DONT call in redo */
// 不要在重做(redo)操作中调用该函数

void CStore::CreateStorage(Relation rel, Oid newRelFileNode)
{
    // 获取关系的描述符(描述关系的结构信息)
    TupleDesc desc = RelationGetDescr(rel);
    // 获取属性的数量
    int nattrs = desc->natts;
    // 获取属性描述符的数组
    Form_pg_attribute* attrs = desc->attrs;
    // 获取关系的持久性
    char relpersistence = rel->rd_rel->relpersistence;

    // 获取关系的节点信息
    RelFileNode rd_node = rel->rd_node;
    // 如果提供了有效的新文件节点,使用新的文件节点
    if (OidIsValid(newRelFileNode)) {
        rd_node.relNode = newRelFileNode;
    }

    // 遍历每个属性
    for (int i = 0; i < nattrs; i++) {
        // 如果属性标记为已删除,跳过
        if (attrs[i]->attisdropped)
            continue;
        // 获取属性编号
        int attrid = attrs[i]->attnum;

        // 创建列存储文件节点对象
        CFileNode cnode(rd_node, attrid, MAIN_FORKNUM);

        // 创建CUStorage对象用于管理列存储数据的存储
        CUStorage* custorage = New(CurrentMemoryContext) CUStorage(cnode);
        // 断言确保CUStorage对象成功创建
        Assert(custorage);
        // 创建实际的数据文件
        custorage->CreateStorage(0, false);
        // 删除CUStorage对象
        DELETE_EX(custorage);

        // 记录日志并将相关信息插入到待删除列表中
        CStoreRelCreateStorage(&rd_node, attrid, relpersistence, rel->rd_rel->relowner);
    }
}

  在这段代码中,提到了通过创建一个 CUStorage 对象用于管理列存储数据的存储。其中,CUStorage 类的主要作用是管理列存储中的数据文件.cu 文件)的存储读写操作。它负责将列存储单元(CU)的数据保存到磁盘从磁盘加载 CU 数据分配存储空间等任务。CUStorage 类与 CU 类关系密切,因为它用于保存和加载 CU 中的数据CUStorage 类通常是由 CStore 类或其他管理列存储文件的类所使用,以执行列存储数据的持久化和读取操作
  总之,CUStorage 类是列存储中负责管理 CU 数据文件的核心组件之一,与 CUCStore 类协同工作,用于实现列存储数据的高效存储和读取。后续有机会的话我们再对 CUStorage 类进行进一步学习。

CUStorage::CreateStorage 函数

  这里简单了解一下 CUStorage::CreateStorage 函数。CUStorage::CreateStorage 函数用于创建列存储数据文件,并执行相关的文件处理步骤。函数源码如下所示:(路径:src/gausskernel/storage/cstore/custorage.cpp

void CUStorage::CreateStorage(int fileId, bool isRedo) const
{
    // 声明一个文件描述符,用于表示创建的文件
    File fd;
    // 声明一个字符数组,用于存储文件的路径
    char filePath[MAXPGPATH];

    // 调用 GetFileName 函数,将文件路径存储在 filePath 中。fileId 是传入的参数,用于指定文件的标识
    GetFileName(filePath, MAXPGPATH, fileId);
    
    // 调用 CreateFile 函数,创建一个名为 filePath 的文件,其中 fileId 表示文件的标识,isRedo 是一个布尔值,表示是否在重做(redo)操作中创建文件。函数返回的文件描述符被存储在 fd 中
    fd = CreateFile(filePath, fileId, isRedo);
    
    // 关闭刚创建的文件,释放文件描述符。这是一个文件处理结束的步骤
    close(fd);
}

UnlinkColDataFile 函数

  UnlinkColDataFile 函数用于解除关系的一个列的数据文件关联,可以根据 bcmIncluded 参数决定是否同时解除 BCM 文件的关联。如果不延迟 DDL 操作,会解除数据文件的关联,否则会在日志中记录消息以表示延迟解除关联。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/* 解除关系的一个列的数据文件关联。 */
void CStore::UnlinkColDataFile(const RelFileNode& rnode, AttrNumber attrnum, bool bcmIncluded)
{
    // 断言,确保列号(attrnum)大于 0
    Assert(attrnum > 0);
    
    // 创建列文件节点对象 cFileNode,用于表示数据文件的位置和属性
    CFileNode cFileNode(rnode, attrnum, MAIN_FORKNUM);

    // 使 CStore 分配器无效化列的空间缓存
    CStoreAllocator::InvalidColSpaceCache(cFileNode);

    // 创建列存储存储对象 cuStorage,用于处理列存储的数据文件
    CUStorage cuStorage(cFileNode);

    // 如果不延迟 DDL 操作
    if (!t_thrd.xact_cxt.xactDelayDDL) {
        /* 解除数据文件的关联:C1.0, C1.1 ... */
        CStoreUnlinkCuDataFiles(&cuStorage);
    } else {
        // 在日志中记录延迟解除关联的消息
        ereport(LOG,
                (errmsg(
                     "延迟解除列文件 %u/%u/%u 属性 %d 的关联", rnode.spcNode, rnode.dbNode, rnode.relNode, attrnum)));
    }

    // 如果包括 BCM 文件
    if (bcmIncluded) {
        /* 解除 BCM 文件的关联:C1_bcm, C1_bcm.1 ... */
        CStoreUnlinkCuBcmFiles(&cuStorage);
    }

    // 销毁 cuStorage 对象,释放相关资源
    cuStorage.Destroy();
}

CStoreUnlinkCuDataFiles 函数

  CStoreUnlinkCuDataFiles 函数用于解除关联 CU 文件,文件名的格式类似 “16385_c1.0”, “16385_c1.1” 等。它会不断增加文件 ID,直到找不到下一个数据文件为止,然后解除文件的关联。如果解除失败,会记录警告消息。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

// 解除 cu 文件的关联:16385_c1.0 16385_c1.1 16385_c1.2 ...
static void CStoreUnlinkCuDataFiles(CUStorage* cuStorage)
{
    // 初始化文件 ID 为 0
    int fileId = 0;
    // 临时文件名缓冲区
    char tmpFileName[MAXPGPATH];

    // 进入循环,直到找不到下一个数据文件
    while (1) {
        // 如果 cuStorage 中的数据文件存在
        if (!cuStorage->IsDataFileExist(fileId))
            break;

        // 获取数据文件的文件名
        cuStorage->GetFileName(tmpFileName, MAXPGPATH, fileId);
        
        // 解除文件的关联,如果解除失败则记录警告消息
        if (unlink(tmpFileName)) {
            ereport(WARNING, (errmsg("无法解除文件的关联 \"%s\": %m", tmpFileName)));
        }
        
        // 增加文件 ID,继续下一个文件的解除
        ++fileId;
    }
}

  解除关联(unlink)文件通常是为了删除不再需要的文件,这在数据库管理系统中也是一个常见的操作。在上下文中,为什么要解除 CU 文件的关联可能有以下几种情况:

  1. 数据文件清理:在数据库中,数据可能被插入更新删除,这会导致生成不再需要的数据文件。解除 CU 文件的关联可以将这些不再需要的文件从磁盘上删除,以释放存储空间
  2. 数据表维护:当执行数据表的结构更改(例如删除列重新组织表等)时,不再需要的 CU 文件可能会成为垃圾文件。解除关联可以清理这些垃圾文件。
  3. 数据备份:在备份数据库时,通常只需备份最新的数据文件,而不需要备份不再使用的 CU 文件。因此,解除 CU 文件的关联可以减小备份文件的大小
  4. 性能优化减少磁盘上的无用文件数量可以提高文件系统性能,减少不必要的磁盘 I/O 操作。

CStore::FormCudescTuple 函数

  CStore::FormCudescTuple 函数的作用是CUDesc 结构中的信息构建成数据库表中的一个元组,包括列编号CU ID最小值最大值行数CU 模式CU 大小CU 指针以及CU 魔数等信息,并返回构建好的元组。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

// CStore::FormCudescTuple - 创建Cudesc元组,填充CUDesc对象的信息并返回元组
// 参数:
//   pCudescTupDesc: Cudesc元组的描述
//   pCudesc: 包含完整Cudesc元组信息的CUDesc对象
//   pTupVals: 用于构建元组的值数组
//   pTupNulls: 用于构建元组的空值标志数组
//   pColAttr: 与pCudesc匹配的列属性,用于列存储表
// 返回:
//   创建并填充的Cudesc元组
HeapTuple CStore::FormCudescTuple(_in_ CUDesc* pCudesc, _in_ TupleDesc pCudescTupDesc,
                                  _in_ Datum pTupVals[CUDescMaxAttrNum], _in_ bool pTupNulls[CUDescMaxAttrNum], _in_ Form_pg_attribute pColAttr)
{
    // 初始化pTupNulls数组,确保不包含空值
    errno_t rc = memset_s(pTupNulls, CUDescMaxAttrNum, false, CUDescMaxAttrNum);
    securec_check(rc, "\0", "\0");

    // 设置元组的属性值:
    // CUDescColIDAttr: 列编号
    pTupVals[CUDescColIDAttr - 1] = Int32GetDatum(pColAttr->attnum);

    // CUDescCUIDAttr: cu_id
    pTupVals[CUDescCUIDAttr - 1] = UInt32GetDatum(pCudesc->cu_id);

    // 计算最小值和最大值的数据长度和指针
    int minDataLen = 0, maxDataLen = 0;
    char *minDataPtr = NULL, *maxDataPtr = NULL;

    if (pColAttr->attlen > 0) {
        if (pColAttr->attbyval) {
            // 如果属性是按值存储,使用int8的大小存储
            minDataLen = maxDataLen = sizeof(Datum);
        } else if (pColAttr->attlen <= MIN_MAX_LEN) {
            // 如果属性长度不超过MIN_MAX_LEN,则使用属性的长度存储
            minDataLen = maxDataLen = pColAttr->attlen;
        } else {
            // 如果属性长度大于MIN_MAX_LEN,则抛出异常
            Assert(minDataLen == 0 && maxDataLen == 0);
        }

        // 设置最小值和最大值的指针
        minDataPtr = pCudesc->cu_min;
        maxDataPtr = pCudesc->cu_max;
    } else {
        // 属性的长度为0的情况
        Assert(pCudesc->cu_min[0] >= 0 && pCudesc->cu_min[0] < MIN_MAX_LEN);
        Assert(pCudesc->cu_max[0] >= 0 && pCudesc->cu_max[0] < MIN_MAX_LEN);

        // 设置最小值和最大值的数据长度和指针
        minDataLen = pCudesc->cu_min[0];
        minDataPtr = pCudesc->cu_min + 1;
        maxDataLen = pCudesc->cu_max[0];
        maxDataPtr = pCudesc->cu_max + 1;
    }

    // 设置CUDescMinAttr和CUDescMaxAttr属性,分别表示最小值和最大值的文本数据
    pTupVals[CUDescMinAttr - 1] = PointerGetDatum(cstring_to_text_with_len(minDataPtr, minDataLen));
    pTupVals[CUDescMaxAttr - 1] = PointerGetDatum(cstring_to_text_with_len(maxDataPtr, maxDataLen));

    // 设置CUDescRowCountAttr属性为行数
    pTupVals[CUDescRowCountAttr - 1] = Int32GetDatum(pCudesc->row_count);

    // 设置CUDescCUModeAttr属性为CU模式
    pTupVals[CUDescCUModeAttr - 1] = Int32GetDatum(pCudesc->cu_mode);

    // 设置CUDescSizeAttr属性为CU大小
    pTupVals[CUDescSizeAttr - 1] = Int32GetDatum(pCudesc->cu_size);

    // 设置CUDescCUPointerAttr属性为CU指针
    text* tmpStr3 = cstring_to_text_with_len((const char*)&pCudesc->cu_pointer, sizeof(CUPointer));
    pTupVals[CUDescCUPointerAttr - 1] = PointerGetDatum(tmpStr3);

    // 设置CUDescCUMagicAttr属性为CU魔数
    pTupVals[CUDescCUMagicAttr - 1] = UInt32GetDatum(pCudesc->magic);
    Assert(pTupVals[CUDescCUMagicAttr - 1] > 0);

    // 标记CUDescCUExtraAttr属性为null

    // 使用tableam_tops_form_tuple函数创建并返回构建好的元组
    return (HeapTuple)tableam_tops_form_tuple(pCudescTupDesc, pTupVals, pTupNulls, HEAP_TUPLE);
}

  这里举一个具体案例来说明该函数的功能:假设有一个列存储表,其中存储了某个销售产品的销售额数据。每个CU(列单元)代表一段数据,每个 CU 包括了该段数据的最小值最大值行数等信息。现在,在该表中新增一段数据,需要创建一个新的 CU 并将其相关信息存储到数据库中。这时,就可以使用该函数将新 CU 的相关信息构建成一个数据库表中的元组,并插入到 CUDesc 表中,以便数据库系统能够管理和查询该新 CU 的数据。这样,该函数将新的 CU 描述信息转化为数据库中的一个元组,为数据库中的数据操作提供了必要的元数据。

DeformCudescTuple 函数

  DeformCudescTuple 函数的主要作用是CUDesc 表中的元组解析成 CUDesc 结构,以提取 CU 的描述信息CU 描述信息包括 CUID最小值最大值行数模式大小指针等属性,这些信息在列存储表中用于管理和查询 CU 的数据。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/* 
 * description: 该函数用于将CUDesc表的元组解析成CUDesc结构,以提取CU的描述信息。
 */
void CStore::DeformCudescTuple(
    _in_ HeapTuple pCudescTup,             // 输入参数,CUDesc表中的元组
    _in_ TupleDesc pCudescTupDesc,         // 输入参数,CUDesc表的元组描述
    _in_ Form_pg_attribute pColAttr,      // 输入参数,列属性信息,对应于CUDesc的列
    _out_ CUDesc* pCudesc                 // 输出参数,用于存储解析后的CU描述信息
)
{
    errno_t rc = EOK;
    bool isnull = false;

    // 从CUDesc元组中提取并设置CU的ID
    pCudesc->cu_id = DatumGetUInt32(fastgetattr(pCudescTup, CUDescCUIDAttr, pCudescTupDesc, &isnull));
    Assert(!isnull);

    // 将最小值存入pCudesc->cu_min
    char* valPtr = DatumGetPointer(fastgetattr(pCudescTup, CUDescMinAttr, pCudescTupDesc, &isnull));
    if (!isnull) {
        if (pColAttr->attlen > 0) {
            if (pColAttr->attbyval) {
                // 对于固定大小的基本类型,使用 sizeof(Datum) 存储
                Assert((int)VARSIZE_ANY_EXHDR(valPtr) == sizeof(Datum));
                rc = memcpy_s(pCudesc->cu_min, MIN_MAX_LEN, VARDATA_ANY(valPtr), sizeof(Datum));
                securec_check(rc, "", "");
            } else if (pColAttr->attlen <= MIN_MAX_LEN) {
                // 对于可变大小类型,存储具体长度
                Assert((int)VARSIZE_ANY_EXHDR(valPtr) == pColAttr->attlen);
                rc = memcpy_s(pCudesc->cu_min, MIN_MAX_LEN, VARDATA_ANY(valPtr), pColAttr->attlen);
                securec_check(rc, "", "");
            } else {
                Assert(pCudesc->cu_min[0] == 0);
            }
        } else {
            // 对于可变长度的列,元组第一个字节表示长度
            pCudesc->cu_min[0] = VARSIZE_ANY_EXHDR(valPtr);
            if (pCudesc->cu_min[0] > 0) {
                Assert(pCudesc->cu_min[0] < MIN_MAX_LEN);
                rc = memcpy_s(pCudesc->cu_min + 1, (MIN_MAX_LEN - 1), VARDATA_ANY(valPtr), pCudesc->cu_min[0]);
                securec_check(rc, "", "");
            } else {
                Assert(pCudesc->cu_min[0] == 0);
            }
        }
    }

    // 将最大值存入pCudesc->cu_max
    valPtr = DatumGetPointer(fastgetattr(pCudescTup, CUDescMaxAttr, pCudescTupDesc, &isnull));
    if (!isnull) {
        if (pColAttr->attlen > 0) {
            if (pColAttr->attbyval) {
                Assert((int)VARSIZE_ANY_EXHDR(valPtr) == sizeof(Datum));
                rc = memcpy_s(pCudesc->cu_max, MIN_MAX_LEN, VARDATA_ANY(valPtr), sizeof(Datum));
                securec_check(rc, "", "");
            } else if (pColAttr->attlen <= MIN_MAX_LEN) {
                Assert((int)VARSIZE_ANY_EXHDR(valPtr) == pColAttr->attlen);
                rc = memcpy_s(pCudesc->cu_max, MIN_MAX_LEN, VARDATA_ANY(valPtr), pColAttr->attlen);
                securec_check(rc, "", "");
            } else {
                Assert(pCudesc->cu_max[0] == 0);
            }
        } else {
            pCudesc->cu_max[0] = VARSIZE_ANY_EXHDR(valPtr);
            if (pCudesc->cu_max[0] > 0) {
                Assert(pCudesc->cu_max[0] < MIN_MAX_LEN);
                rc = memcpy_s(pCudesc->cu_max + 1, (MIN_MAX_LEN - 1), VARDATA_ANY(valPtr), pCudesc->cu_max[0]);
                securec_check(rc, "", "");
            } else {
                Assert(pCudesc->cu_max[0] == 0);
            }
        }
    }

    // 从CUDesc元组中提取并设置CU的行数
    pCudesc->row_count = DatumGetInt32(fastgetattr(pCudescTup, CUDescRowCountAttr, pCudescTupDesc, &isnull));
    Assert(!isnull);

    // 从CUDesc元组中提取并设置CU的模式
    pCudesc->cu_mode = DatumGetInt32(fastgetattr(pCudescTup, CUDescCUModeAttr, pCudescTupDesc, &isnull));
    Assert(!isnull);

    // 从CUDesc元组中提取并设置CU的大小
    pCudesc->cu_size = DatumGetInt32(fastgetattr(pCudescTup, CUDescSizeAttr, pCudescTupDesc, &isnull));
    Assert(!isnull);

    // 从CUDesc元组中提取并设置CU的指针
    char* cu_ptr = DatumGetPointer(fastgetattr(pCudescTup, CUDescCUPointerAttr, pCudescTupDesc, &isnull));
    if (!isnull) {
        Assert(VARSIZE_ANY_EXHDR(cu_ptr) == sizeof(CUPointer));
        pCudesc->cu_pointer = *(CUPointer*)VARDATA_ANY(cu_ptr);
    } else
        Assert(pCudesc->cu_pointer == 0);

    // 从CUDesc元组中提取并设置CU的标识
    pCudesc->magic = DatumGetUInt32(fastgetattr(pCudescTup, CUDescCUMagicAttr, pCudescTupDesc, &isnull));
    Assert(!isnull);
}

CUDesc 结构体

  细心的小伙伴可以发现,上面的代码中,反复提到了一个结构 CUDesc那它到底是什么呢?有什么作用呢?和 CU 又是什么关系呢?
  CUDesc 结构体用于表示列存储引擎中的 Column UnitCU)的描述信息。CU 是列存储表的基本单位,每个 CU 包含一组行数据,并存储有关这些数据的元信息。CUDesc 结构体的字段和方法用于描述和操作 CU 的属性和状态。
  CUDesc 结构体描述了 CU元信息,而 CU 类存储了 CU实际数据。这两个类协同工作,以便在列存储引擎中管理和查询列存储数据。CUDesc 通常用于获取元信息,以便定位和操作 CU 中的实际数据。
  CUDESC 表和 CU 对应关系示意图如下所示:
【 OpenGauss源码学习 —— 列存储(CStore)(一)】_第1张图片
  CUDesc 类源码如下所示:(路径:src/include/storage/cu.h

struct CUDesc : public BaseObject {
    TransactionId xmin;
    /*
     * CU的序列号
     */
    uint32 cu_id;

    // CU的最小值
    // 如果数据类型是固定长度,cu_min存储数值。
    // 如果数据类型是可变长度,cu_min存储长度和值
    // 格式: 长度(1字节) 值(长度 <= MIN_MAX_LEN)
    //
    char cu_min[MIN_MAX_LEN];

    // CU的最大值
    // 如果数据类型是固定长度,cu_max存储数值。
    // 如果数据类型是可变长度,cu_max存储长度和值
    // 格式: 长度(1字节) 值(长度 <= MIN_MAX_LEN)
    //
    char cu_max[MIN_MAX_LEN];

    /*
     * CU的行数
     */
    int row_count;

    /*
     * CU的数据大小
     */
    int cu_size;

    /*
     * CU信息掩码
     */
    int cu_mode;

    /*
     * CU在CU存储中的指针
     */
    CUPointer cu_pointer;

    /*
     * 魔数用于验证CU数据的有效性
     */
    uint32 magic;

public:
    CUDesc();
    ~CUDesc();

    // 设置为NULL CU
    void SetNullCU();

    // 检查是否为NULL CU
    bool IsNullCU() const;

    // 设置为正常CU
    void SetNormalCU();

    // 设置为包含相同值的CU
    void SetSameValCU();

    // 检查是否为正常CU
    bool IsNormalCU() const;

    // 检查是否为包含相同值的CU
    bool IsSameValCU() const;

    // 设置为没有最小最大值的CU
    void SetNoMinMaxCU();

    // 检查是否为没有最小最大值的CU
    bool IsNoMinMaxCU() const;

    // 设置CU包含NULL值
    void SetCUHasNull();

    // 检查CU是否包含NULL值
    bool CUHasNull() const;

    // 重置CU
    void Reset();

    // 销毁CU
    void Destroy() {}
};

CStore::SaveCUDesc 函数

  CStore::SaveCUDesc 函数用于将列单元描述CUDesc信息存储到 CUDesc 表中,以便列存储数据库能够跟踪和管理列单元的元信息,支持列存储引擎的操作。这有助于维护和优化数据的存储结构,并支持列存储表的查询和维护。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

// 将CUDesc(列单元描述)保存到CUDesc表中
void CStore::SaveCUDesc(_in_ Relation rel, _in_ CUDesc* cuDescPtr, _in_ int col, int options) {
    // 确保关系对象不为空
    Assert(rel != NULL);
    // 确保列号大于等于零
    Assert(col >= 0);

    // 如果要保存的列已经被标记为已删除,则抛出错误
    if (rel->rd_att->attrs[col]->attisdropped) {
        ereport(PANIC,
                (errmsg("无法为已删除的列 \"%s\" 保存CUDesc 到表 \"%s\" 中",
                        NameStr(rel->rd_att->attrs[col]->attname),
                        RelationGetRelationName(rel))));
    }

    // 打开CUDesc表
    Relation cudesc_rel = heap_open(rel->rd_rel->relcudescrelid, RowExclusiveLock);
    // 打开CUDesc表的索引
    Relation idx_rel = index_open(cudesc_rel->rd_rel->relcudescidx, RowExclusiveLock);

    // 创建一个用于存储插入数据的数组和NULL值信息的数组
    Datum values[CUDescMaxAttrNum];
    bool nulls[CUDescMaxAttrNum];
    // 创建一个HeapTuple,包含CUDesc的信息
    HeapTuple tup = CStore::FormCudescTuple(cuDescPtr, cudesc_rel->rd_att, values, nulls, rel->rd_att->attrs[col]);

    // 通过插入操作将CUDesc元组插入到CUDesc表中
    // options参数用于控制是否生成XLOG记录以确保数据持久性
    (void)heap_insert(cudesc_rel, tup, GetCurrentCommandId(true), options, NULL);
    // 在CUDesc表的相关索引中插入对应的条目,以支持索引查找和唯一性检查
    index_insert(idx_rel,
                       values,
                       nulls,
                       &(tup->t_self),
                       cudesc_rel,
                       idx_rel->rd_index->indisunique ? UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);

    // 释放CUDesc元组的内存和values数组中的数据
    heap_freetuple(tup);
    pfree(DatumGetPointer(values[CUDescMinAttr - 1]));
    pfree(DatumGetPointer(values[CUDescMaxAttr - 1]));
    pfree(DatumGetPointer(values[CUDescCUPointerAttr - 1]));

    // 关闭CUDesc表和索引,释放锁
    index_close(idx_rel, RowExclusiveLock);
    heap_close(cudesc_rel, RowExclusiveLock);
}

CStore::GetMaxCUID 函数

  CStore::GetMaxCUID 函数用于获取指定列的最大 CU列单元ID,从 CUDesc 表中检索该信息,并支持列存储表的操作。该函数首先创建一个名为 key扫描键,然后打开 CUDesc 关系表,扫描其索引,最终返回指定列的最大CU ID,以便用于列存储表的操作。函数执行过程中考虑了列是否被删除,获取相关的元数据信息,并利用索引执行有序扫描以提高效率。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

uint32 CStore::GetMaxCUID(Oid cudescHeap, TupleDesc cstoreRelTupDesc, Snapshot snapshotArg) {
    ScanKeyData key;
    HeapTuple tup;
    bool isnull = false;

    /* 使用任何快照查找最大的CU ID,包括已中止或崩溃的事务。 */
    Snapshot snapshot = NULL;
    snapshot = snapshotArg ? snapshotArg : SnapshotAny;

    // 查找一个未被删除的列。
    int attrId = 0;
    for (int i = 0; i < cstoreRelTupDesc->natts; ++i) {
        if (!cstoreRelTupDesc->attrs[i]->attisdropped) {
            attrId = cstoreRelTupDesc->attrs[i]->attnum;
            break;
        }
    }
    Assert(attrId > 0);

    // 打开CUDesc关系和它的索引
    Relation heapRel = heap_open(cudescHeap, AccessShareLock);
    TupleDesc heapTupDesc = RelationGetDescr(heapRel);
    Relation indexRel = index_open(heapRel->rd_rel->relcudescidx, AccessShareLock);
    uint32 maxCuId = FirstCUID;

    // 设置用于按列ID从索引中获取数据的扫描键。
    ScanKeyInit(&key, (AttrNumber)CUDescColIDAttr, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(attrId));

    SysScanDesc cudesc_scan = systable_beginscan_ordered(heapRel, indexRel, snapshot, 1, &key);
    // 使用BackwardScanDirection扫描以优化获取列的最后一个CU描述。
    if ((tup = systable_getnext_ordered(cudesc_scan, BackwardScanDirection)) != NULL) {
        maxCuId = DatumGetUInt32(fastgetattr(tup, CUDescCUIDAttr, heapTupDesc, &isnull));
        Assert(!isnull);
    }

    systable_endscan_ordered(cudesc_scan);

    index_close(indexRel, AccessShareLock);
    heap_close(heapRel, AccessShareLock);

    return maxCuId;
}

  对于 CStore 类中的公共静态函数暂且先介绍到这里,对于未介绍的部分等后续涉及到再做详细的学习。

致谢:这里感谢以下作者的相关资料:

  1. openGauss数据库源码解析系列文章——存储引擎源码解析(三)
  2. openGauss存储技术(二)——列存储引擎和内存引擎
  3. 解读数仓中的数据对象及相关关系

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