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;
每次数据更新时:
直接在原位置更新数据 旧数据写入Undo日志 通过Undo链实现版本可见性判断 索引同样进行原位更新,版本信息写入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. 性能影响分析
在版本很多的情况下,确实可能存在性能问题,但实际影响有限:
大多数情况下遍历深度有限:通常只需要遍历少数几个版本就能找到可见版本 全局回收机制:旧版本会被定期清理,避免UNDO链无限增长 内存访问局部性: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);
技术优势
高稳定性:原位更新和回滚管理机制,消除autovacuum扫盘需求 低膨胀率:原位更新技术解决空间膨胀问题,不依赖垃圾回收 易运维:无需手动vacuum,简化autovacuum配置
Notes
XStore是OpenTeleDB的核心创新之一,通过重新设计存储引擎架构,从根本上解决了PostgreSQL在高并发OLTP场景下的性能瓶颈。其核心思想是将MVCC的版本管理从事务ID转向Undo日志,实现了真正的原位更新,同时保持了ACID特性。