一、金仓时序数据库超表架构,难在哪
先撂个结论:手工分表这套办法,毛病不在“分”上,在于分完之后,所有的活儿都得你自己干。
做过时序数据的人应该都懂。按月建表嘛,xxx_202501、xxx_202502,一路往下排。写入靠应用层拼表名做路由,查跨月的数据就 UNION ALL 一串。量小的时候是真没毛病,甚至可以说还挺优雅。可数据一上来,麻烦就开始一个接一个往外蹦。
最先绷不住的往往是分区规则。它散落在代码里啊,建表靠定时任务,路由逻辑埋在应用里头。新同事不知道有这套约定,改查询忘了带月份路由,一条 SQL 直接扫当月大表。这种事几乎每个团队都出过,出一次记一辈子。
然后是跨月查询。UNION ALL 完再聚合,你自己琢磨琢磨,各表上的索引不就白建了嘛。月份跨得越多越慢,没有例外。
冷热数据还混着放。三个月前的明细,除了出报表那天,谁碰它啊?可它偏偏和今天的热数据躺同一块盘上,磁盘告警三天两头来报到。删又不敢删,鬼知道哪张报表哪天要用。
到期删旧数据这事儿靠人惦记,忘了就堆着,堆着堆着就成了谁也不敢动的历史包袱。想扩容,就剩换更贵的服务器一条道,账单蹭蹭涨。
这些麻烦摊开来看,根子其实就一个问题:分区这件事,到底归谁管?手工分表时代它归应用管,所以桩桩件件都得人扛。
金仓的超表(Hypertable)治的就是这个。它把分区的活儿整个下沉到数据库里头去了。数据进来,按时间自动切成一个个数据块(Chunk),每块只管一段时间的;再叠个空间维度,比如按点位 ID 哈希,把高并发写入摊开。应用这边看到啥?从头到尾就一张monitor_point_data,INSERT 该咋写咋写。查半年的数据也是一条普普通通的 SQL,跟你当前时间区间没关系的块,数据库自己就不碰了。
两种模式摆一块儿对比下:
| 对比项 | 手工分表 | 金仓超表 |
|---|---|---|
| 分区谁管 | 应用建表+定时任务,规则散在代码里 | 数据库自己按时间(+空间)切 Chunk |
| 跨时段查询 | 多表 UNION ALL,索引废掉 | 一条普通 SQL,没关的块直接不碰 |
| 写入路由 | 应用按时间拼表名 | 不存在这个环节,直接插 |
| 老数据治理 | 手工 DROP,忘了就堆着 | 到期自动按块删 |
| 以后扩容 | 堆硬件,烧钱 | 可以往分布式超表走 |
| SQL 兼容 | - | 标准 SQL,BI 工具直连 |
靠 SQL 吃饭的团队,最后一行搞不好才是最香的。报表工具、BI 看板、备份脚本、监控探针、权限体系,全能接着用,不用为时序能力单独搭一条工具链。人就那么几个的小团队,这点比啥跑分都实在。
不过丑话说前头,超表只是把复杂度从应用层挪到了数据库层,它可没消失。块间隔、压缩、保留策略、连续聚合,这套新机制各有各的坑,坑跟坑之间还会互相影响。下面按落地的顺序一个一个说。
目录
- 一、金仓时序数据库超表架构,难在哪
- 二、建表
- 三、第一个坑:块间隔
- 四、压缩和保留
- 五、第二个坑:刷新窗口和保留策略会互相咬
- 六、几句经验
二、建表
先建张普通表,就你平时写的那种,一点特殊语法都没有:
CREATETABLEmonitor_point_data(timeTIMESTAMPTZNOTNULL,point_idINTEGERNOTNULL,metricTEXTNOTNULL,valueDOUBLEPRECISION,qualitySMALLINT);-- 转为超表:时间列做主分区,point_id 哈希做空间分区SELECTcreate_hypertable('monitor_point_data','time',partitioning_column=>'point_id',number_partitions=>8,chunk_time_interval=>INTERVAL'1 day');一个函数调用,完事儿。时间轴按chunk_time_interval自动滚新块,空间轴按点位 ID 哈希成 8 个分区。应用侧要做的改造,就是把原来拼表名那段代码删掉。
有个约束得提前讲,省得对着报错发半天呆:唯一索引必须带上分区列。想拿point_id这种单列做唯一约束,数据库直接给你弹回来,报错还老长一段。道理不复杂,每个 Chunk 各自建索引,数据库没法跨块替你保证唯一性,所以唯一索引里必须把时间列捎上。
三、第一个坑:块间隔
块间隔这个参数,直觉上特别容易犯一个错,觉得块切得越小查询越快,上来就设个 1 小时。
直说了吧,会翻车。块切太细,后台建新块的频率就飞起,写入高峰还会莫名其妙冒出锁等待。为啥?建新块要拿的锁,比往已有块里插数据拿的锁时间长。一堆事务挤在同一时刻抢着开新块,可不就互相顶死了嘛。
那到底设多大?手册里有参考值,按日写入量给的:每天写 2GB、内存 64GB 的机器,7 天一块正合适;一天写到 10GB,缩到 1 天一块。所以原则就一句话,先抄手册的作业,再按自己的量微调。直觉在这儿不值钱。
运行中想调也有接口,但藏着个语义坑:
-- 注意:只对之后新建的块生效,已经建好的块不动SELECTset_chunk_time_interval('monitor_point_data',INTERVAL'24 hours');-- 看看当前分区配置长啥样SELECTcolumn_name,num_partitions,time_intervalFROMtimescaledb_information.dimensionsWHEREhypertable_name='monitor_point_data';改间隔只管新块,旧块一个不碰。也就是说,块间隔设大了想改小,旧块是救不回来的。要么干等它自己滚出保留期,要么老老实实迁数据。这参数务必上线前定死。
四、压缩和保留
先问一句,一个月之前的明细,除了出报表那天,还有谁碰它?
没人碰。时序数据的访问模式就是这么有规律,最近的数据天天查,老数据出了报表就没人搭理了。压缩策略照着这个规律配就行,近几天原样搁着,更老的块自动压成列存。
ALTERTABLEmonitor_point_dataSET(timescaledb.compress,timescaledb.compress_segmentby='point_id',timescaledb.compress_orderby='time DESC');-- 超过 7 天的块自动压缩SELECTadd_compression_policy('monitor_point_data',compress_after=>INTERVAL'7 days');-- 原始明细保留 180 天,到期自动删块SELECTadd_retention_policy('monitor_point_data',drop_after=>INTERVAL'180 days');配置里两个参数说下作用。compress_segmentby是按哪列分组压缩,时序场景一般挑设备或者点位 ID。compress_orderby是组内按啥排,通常就填时间列。这俩选对了,压缩比和查询效率都跟着受益,实测压缩比 4:1 上下,跟官方口径基本对得上。
顺带提个不大但挺阴的细节:压缩块上没法直接加带默认值的列,要加得先解压。嫌解压麻烦也有变通,加个可空列,再 UPDATE 把值补上,就这么绕过去。
五、第二个坑:刷新窗口和保留策略会互相咬
报表聚合慢,靠连续聚合治。思路不玄乎,把小时级的聚合结果提前物化成一张特殊的超表,后台按策略增量刷新,查询直接拿现成的,不碰明细。
CREATEMATERIALIZEDVIEWpoint_data_hourlyWITH(timescaledb.continuous)ASSELECTpoint_id,time_bucket(INTERVAL'1 hour',time)ASbucket,avg(value)ASavg_val,max(value)ASmax_val,min(value)ASmin_valFROMmonitor_point_dataGROUPBYpoint_id,bucketWITHNODATA;SELECTadd_continuous_aggregate_policy('point_data_hourly',start_offset=>INTERVAL'3 days',end_offset=>INTERVAL'1 hour',schedule_interval=>INTERVAL'30 minutes');下面这个坑,我个人认为是整套方案里最容易翻车的,必须单独拎出来说。
很多人配这俩策略的时候是分开想的,保留策略拍一个数,刷新窗口拍另一个数,各管各的,看着多合理啊。但它们实际上会互相咬。你保留 30 天,刷新窗口也伸到 30 天前,会咋样?聚合刷新跑到那个时间段一看,源数据让保留策略给删了,那它就把物化好的结果也顺手删了。
注意,这整个过程没有报错。没告警,没异常日志,就是报表上的数字悄无声息地没了。直到哪天有人盯着一条空曲线问数据咋回事,你才知道坏了。这种静默失败排查起来最磨人。
正确姿势就一条,保留周期要远大于刷新窗口。照上面例子那样,保留 180 天、刷新窗口只伸到 3 天前,让刷新永远落在还有原始数据的时段里,这坑就踩不着。手册里对这个组合是有明确警告的,别问我为啥知道得这么清楚。
日常查趋势还是普通 SQL,配个时间桶函数,要多细有多细:
SELECTtime_bucket('5 minutes',time)ASfive_min,avg(value)ASavg_valFROMmonitor_point_dataWHEREpoint_id=1024ANDtime>=now()-INTERVAL'2 hours'GROUPBYfive_minORDERBYfive_min;六、几句经验
回头看,超表落地这事技术上真不难,难的是心态。把原来攥在自己手里那点“分表智慧”整个交还给数据库,交出去那几天是真没底,老琢磨它真能替我管好?
几条经验搁这儿。
块间隔先抄作业再微调,按日写入量对照手册来。这参数改小只对新块生效,设大了想缩,没有回头路。
唯一索引必须带分区列,单列唯一约束直接报错,把时间列加上就好。
保留策略和刷新窗口必须一起设计。这条得再说一遍,因为这俩配岔了连报错都没有,数据就这么没了。
压缩块上别直接加带默认值的列,先解压,或者可空列加 UPDATE 绕过去。
还有个容易忽略的:超表的价值不止“快”那一下。应用代码干净了,团队里没人再需要搞懂那套分表路由的约定。这种工程上的松快,时间拉长了看,比查询快几秒值钱。
往后单机写入真到了天花板,还有分布式超表这条路,写入再横向摊一层,架构不用推倒重来。先写到这,等真跑起来了再补后话。