我花了一天时间,拆了一下 OpenTeleDB 的 XStore,到底解决了 PG 的哪根老筋?
这两年数据库圈有点像3年前的云原生圈:"分布式"、"新一代内核"、"重构存储引擎"这些词突然又密集起来了。 前几天刷群,看到有人转了 OpenTeleDB 的开源消息,说是"基于 PostgreSQL 的新一代内核"。说实话,我第一反应是:又一个魔改 PG? 但看到里面提到一个点:原位更新 + Undo 引擎(XStore),我还是没忍住下了源码。 因为这恰好戳中我这些年被 PG 折磨得最狠的痛点: 表膨胀、autovacuum 抽风、性能像心电图一样忽高忽低。 所以这次我没看 PPT,也没看宣传稿,直接跑到机器上拆了半天,想看看它究竟动了 PG 的哪根"老筋"。 我装的是 OpenTeleDB 的 17.6 内核版。 创建方式很直观: 这一步其实就已经很有意思了------它不是 fork 了一套新引擎,而是作为插件挂进去的。 这个思路我很认可: 像 Citus、openHalo 这些"成功插件化路线"的项目,本质都是这个思路。 在 这就是 XStore 的核心: 它不是靠多版本链来维护 MVCC,而是靠 Undo 日志回滚。 这点和 Oracle、MySQL InnoDB 的逻辑更像。 也正是它敢说"原位更新"的底气来源。 我用同样的参数,在同一台机器上跑了两组: 结果是: 写慢了将近一倍。这点我反而觉得真实:因为 XStore 在写数据页的同时,还要写一份 Undo。物理写入翻倍,吞吐下降是必然的。如果一个系统告诉你"原位更新 + Undo 还更快",那我反而会不太信。 我设计了一项创新实验:在 1000 万条级别的大数据量下,评估 XStore 与 Heap 表在高频更新下的空间膨胀、索引稳定性以及查询性能表现。该实验主要有两个创新点: 大规模数据模拟 多维度空间分析 表结构 初始化 1000 万条数据 先对现在存入1000w数据的空间监控与记录一下如下。 多轮全表更新 空间监控与记录 第一轮: 第五轮: 这组 1000 万级数据 + 多轮全表更新的实验,其实把 PG 传统 Heap 表的"老问题"放大得非常清楚。 最核心的对比结果只有一句话: XStore 的空间是线性的、可预测的;Heap 表的空间是失控的、不可预测的。 具体来看: a. Heap 表在第一次更新后,表体空间直接翻倍,从 789MB 飙到 1578MB。 b. 之后每一轮更新,虽然增长幅度趋缓,但空间再也回不到初始状态。 c. XStore 从头到尾不变: 985MB → 985MB → 985MB a. Heap 表索引从 214MB 膨胀到 428MB,且在后续更新中保持"高位横盘"。 b. XStore 的索引尺寸始终维持在 388MB 左右,没有明显漂移。 a. Heap:每一次 UPDATE,本质都是 DELETE + INSERT → 老版本残留 → 表膨胀 → 索引碎片 → autovacuum 压力。 b. XStore:真正的原位更新 → 历史版本进 Undo → 主表物理页不变 → 无膨胀。 a. 在 Heap 表上,如果你不 VACUUM,它一定会慢; 如果你 VACUUM,系统一定会抖。 b. 在 XStore 上,这两件事都不再是必选项。 这意味着什么? 它不是让你飞起来,而是让你不再塌方。 说实话,这几年我已经对"新一代数据库内核"这类说法有点免疫了。大多数项目,要么是在 PG 上糊一层分布式壳; 要么就是换个名字,重新卖一遍 MVCC。而 XStore 给我的感觉不一样。它没有试图掩盖代价。写入更慢, IO 更多,架构更复杂。 但它正面承认了一个事实: PostgreSQL 的 MVCC,在高频更新场景下已经接近物理极限。 这不是参数调优能解决的事,也不是加机器能扛住的事,而是存储模型本身的问题。这些年我见过太多系统:白天 QPS 很稳,半夜 autovacuum 开始清垃圾,延迟突然拉长,业务报警,DBA 开始手工 VACUUM / REINDEX / CLUSTER,第二天继续循环。 这不是运维水平的问题,而是模型在和现实硬扛。XStore 让我第一次意识到:原来 PG 也可以选择不走这条老路。它没有追求"更快",而是选择了一个更难、但更稳的方向: 如果你是写多、更新密集型 OLTP 系统,如果你被表膨胀、索引碎片、autovacuum 抽风折磨过,那你会和我一样---不一定立刻用它,但你会开始认真看它。这大概就是我这次拆源码、跑实验,最大的收获。一、先说结论:XStore 不是快,而是"稳"

SELECT relname, amname
FROM pg_class c
JOIN pg_am a ON c.relam = a.oid
WHERE relname = 'test_xstore';

二、打开数据目录,我第一次意识到:它真不是换皮
$PGDATA 下面,多了一个非常显眼的目录:drwx------ 2 postgres postgres 4096 Nov 3 20:15 undo三、插入测试:它不快,但很"诚实"
INSERT INTO test_xstore (name, value)
SELECT md5(random()::text), (random()*1000)::int
FROM generate_series(1,10000000);INSERT INTO test_heap (name, value)
SELECT md5(random()::text), (random()*1000)::int
FROM generate_series(1,10000000);
四、创新实验:模拟1千万数据的存储膨胀对比
generate_series(1,10000000) 生成 1000 万条数据,保证数据量级对存储膨胀影响明显。id、name、value 和 updated_at 四列,与前期实验一致,但数据量增加十倍,以模拟真实大规模 OLTP 系统负载。pg_relation_size、pg_total_relation_size 和 pg_indexes_size 获取精细化指标。
4.1 实验设计
CREATE TABLE xstore_large (
id SERIAL PRIMARY KEY,
name TEXT,
value INT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) USING XSTORE;
CREATE TABLE heap_large (
id SERIAL PRIMARY KEY,
name TEXT,
value INT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);INSERT INTO xstore_large (name, value)
SELECT 'name_' || g, g
FROM generate_series(1, 10000000) AS g;
INSERT INTO heap_large (name, value)
SELECT 'name_' || g, g
FROM generate_series(1, 10000000) AS g;SELECT
pg_size_pretty(pg_total_relation_size('xstore_large')) AS xstore_total,
pg_size_pretty(pg_indexes_size('xstore_large')) AS xstore_index,
pg_size_pretty(pg_total_relation_size('heap_large')) AS heap_total,
pg_size_pretty(pg_indexes_size('heap_large')) AS heap_index; xstore_total | xstore_index | heap_total | heap_index
--------------+--------------+------------+------------
985 MB | 388 MB | 789 MB | 214 MBvalue 和 updated_at,模拟写入密集场景:UPDATE xstore_large
SET value = value + 1, updated_at = CURRENT_TIMESTAMP;
UPDATE heap_large
SET value = value + 1, updated_at = CURRENT_TIMESTAMP;
SELECT
pg_size_pretty(pg_total_relation_size('xstore_large')) AS xstore_total,
pg_size_pretty(pg_indexes_size('xstore_large')) AS xstore_index,
pg_size_pretty(pg_total_relation_size('heap_large')) AS heap_total,
pg_size_pretty(pg_indexes_size('heap_large')) AS heap_index;
xstore_total | xstore_index | heap_total | heap_index
--------------+--------------+------------+------------
985 MB | 388 MB | 1578 MB | 428 MB
xstore_total | xstore_index | heap_total | heap_index
--------------+--------------+------------+------------
985 MB | 388 MB | 1628 MB | 428 MB4.2 千万数据更新膨胀可视化


五、实验结论
六、我的心得