PostgreSQL码农集散地

XStore 引擎原理

本期播客

电信天翼云 XStore 引擎原理

XStore是OpenTeleDB针对高并发OLTP场景设计的原位更新存储引擎,通过重新设计堆表(xheap)和索引(xbtree),实现数据原位更新,旧数据写入Undo,解决了传统PostgreSQL MVCC机制的空间膨胀和垃圾回收性能波动问题  。

原位更新机制

传统MVCC问题

PostgreSQL的MVCC机制每次更新都会产生新版本数据,旧版本积压在数据页中,需要周期性vacuum清理,导致:

  • 存储空间膨胀
  • 垃圾回收造成性能波动(可达40%以上)
  • 需要复杂的autovacuum配置维护

XStore解决方案

XStore通过原位更新技术解决上述问题:

// XStore行格式定义  
staticconstint ISNULL_ATTRS_PER_BYTE = 8;  

每次数据更新时:

  1. 直接在原位置更新数据
  2. 旧数据写入Undo日志
  3. 通过Undo链实现版本可见性判断
  4. 索引同样进行原位更新,版本信息写入Undo

核心组件架构

1. 访问方法注册

XStore通过扩展注册新的表和索引访问方法:

CREATEACCESS METHOD xstore TYPETABLEHANDLER xheap_tableam_handler;  
CREATEACCESS METHOD xbtree TYPEINDEXHANDLER xbthandler;  

2. 表访问方法实现

XStore实现了完整的表访问方法接口:

staticconst TableAmRoutine xheapam_methods = {  
    .tuple_insert = xheapam_tuple_insert,  
    .tuple_update = xheapam_tuple_update,  
    .tuple_delete = xheapam_tuple_delete,  
// ... 其他接口  
};  

3. Undo系统

XStore维护完整的Undo上下文:

typedefstructUndoLogContext
{

int logs[UNDO_PERSISTENCE_LEVELS];  
void *slots[UNDO_PERSISTENCE_LEVELS];  
    uint64 slot_ptr[UNDO_PERSISTENCE_LEVELS];  
    FullTransactionId prev_xid[UNDO_PERSISTENCE_LEVELS];  
    uint64 curr_trans_undo_size;  
} UndoLogContext;  

事务处理机制

事务回调

XStore注册事务回调处理Undo操作:

voidxstore_xact_callback(XactEvent event, void *arg)
{  
switch (event) {  
case XACT_EVENT_COMMIT:  
            reset_undo_actions_info();  
            clear_my_global_frozen_xmin();  
            release_undo_buffer_in_top_resource_owner();  
break;  
case XACT_EVENT_ABORT:  
            apply_undo_actions();  // 事务中止时应用Undo  
            reset_undo_actions_info();  
            clear_my_global_frozen_xmin();  
            release_undo_buffer_in_top_resource_owner();  
break;  
    }  
}  

XStore实现了基于Undo的可见性判断逻辑,通过遍历Undo链确定元组的可见性状态(只要找到可见版本, 可提前终止遍历)  。

可见性判断

XStore中UNDO指针与可见性判断机制:

1. 原始记录UNDO指针

XStore的原始记录中包含一个指向对应UNDO位置的指针。在XHeapDiskTuple结构中,urec字段存储了指向该记录最新UNDO记录的指针  。

// XHeapTupleTransInfo结构中的urec_add字段  
typedefstructXHeapTupleTransInfo
{

    FullTransactionId xid;  
    CommandId cid;  
    UndoRecPtr urec_add;  // 指向UNDO记录的指针  
} XHeapTupleTransInfo;  

2. 遍历UNDO链确定可见版本

需要遍历UNDO链是因为XStore的MVCC机制要求找到对当前snapshot可见的版本。在get_tuple_from_undo函数中,通过while循环不断回溯UNDO链,直到找到满足可见性条件的版本  。

while (1)  
{  
// 获取UNDO记录并检查可见性  
    state = get_tuple_from_undo_record(urec_add, prev_undo_xid, buffer, offnum, &hdr,  
                                   visible_tuple, &free_tuple, &xinfo, ctid, &last_xid,  
                                   &urp);  

// 检查是否找到可见版本  
    selector = xheap_check_undo_snapshot(snapshot, xinfo, curcid, op, buffer);  
if (selector == XVERSION_CURRENT)  
break;  // 找到可见版本,终止遍历  

// 继续遍历更旧的版本  
    urec_add = xinfo.urec_add;  
    prev_undo_xid = xinfo.xid;  
}  

3. 不能提前终止遍历的原因

不能提前终止遍历是因为必须确保找到的版本对当前snapshot是真正可见的。每个版本都需要经过完整的可见性检查:

  • 事务状态检查:判断修改该版本的事务是否已提交、中止或仍在进行中
  • 快照可见性检查:根据snapshot的xmin/xmax判断版本是否可见
  • 命令ID检查:对于同一事务内的不同命令,需要检查CID的可见性

只有当所有检查都通过时,才能确定该版本是可见的, 此时可终止遍历。

"不能提前终止"指的是不能在完成必要检查之前就终止。具体来说:

  • 必须检查的情况:如果 selector == XVERSION_CHECK_CID,必须获取CID并进行最终判断
  • 可以终止的情况:只有当所有检查完成且确认可见时,才能终止

XStore的遍历终止机制体现了"完整性优先"的设计原则:确保每个版本都经过严格检查,但一旦确认结果就立即终止,避免无效遍历。这种设计既保证了MVCC的正确性,又兼顾了性能效率。

4. 性能优化机制

虽然遍历UNDO链可能带来性能开销,但XStore实现了多种优化机制:

4.1 全局回收XID优化

通过TransactionIdOlderThanAllUndo检查,如果事务ID足够老,可以直接判定为全可见,避免遍历  。

4.2 快照类型优化

针对不同类型的snapshot(如SNAPSHOT_TOAST)提供快速路径  。

4.3 缓存机制

在xheap_tuple_is_surely_dead等函数中,可以使用缓存的transaction信息避免重复获取  。

5. 性能影响分析

在版本很多的情况下,确实可能存在性能问题,但实际影响有限:

  1. 大多数情况下遍历深度有限:通常只需要遍历少数几个版本就能找到可见版本
  2. 全局回收机制:旧版本会被定期清理,避免UNDO链无限增长
  3. 内存访问局部性:UNDO记录通常在内存中连续存储,访问效率较高

如果遇到极端情况(如单个记录被大量更新),XStore会通过错误机制提示"snapshot too old",要求客户端重新查询  。

XStore的UNDO链遍历机制是其原位更新设计的核心组成部分。虽然理论上可能存在性能问题,但通过多种优化机制,在实际应用中能够保持良好的性能表现。这种设计避免了传统PostgreSQL的VACUUM开销,整体上提供了更稳定的性能特征。

系统集成

钩子机制

XStore通过钩子机制集成到PostgreSQL内核:

static FullTransactionId get_global_recycle_xid(void);  
staticvoidset_global_frozen_xid(FullTransactionId xid);  
staticvoidset_global_recycle_xid(FullTransactionId xid);  

资源管理器注册

XStore注册自定义资源管理器处理XHeap和XBTREE的WAL记录:

RegisterCustomRmgr(RM_XHEAP_ID,&xheapRmgr);  
RegisterCustomRmgr(RM_XHEAPUNDO_ID,&xheapUndoRmgr);   
RegisterCustomRmgr(RM_XBTREE_ID,&xbtreeRmgr);  
RegisterCustomRmgr(RM_XBTREE2_ID,&xbtree2Rmgr);  
RegisterCustomRmgr(RM_XUNDOLOG_ID, &undoRmgr);  

使用方式

创建XStore表

-- 方式1:指定存储引擎  
CREATETABLE xt1(a int primary key, b int) USING xstore;  

-- 方式2:设置默认存储引擎  
-- 在postgresql.conf中设置:default_table_access_method = 'xstore'  
CREATETABLE xt1(a int primary key, b int);  

创建XStore索引

-- XStore表默认创建xbtree索引  
CREATEINDEX xbt_idx1 ON xt1(a);  

-- 或显式指定  
CREATEINDEX xbt_idx2 ON xt1 USING xbtree(a);  

技术优势

  1. 高稳定性:原位更新和回滚管理机制,消除autovacuum扫盘需求
  2. 低膨胀率:原位更新技术解决空间膨胀问题,不依赖垃圾回收
  3. 易运维:无需手动vacuum,简化autovacuum配置

Notes

XStore是OpenTeleDB的核心创新之一,通过重新设计存储引擎架构,从根本上解决了PostgreSQL在高并发OLTP场景下的性能瓶颈。其核心思想是将MVCC的版本管理从事务ID转向Undo日志,实现了真正的原位更新,同时保持了ACID特性。