告别分片地狱,金仓时序数据库的超表架构如何让开发者只管写SQL
聊时序数据库之前,先说个真事。
去年有个轨交项目,调度中心的监控大屏突然卡了,晚上12点。值班工程师查日志,写入队列堆了两百多万条没落盘的数据,查询从200毫秒窜到40秒。但你说硬件有问题吧,CPU才到30%,内存还剩一大截。最后定位到是某个时间分片数据量膨胀,写入路由表挂了,所有新数据挤在一个物理分区里抢锁。
我这些年经手时序库选型的活儿不算少,晚上12点被电话叫醒的故事每个项目都能讲出不同版本。轨交也好,工厂也好,电力也好,时序数据库真正的麻烦从来不是"能不能写进去"——刚上线那会儿啥都好好的。麻烦在跑了几个月、数据量上来之后,各种你当初根本没想到的配置坑全蹦出来了。
金仓时序数据库搞了个叫"超表"(Hypertable)的东西,思路跟传统方案不太一样。这篇文章就写写我理解的这套架构,它怎么把分片管理这个运维老大难问题从根上拆掉。
传统分片到底怎么把人逼疯的
我自己踩过最深的坑是分片预估。
大多数时序库的分片策略,说白了就是"手动配+静态规则"。部署阶段你得提前定好分片键、分几个片、每个片多大触发分裂。拿设备ID做Hash分片来说,你得猜最终会接多少台设备——猜少了,后面数据倾斜,有的片撑爆有的片空着;猜多了,每片数据量太小,查询时跨片合并的开销比单片扫描还高。
时间维度的坑更深。时序数据天然有冷热之分,最近一小时的数据被反复查,三个月前的数据也就月报才翻一次。传统分片不做冷热分离的话,所有数据挤一层存储,热点分片的I/O被读写两头抢。做分离吧,归档策略、迁移任务、索引重建脚本全得手写,运维每周加班写Shell。
有个能源监控的项目我印象特别深。跑了八个月之后,运维团队每个月手动做一次分片重平衡。怎么操作呢?数据导出来,删旧分片,按新规则重新灌进去。四到六小时,期间系统降级跑。运维那哥们跟我原话是这么说的:“我面试进来是做DBA的,不是来做ETL搬运工的。”
还有个更阴的坑——元数据膨胀。每个分片自己有索引、统计信息、路由表。分片从几十涨到几百之后,查询规划器光遍历元数据就得几十毫秒。亚秒级监控场景下,这个开销谁受得了。
超表干的第一件事:把物理分片藏起来
超表这名字听着唬人,其实道理不复杂。你应用层看到的就是一张普普通通的逻辑表,INSERT、SELECT、GROUP BY,该咋写咋写。底下数据怎么切、存哪台机器上,你不用知道,也不用管。
我经常拿公司前台打比方。传统分片就像你去找人,得先翻一张"员工-楼层-工位"映射表,查到在几楼几座,自己跑过去。映射表过期了或者写错了,你就跑空。超表呢,你只管报名字,前台自己帮你定位,映射表怎么维护是前台的事,你感觉不到。
具体到技术实现上,有几层设计我挑重点说。
写入路由是自动的。每条数据进来,系统根据时间戳加上可选的空间标签(设备ID、区域编号之类),算出它该落在哪个Chunk里。你写INSERT的时候不指定目标分片,甚至不需要知道有分片这东西存在:
-- 就这么写,不用管数据落到哪个物理分片
INSERT INTO device_metrics (ts, device_id, temperature, pressure, vibration)
VALUES ('2026-08-15 14:30:00', 'dev_00125', 78.5, 1.2, 0.03);
查询的时候有分区裁剪。你带时间范围条件查,系统自己判断哪些Chunk跟这次查有关系,无关的直接跳过。查"最近一小时"的数据,只扫当前小时的Chunk,三个月前的分片碰都不碰。这个过程对你完全透明,SQL跟查普通表没区别:
-- 查最近1小时平均温度,系统自动跳过无关分区
SELECT device_id, AVG(temperature) AS avg_temp
FROM device_metrics
WHERE ts >= NOW() - INTERVAL '1 hour'
GROUP BY device_id
ORDER BY avg_temp DESC;
分片的生命周期也是系统管。创建新分片、归档旧分片、删过期分片,全按策略自动跑。你配个"保留180天",到点自己删,不用写定时脚本,也不用晚上爬起来敲DROP PARTITION。
有个事可以佐证这套设计到底省了多少事。我经手过一个五人团队从某传统时序库迁到金仓超表的项目,迁完之后跟分片相关的代码和配置文件删了七成以上。之前那些手动路由逻辑、分片选择算法、跨片查询结果合并的代码——全砍了,不需要了。
二维分区:不是按时间切一刀就完事的
金仓的底层分片策略有个自研的"二维分区"算法,时间轴和空间轴两个维度同时做路由。这个设计我觉得是他们整个超表架构里最有技术含量的部分。
时间轴是主分区。数据按时间窗口(每小时、每天,看你配置)自动切成独立物理块。好处直接两条:新数据追加到最新分片,不跟历史数据抢锁;查询走分区裁剪,只扫相关时间段的数据块,I/O量掉一大截。
空间轴是次分区。拿设备ID或者区域编号做哈希,把高并发写入压力均匀打散到各节点。这个设计解决的是一个特别实际的问题——你3000台设备同时上报,如果只按时间分区,某一秒所有设备的数据全涌向同一个时间分片,写入吞吐被单分片的处理能力卡死了。加上空间哈希分区之后,同一时刻不同设备的数据被路由到不同物理节点并行写,整体吞吐跟节点数挂勾,线性扩展。
-- 时间维度按天分区,空间维度按device_id哈希
CREATE TABLE device_metrics (
ts TIMESTAMP NOT NULL,
device_id VARCHAR(50) NOT NULL,
temperature DOUBLE PRECISION,
pressure DOUBLE PRECISION,
vibration DOUBLE PRECISION,
region VARCHAR(20),
PRIMARY KEY (device_id, ts)
) PARTITION BY RANGE (ts);
-- 时间分片内部再做哈希子分区
CREATE TABLE device_metrics_20260815
PARTITION OF device_metrics
FOR VALUES FROM ('2026-08-15 00:00:00') TO ('2026-08-16 00:00:00')
PARTITION BY HASH (device_id);
CREATE TABLE device_metrics_20260815_h0
PARTITION OF device_metrics_20260815
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE device_metrics_20260815_h1
PARTITION OF device_metrics_20260815
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE device_metrics_20260815_h2
PARTITION OF device_metrics_20260815
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE device_metrics_20260815_h3
PARTITION OF device_metrics_20260815
FOR VALUES WITH (MODULUS 4, REMAINDER 3);
这套二维分区在高基数场景下优势特别明显。所谓高基数就是设备多、标签多。一个城市级物联网项目,动辄上万台设备,每台几十个指标,一秒一报。光做时间分区的话,一个时间窗口里攒的数据量太大,单分片扛不住写入峰值。加了空间哈希之后,同一时间段的数据被均匀打散到N个物理分片上,每个分片只扛总写入量的1/N。
北京轨交TCC项目是个很硬的数据 $性能提升超十倍!金仓时序数据库首入北京轨交TCC。接入金仓时序数据库后,写入性能相比旧系统提升超过10倍,每秒处理数十万条数据点的洪峰写入,存储空间占用降了70%到80%。这背后时间维度控制了每个数据块的规模,空间维度把写入压力分摊到了多个节点上,缺了哪个维度都撑不到这个数。
标准SQL干时序的活:不用学新语法这件事很重要
有些专用时序库为了追性能,把SQL给砍了,搞了套自己的查询语言。你说这有多大影响?在技术评审会上可能听着无所谓,真到项目落地的时候,开发团队要重新培训、重写数据访问层、调试工具全换一套,DBA也得重新学运维体系。这种隐性成本,选型阶段九成的人估不出来。
金仓的时序能力是长在KingbaseES内核里的,不是外挂服务。标准SQL写时序数据,写入、查询、聚合、关联,全都跟写普通表一样。
-- 时序表和关系表直接JOIN,查温度异常设备并关联档案信息
SELECT d.device_name, d.location, d.maintainer,
m.ts, m.temperature, m.pressure
FROM device_metrics m
JOIN device_archive d ON m.device_id = d.device_id
WHERE m.ts >= '2026-08-15 00:00:00'
AND m.ts < '2026-08-15 01:00:00'
AND m.temperature > d.alert_threshold
ORDER BY m.temperature DESC;
这条SQL同时碰了时序表和关系表。放纯时序库里你干不了这个,得先查时序库拿异常设备列表,再查关系库拿设备档案,中间加应用代码拼接。金仓这边一条SQL搞定。
工业场景里降采样查询太常用了,每秒一条的原始数据聚合成每分钟的平均值:
-- 按分钟降采样
SELECT
time_bucket('1 minute', ts) AS minute_bucket,
device_id,
AVG(temperature) AS avg_temp,
MAX(pressure) AS max_pressure,
STDDEV(vibration) AS vib_std
FROM device_metrics
WHERE ts >= '2026-08-15 00:00:00'
AND ts < '2026-08-16 00:00:00'
GROUP BY minute_bucket, device_id
ORDER BY minute_bucket, device_id;
time_bucket是金仓内置的时间桶聚合函数。底下执行的时候,排序和聚合算子被推到各数据节点本地算,先局部聚合再汇总,尽量少搬数据 。原来分钟级的跨节点分析,能压到亚秒。
排查查询性能的时候,EXPLAIN直接看分区裁剪有没有生效:
EXPLAIN (ANALYZE, BUFFERS)
SELECT device_id, AVG(temperature)
FROM device_metrics
WHERE ts >= '2026-08-15 00:00:00'
AND ts < '2026-08-15 01:00:00'
GROUP BY device_id;
执行计划里只出现20260815相关的子分区,说明裁剪生效了。要是看到所有分片都在扫,查两件事:查询条件有没有带分区键(时间列),统计信息是不是过期了跑一下ANALYZE。
几个真实项目里的数据
架构到底行不行,不看你PPT做得多漂亮,看落地。
北京轨交TCC这个项目我追了大半年。应急指挥调度系统每天接入的数据量很吓人:列车实时位置、速度、牵引能耗、信号设备状态、车站客流密度、环境温湿度,每秒数十万条时序数据点往里灌。旧系统用传统关系型数据库硬扛了几年,到后来每早晚高峰写入队列就堆,监控大屏延迟从亚秒劣化到十几秒,运营分析报表跑一次等好几个小时。
换了金仓之后,TCC技术团队上了一主多从的读写分离集群。主节点专门写,从节点扛监控大屏和后台分析查询。写入和查询在物理上隔离开了——写的不被查的拖累,查的不被写的干扰。写入性能比旧系统高超10倍,存储省了70%到80%。过去花几分钟才能跑出来的"某线路过去一个月早高峰平均旅行速度分析",现在秒级出结果。
数据生命周期管理也做了分层:热数据放高速存储服务实时告警,温数据放普通磁盘服务历史分析,冷数据自动归档。目前系统稳定存着TB级轨交全网时序历史数据,百TB级可扩展存储体系已经搭好了。
泰兴交通走了另一条路,做的是时空一体化。金仓用时序引擎加地理空间引擎双擎驱动,把交通数据的时间维度和空间维度放到底层一起处理。城市交通是个时刻变的网络,一个路口堵了,周边几条路跟着慢。传统方案你得先查时序库拿流量变化,再查GIS库拿路口空间关系,然后在应用层自己拼。金仓这边时序数据和空间数据在一个内核里直接关联,省了数据同步和跨系统拼接。据报道,通行效率提了20%以上。
还有个机场的项目。金仓时序数据库帮机场从"被动抢修"转到"预判"模式,靠对设备运行数据的时序分析和趋势预测,在故障真发生之前就触发预警。这种预测性维护的底子,就是超表对海量历史设备数据的快速检索和聚合能力。
运维侧:能自动的就别让人来
开发者看到的是一张逻辑表,运维要管的是底下分片怎么跑。金仓在运维侧的思路我总结一下就是一句话:能自动的绝不让人手动来。
分片创建这件事,传统方案要DBA提前把未来几个月的分区手动建好,或者写个定时脚本批量生成。脚本出了bug或者漏了某段时间的分区,数据写入直接报错。超表这边,系统根据你配的时间分区策略自己建新分片。当前分片快满了,下一个自动跟上,不需要人插手。
数据归档和清理也一样。保留多久、什么时候归档、归到哪层存储、什么时候彻底删,传统方案全靠运维脚本和手动操作。金仓超表支持配自动化规则:
-- 保留180天,更早的自动清理
SELECT add_retention_policy('device_metrics', INTERVAL '180 days');
-- 30天以上的数据自动迁到低成本表空间
SELECT add_storage_policy('device_metrics',
INTERVAL '30 days',
tablespace => 'ts_cold_storage');
配完了系统到点自己执行迁移和清理。需要手动干预的时候(比如临时保留某段数据做故障分析),暂停一下特定分片的自动清理,事后再恢复就行。
写入性能监控也集成了。运维可以通过系统视图看各分片的写入量、数据量、压缩率:
-- 看各时间分片的存储大小
SELECT
relname AS chunk_name,
pg_size_pretty(pg_relation_size(oid)) AS size
FROM pg_class
WHERE relname LIKE 'device_metrics_2026%'
ORDER BY pg_relation_size(oid) DESC
LIMIT 10;
要是发现某个哈希子分片的数据量是其他的好几倍,说明空间维度有倾斜,可能得调哈希分区数量或者换个哈希键。不过实际上设备ID的哈希分布一般比较均匀,这种倾斜不常见。
底层存储:列式压缩和批量写入
超表解决的是分片管理问题。但写入快不快、存储省不省钱,还得看底层存储引擎。
列式存储这块,时序数据本质是同一指标在不同时间点的连续数值,列式把同一列的数据放一块,用RLE、字典编码、Delta-of-Delta这些压缩算法,压缩比比传统行存储高不少 $时序数据处理组件:金仓数据库如何高效管理时间序列。
写入路径的设计是数据先进内存时序缓冲区,攒够了阈值再批量刷盘。随机I/O变成顺序写,写入效率拉满。应用层配合批量写入效果更好,每秒的传感器数据打包成批次走JDBC批量插入:
// Java批量写入
String sql = "INSERT INTO device_metrics (ts, device_id, temperature, pressure, vibration) "
+ "VALUES (?, ?, ?, ?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
conn.setAutoCommit(false);
for (SensorReading reading : batch) {
ps.setTimestamp(1, Timestamp.from(reading.getTs()));
ps.setString(2, reading.getDeviceId());
ps.setDouble(3, reading.getTemperature());
ps.setDouble(4, reading.getPressure());
ps.setDouble(5, reading.getVibration());
ps.addBatch();
}
ps.executeBatch();
conn.commit();
}
批量大小有个讲究。太小了看不出效果,太大了吃内存而且失败了回滚代价高。我自己的习惯是从1000条起步压测,逐步加大到写入延迟开始往上走的拐点,取拐点前面一档当生产配置。
时序数据不该是座孤岛
最后说个容易被忽略的点。金仓的时序能力是构建在融合数据库架构里的原生能力,不是独立外挂的模块。
这事说起来抽象,放到具体场景里就好理解了。你做设备预测性维护,要喂给模型的数据包括:设备历史运行参数(时序)、设备型号和投运日期(关系)、设备安装位置和周边环境(空间)、领域专家总结的故障特征(向量)。这些数据如果散在不同系统里,数据工程师光做对齐和搭管道就得花掉项目六成以上的时间。金仓的融合架构让这些数据在一个内核里直接可用,一条SQL就能跨时序、关系、空间、向量做关联查询,AI团队能把精力挪到模型迭代上 $AI落地卡壳?数据散成渣?金仓时序数据库多模融合能力一键打通。
回到晚上12点
开头老张那个故事,如果当时系统跑的是超表架构,"某个时间分片数据量膨胀导致写入路由失效"这个问题压根不会出现。超表的分片创建是自动的,数据量涨了系统自己切新分片,不存在一个分片被撑爆的情景。运维不用手动维护路由表,不用每月做重平衡,不用写归档脚本。部署阶段配好分区策略和保留周期,剩下的交给系统自己跑。
超表架构真正有价值的地方,不是哪个单点技术多花哨,而是它把分片管理这件事从运维的负担变成了系统内置的能力。开发者对着一张逻辑表写标准SQL,运维对着自动化策略做配置,底层物理分片的创建、路由、裁剪、归档全由内核处理。关系型数据库的易用性保住了,分布式数据库的水平扩展能力也拿到了。
做时序数据库选型的同行,别光盯着"每秒能写多少行"这个数。更该问的是:跑了几个月之后还能不能稳定写?历史数据越堆越多查询还快不快?设备不断接入架构能不能平滑扩?金仓超表在北京轨交TCC、泰兴交通、机场预测性维护这些项目里已经跑出了答案 。剩下的,看你自己的场景对不对得上。
更多推荐



所有评论(0)