找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1388

积分

0

好友

181

主题
发表于 昨天 23:53 | 查看: 1| 回复: 0

天翼云OpenTeleDB的XStore引擎:原位更新如何解决PostgreSQL MVCC痛点

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

原位更新机制

传统MVCC问题

众所周知,PostgreSQL的多版本并发控制机制在每次更新时都会在数据页中产生一个新版本,旧版本则会积压下来。这带来了几个明显的痛点:

  • 存储空间膨胀:旧数据无法被立即回收。
  • 性能波动:需要周期性的VACUUM清理,性能影响可达40%以上。
  • 运维复杂:需要复杂的autovacuum配置和维护工作。

XStore解决方案

针对上述问题,XStore引入了原位更新技术。其核心思想是:更新数据时,直接在原位置修改,同时将旧数据写入独立的Undo日志中。这巧妙地将空间膨胀转移到了更易管理的Undo子系统。

每次数据更新时:

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

例如,XStore的行格式包含了关键的事务信息字段:

// XStore行格式定义
static const int ISNULL_ATTRS_PER_BYTE = 8;

核心组件架构

1. 访问方法注册

XStore作为插件集成到PostgreSQL中,通过扩展注册新的表和索引访问方法:

CREATE ACCESS METHOD xstore TYPE TABLE HANDLER xheap_tableam_handler;
CREATE ACCESS METHOD xbtree TYPE INDEX HANDLER xbthandler;

2. 表访问方法实现

XStore实现了完整的表访问方法接口,覆盖了增删改查等核心操作:

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

3. Undo系统

Undo系统是XStore实现原位更新的基石,它负责记录旧数据版本。系统维护了一个完整的Undo上下文结构:

typedef struct UndoLogContext
{
    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操作,确保数据一致性。

void xstore_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位置的指针。例如,XHeapTupleTransInfo结构中的urec_add字段就存储了指向该记录最新UNDO记录的指针。

// XHeapTupleTransInfo结构中的urec_add字段
typedef struct XHeapTupleTransInfo
{
    FullTransactionId xid;
    CommandId cid;
    UndoRecPtr urec_add; // 指向UNDO记录的指针
} XHeapTupleTransInfo;

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

为什么要遍历UNDO链?因为XStore的MVCC机制要求找到对当前快照可见的版本。在get_tuple_from_undo函数中,通过一个循环不断回溯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的xmin/xmax判断版本是否可见。
  • 命令ID检查:对于同一事务内的不同命令,需要检查CID的可见性。

只有当所有检查都通过时,才能确定该版本是可见的,并终止遍历。这里说的“不能提前终止”指的是不能在完成必要检查之前就终止。具体而言,如果 selector == XVERSION_CHECK_CID,则必须获取CID并进行最终判断;只有在所有检查完成且确认可见后,才能终止。这种设计体现了“完整性优先”的原则,在保证MVCC正确性的同时兼顾了性能效率。

4. 性能优化机制

虽然遍历UNDO链可能带来开销,但XStore实现了多种优化来确保性能。

4.1 全局回收XID优化

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

4.2 快照类型优化

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

4.3 缓存机制

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

5. 性能影响分析

在实际应用中,遍历UNDO链带来的性能影响是可控的:

  1. 遍历深度有限:在大多数业务场景下,一个记录不会有无穷的版本,通常只需要遍历少数几个版本就能找到可见版本。
  2. 全局回收机制:旧版本会被后台进程定期清理,防止UNDO链无限增长。
  3. 内存访问局部性:UNDO记录通常在内存中连续存储,访问效率较高。

即便遇到极端情况(如单个记录被大量更新),XStore也有应对机制,例如通过“snapshot too old”错误提示客户端重新查询。总的来说,XStore的UNDO链遍历是其设计的核心部分,通过优化,在实际高并发应用中能够提供比传统VACUUM机制更稳定、可预测的性能表现。

系统集成

钩子机制

XStore通过钩子(hook)机制深度集成到PostgreSQL内核中,用于管理全局事务ID等关键信息。

static FullTransactionId get_global_recycle_xid(void);
static void set_global_frozen_xid(FullTransactionId xid);
static void set_global_recycle_xid(FullTransactionId xid);

资源管理器注册

为了支持WAL日志和崩溃恢复,XStore注册了自定义的资源管理器来处理其特有的操作记录。

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表

用户可以通过两种方式创建使用XStore引擎的表。

-- 方式1:建表时显式指定存储引擎
CREATE TABLE xt1(a int primary key, b int) USING xstore;

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

创建XStore索引

XStore表的索引默认会使用xbtree,用户也可以显式指定。

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

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

技术优势

通过上述架构设计和实现机制,XStore带来了显著的技术优势:

  1. 高稳定性:原位更新和集中的回滚管理机制,消除了传统autovacuum扫盘带来的性能波动,使得系统在高负载下表现更稳定。
  2. 低膨胀率:数据页内不再堆积旧版本,空间膨胀问题得到根本性解决,存储利用率更高。
  3. 易于运维:无需手动执行VACUUM,也简化了复杂的autovacuum配置,降低了数据库的运维负担。

总结

作为OpenTeleDB的核心创新之一,XStore存储引擎通过重新设计存储引擎架构,从根源上应对了PostgreSQL在高并发OLTP场景下的性能瓶颈。其核心思想是将MVCC的版本管理从事务ID空间转向Undo日志,实现了真正的原位更新,同时完整保持了数据库的ACID特性。对于面临PostgreSQL VACUUM挑战的开发者而言,深入了解XStore的原理颇具价值。欢迎在云栈社区继续交流数据库内核与架构的更多技术细节。




上一篇:嵌入式驱动开发需要掌握哪些核心技能?现代岗位技能体系解析
下一篇:日期选择器设计困境:输入遮罩、无障碍访问与日期格式的三方博弈
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-16 21:14 , Processed in 0.232004 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表