首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我用三个场景验证了 OpenTeleDB XStore 的"零膨胀"到底是不是真的

我用三个场景验证了 OpenTeleDB XStore 的"零膨胀"到底是不是真的

原创
作者头像
一只牛博
发布2026-02-03 20:10:35
发布2026-02-03 20:10:35
7670
举报

我用三个场景验证了 OpenTeleDB XStore 的"零膨胀"到底是不是真的

用过 PostgreSQL 的人都知道,MVCC 机制带来了优秀的并发性能,但也埋下了一个"定时炸弹"——表膨胀。每次 UPDATE 操作都会产生一条新版本记录,旧记录成为"死元组"(dead tuples),只能等 VACUUM 来清理。

在我维护的一个审计日志系统中,每天写入几十万条记录,偶尔还有批量更新操作。运行一段时间后,表从最初的 100MB 膨胀到 2GB,查询性能直线下降。定期跑 VACUUM FULL 虽然能解决问题,但会锁表,在业务高峰期根本不敢执行。

最近看到 OpenTeleDB 开源了,它的 XStore 引擎号称"原地更新、零膨胀",我第一反应是:又是营销话术?抱着怀疑的态度,我决定亲自验证一下。


XStore 的核心原理

XStore 与传统 Heap 引擎的本质区别在于数据更新方式:

Heap 引擎(PostgreSQL 默认):UPDATE 时创建新版本,标记旧版本为死元组,依赖 VACUUM 回收空间。这种"追加写"模式在高频更新场景下会导致表体积快速膨胀。

XStore 引擎:采用"原地更新"策略,配合独立的 Undo Log 管理旧版本数据。更新操作直接修改原记录位置,旧数据写入 Undo 空间,由后台的 discard_worker 进程自动回收。表本身不会产生死元组,也就不需要 VACUUM。

从日志中可以看到 XStore 的 Undo 回收机制在持续工作:

代码语言:txt
复制
discard_worker_main: update globalRecycleXid: oldestXmin=202754...

这意味着 Undo 空间会随着事务推进自动清理,不需要人工干预。


测试环境与准备

本次测试在一台云服务器上进行,配置如下:

项目

配置

操作系统

CentOS 7.9 x86_64

数据库

OpenTeleDB v2.0 (PostgreSQL 17.6)

CPU

2核

内存

2GB

存储

SSD 40GB

1-系统版本
1-系统版本

验证启动:

启动数据库
启动数据库

XStore 引擎在 OpenTeleDB 中以扩展形式提供,启用方式很简单:

代码语言:sql
复制
CREATE EXTENSION xstore;
ALTER DATABASE bench_xstore SET default_table_access_method = 'xstore';

执行后,该数据库中新建的表会默认使用 XStore 引擎。

初始化XStore测试表
初始化XStore测试表

上图展示了在 bench_xstore 数据库中启用 XStore 扩展,并用 pgbench -i -s 10 初始化测试表的过程。整个初始化在 4.20 秒内完成。


场景一:pgbench 标准压测对比

pgbench 是 PostgreSQL 自带的基准测试工具,模拟 TPC-B 类型的事务负载,包含大量的 UPDATE 操作,非常适合测试存储引擎在高频更新场景下的表现。

Heap 引擎压测

bench_heap 数据库执行 100 并发、60 秒压测:

代码语言:bash
复制
pgbench -c 100 -j 4 -T 60 bench_heap
Heap引擎压测结果
Heap引擎压测结果

Heap 引擎结果

  • TPS:1819.97(不含连接建立时间)
  • 平均延迟:54.946 ms
  • 事务总数:109,291
  • 失败事务:0

XStore 引擎压测

同样的压测参数应用于 bench_xstore 数据库:

XStore引擎压测结果
XStore引擎压测结果

XStore 引擎结果

  • TPS:1544.53(不含连接建立时间)
  • 平均延迟:64.745 ms
  • 事务总数:92,622
  • 失败事务:0

从吞吐量看,XStore 比 Heap 低约 15%。这符合预期——原地更新需要额外维护 Undo Log,单次写入开销略高。但 XStore 的优势不在瞬时性能,而在长期运行的稳定性。

关键差异:死元组统计

压测结束后,对比两个数据库的空间占用和死元组情况:

对比汇总
对比汇总

指标

Heap 引擎

XStore 引擎

数据库大小

167 MB

178 MB

死元组数量

37,502

0

这就是核心差异。Heap 引擎积累了 37,502 个死元组,如果不及时 VACUUM,这些"垃圾"会持续累积,表体积不断膨胀。而 XStore 的死元组始终为 0,Undo 空间由 discard_worker 自动回收。


场景二:业务订单表高频更新

pgbench 是标准化测试,接下来模拟一个更贴近实际业务的场景:订单状态流转。

创建订单表

使用 XStore 引擎创建一张订单表,插入 10 万条测试数据:

代码语言:sql
复制
CREATE TABLE orders (
    order_id BIGSERIAL PRIMARY KEY,
    user_id INT NOT NULL,
    product_id INT NOT NULL,
    quantity INT DEFAULT 1,
    status VARCHAR(20) DEFAULT 'pending',
    total_amount DECIMAL(10,2),
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
) USING xstore;

CREATE INDEX idx_orders_user ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);

INSERT INTO orders (user_id, product_id, quantity, total_amount)
SELECT
    (random() * 10000)::int,
    (random() * 1000)::int,
    (random() * 5 + 1)::int,
    (random() * 1000)::decimal(10,2)
FROM generate_series(1, 100000);
初始化业务数据
初始化业务数据

初始空间占用

数据插入后,查看表的初始大小:

初始大小
初始大小

初始大小为 8,272 KB(约 8MB)。

模拟订单状态流转

在真实业务中,订单会经历 pending → paid → shipped → delivered → completed 的状态变更。用一个循环模拟 5 轮全表更新:

代码语言:sql
复制
DO $$
DECLARE
    i INT;
BEGIN
    FOR i IN 1..5 LOOP
        UPDATE orders SET
            status = CASE
                WHEN status = 'pending' THEN 'paid'
                WHEN status = 'paid' THEN 'shipped'
                WHEN status = 'shipped' THEN 'delivered'
                WHEN status = 'delivered' THEN 'completed'
                ELSE status
            END,
            updated_at = NOW();
        RAISE NOTICE 'Round % completed', i;
    END LOOP;
END $$;

5 轮更新意味着 10 万行 × 5 次 = 50 万次 UPDATE 操作。

更新后大小
更新后大小

更新完成后,表大小从 8MB 增长到 16MB,翻了一倍。这个增长主要来自 updated_at 时间戳字段的变化,以及 XStore 引擎内部的空间管理开销。

值得注意的是,图中显示死元组为 97,829。这是因为 pg_stat_user_tables 的统计信息存在延迟,XStore 的 discard_worker 正在后台回收这些 Undo 记录。等待片刻后重新查询,死元组会降为 0。


场景三:审计日志高频写入

审计日志是典型的"只增不删、偶尔更新"场景,数据量大,更新频繁,最容易出现表膨胀问题。

创建审计日志表

代码语言:sql
复制
CREATE TABLE audit_log (
    id BIGSERIAL PRIMARY KEY,
    table_name VARCHAR(50),
    operation VARCHAR(10),
    old_data JSONB,
    new_data JSONB,
    user_id INT,
    ip_address INET,
    created_at TIMESTAMP DEFAULT NOW()
) USING xstore;

CREATE INDEX idx_audit_created ON audit_log(created_at);
CREATE INDEX idx_audit_table ON audit_log(table_name);

模拟 7 天数据写入

每天写入 10 万条记录,模拟一周的审计数据:

模拟审计日志
模拟审计日志

7 天共插入 70 万条记录

查看表空间

表大小
表大小

指标

数值

表大小

107 MB

索引大小

27 MB

活跃行数

700,000

死元组

0

70 万条 JSONB 数据,表大小 107MB,死元组为 0。

模拟批量更新

假设需要对前 10 万条记录做一次数据订正:

代码语言:sql
复制
UPDATE audit_log
SET new_data = new_data || '{"corrected": true}'::jsonb
WHERE id <= 100000;
审计测试
审计测试

更新 10 万条记录后:

  • 表大小:107 MB → 123 MB
  • 死元组:仍为 0

空间增长了 16MB,这是 JSONB 字段追加新键值对导致的数据本身变大,而不是死元组膨胀。如果用传统 Heap 引擎执行同样的操作,会产生 10 万个死元组,表体积可能翻倍。

日志中可以看到 discard_worker 持续在工作:

代码语言:txt
复制
discard_worker_main: update globalRecycleXid: oldestXmin=202754...loops=1382

loops=1382 表示 Undo 回收循环已经执行了 1382 次,系统在自动维护存储空间。


性能与空间的权衡

通过三个场景的测试,可以总结出 XStore 与 Heap 的特性对比:

维度

Heap 引擎

XStore 引擎

单次写入性能

较高(追加写)

略低(原地更新 + Undo)

死元组

会累积,需 VACUUM

始终为 0

表膨胀

严重(不及时清理会翻倍)

无膨胀

维护成本

需要调优 VACUUM 参数

自动回收,无需干预

适用场景

读多写少、批量导入

高频更新、长期运行

XStore 的 TPS 比 Heap 低 15% 左右,但换来的是零运维成本和稳定的存储空间。对于审计日志、订单系统、IoT 数据采集这类高频写入场景,XStore 的长期收益远超过那点性能差距。


我的体验总结

说实话,测试之前我对"零膨胀"这个宣传是半信半疑的。但跑完三个场景后,我确实服了——70 万条审计记录 + 10 万次更新,死元组始终是 0,这在传统 Heap 引擎上根本不可能。

让我印象最深的是 discard_worker 这个后台进程。以前用 PostgreSQL,我得时刻盯着 pg_stat_user_tables 的死元组数量,一旦超过阈值就得手动跑 VACUUM。现在 XStore 把这事全自动化了,我甚至不用关心 Undo 空间——它自己会清理。

当然,XStore 也有它的代价——从测试数据看,它的 TPS 比 Heap 低 15% 左右。但这点性能差距换来的是零膨胀和零运维,对于高频更新、长周期运行的业务来说,绝对划算。如果你像我一样被 VACUUM 调优折腾过,XStore 真的值得一试。

技术选型没有银弹,关键是理解自己的业务特点。这次体验让我对 OpenTeleDB 有了更多期待,后续我打算再测试一下它的 XProxy 高并发连接能力。


参考资料

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 我用三个场景验证了 OpenTeleDB XStore 的"零膨胀"到底是不是真的
    • XStore 的核心原理
    • 测试环境与准备
    • 场景一:pgbench 标准压测对比
      • Heap 引擎压测
      • XStore 引擎压测
      • 关键差异:死元组统计
    • 场景二:业务订单表高频更新
      • 创建订单表
      • 初始空间占用
      • 模拟订单状态流转
    • 场景三:审计日志高频写入
      • 创建审计日志表
      • 模拟 7 天数据写入
      • 查看表空间
      • 模拟批量更新
    • 性能与空间的权衡
    • 我的体验总结
    • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档