关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

监控点每 10 秒采一次,半年后先撑爆的是磁盘:时序数据的压缩、降采样与留存该怎么排

发布时间:2026-10-10

磁盘告警那天,值班同学开口第一句基本都是"加块盘"

项目上线那天没人把数据量当回事。3000 个采集点,每 10 秒上报一条,一条记录撑死几十字节,评审会上算下来"一天两千多万条",比同机房的业务日志还少。半年后监控告警跳出来:磁盘水位 82%,查最近七天的曲线要点十几秒才出来。解决办法是加盘,三个月后水位又回到 80%。这事儿的循环规律非常稳定。

问题不在盘上。同样这批数据,一套好的生命周期设计能把三年的占用压到几十 GB,一套稀烂的 schema 加全精度留存能吃到 TB 级差了两个数量级。差异不在数据库选型,在几件很具体的事:写入时点是怎么落地成块的、块的压缩比被什么样的数据特征决定、标签(tag)里塞了多少不该塞的东西、以及允不允许对老数据降精度。

这篇不做厂商横评。选型是次要变量,下面这几件事换个引擎照样成立:

  • 容量是可以手算的,不用等它涨:每天点数 = 采集点数 × 86400 ÷ 采样间隔,这个式子加上"单条字节数"和"压缩比",五分钟能算出半年后的磁盘占用。
  • 磁盘没满但查询很慢,通常是块数量和索引先出问题,不是容量。这两个指标要分开盯。
  • 压缩比不是数据库给的常数,是被数据特征定的:采样越规律、值越平滑,压缩比越高;乱序、抖动、高精度浮点会把它打回原形。
  • 标签基数是唯一的真放大器:一行的 schema 决策能让实际的序列数翻几十倍,连带索引、内存、块数量一起涨。
  • 留存不该是一个天数,而是三套精度配三套天数,而且降完精度之后,原始数据的删除动作必须有人验证过真的生效。

先搞清时序库到底在往磁盘上写什么:追加、分块与冷热

要谈容量,先要摆脱"一行一条记录"的关系库直觉。时序库内部不是按行存,是按"序列 + 时间块"存。同一条序列连续一段时间的数据会被攒成一个内部数据块(不同引擎叫 chunk、block、segment,本质是同一个东西),压缩、落盘、过期删除都以块为单位。

写入路径:内存里攒热块,写满才落盘

新到的点先进写前日志(WAL)保证不丢,同时写进内存里的活跃结构。当某个序列攒够一段时间(常见是 1 小时到 2 小时)或者攒够固定字节数,这个块被排序、编码、压缩,一次性刷成一个不可变文件,然后新开一个空块继续接。整条路径上没有一次"找到某一行去改它"的动作——修正数据和删除数据都是在别处记一笔,最后靠后台任务整块重写或者整块丢弃。

这个模型带来一个非常直接的推论:时序库对磁盘的要求是顺序写吞吐量,不是随机写 IOPS。这也是为什么在机械盘上跑时序写入一定难受,而在一块普通 SATA SSD 上却可以很从容——不是 SSD 更快,是 SSD 恰好擅长顺序写。

为什么要强调"冷块不可变"

块一旦写完就不再变化,这一点决定了后面所有操作的行为:压缩是一次性的、删数据只能整块删、改数据要整块重写。所谓"过期策略"实际上是把到期区块整个删掉,而不是逐行 DELETE。理解这点就能回答一个高频疑问——为什么 TTL 设成 30 天,实际磁盘上能看到 30 天多一点的数据:块按时间对齐,最后一个块没走完不能删。

磁盘没满,为什么查询已经很慢了

慢的第一种来源是块数量。每次跨时间查询,引擎要打开区间内所有候选块,先读索引判断哪些块含目标序列,再解压。块太小,文件数暴涨,光 inode 查找和 open 系统调用就是一笔开销;块太碎(一个块里同一条序列只有几十个点),压缩率也会跟着崩,因为你付了完整一份固定开销却只存了几十个点。

慢的第二种来源是 compaction。为了控制块数量,引擎后台会把相邻小块合并成大块。这个合并要读、解压、合并、重压、重写,是纯 CPU 和额外写放大的活。如果写入侧持续压力很高,compaction 追不上新块生成速度,块数就会单调上升,查询随之一路劣化。所以"加盘之后依然慢"是完全正常的——新盘只解决了空间,没解决块数量和 compaction 追赶。

乱序写入和批次大小:两个容易被忽略的变量

采集端断线补传、边缘盒子本地缓存后批量回传、多个采集通道汇聚时间不一致,都会产生迟到数据(out-of-order)。迟到点在多数实现里没法直接写进已经封闭的旧块,要么被塞进新块造成跨块重叠,要么触发一次昂贵的重写。块一旦出现时间区间的重叠,查询引擎就不得不多读几个块再做归并,读写两侧同时受损。

写入批次(batch size)则是另一头的取舍。一次请求里塞 500 条和塞 5 条,网络往返和解析开销差了上百倍,但大批次意味着数据在采集端内存里停留更久,掉电丢失窗口更大,也意味着单次请求的延迟尖峰更高。经验口径是让单批压缩前的报文体量落在几百 KB 量级、批次延迟不超过采集间隔的一半;具体值取决于你的丢包容忍度和链路状况,没有通用最优解。

压缩比到底由什么决定:不是算法标签,是数据特征

很多人把压缩比当成某种可以查表的产品属性。真实情况是,同一套引擎,规律采样、慢变量的温度信号能压到十几倍;同样是这套引擎,塞进去高精度抖动的浮点或者随机字符串,压缩比可以掉到 2 倍以内。算法只提供手段,上限在数据那里。

时间戳:delta-of-delta 编码

时间戳通常是每个点最占地方也最容易被压的一部分。原始 8 字节毫秒时间戳,先做一阶差分(相邻两条的间隔),规律采样时这个差分是常数;再对差分做一次差分(二阶差分,即 delta-of-delta),常数序列的差分结果就是 0,一个比特就能表示。这就是为什么 10 秒整点上报的数据,时间戳几乎不占空间——引擎只记录了"从某时刻起,每隔 10000 毫秒一条"。

反过来,只要间隔抖动(上报延迟、重试、采集端时钟不稳),二阶差分就不再是 0,编码位数立刻上升。这也是很多团队发现"压缩比莫名其妙变差"时第一嫌疑:不是值变了,是时间戳抖了。

浮点值:XOR(Gorilla 类)编码

数值的处理思路是相邻两条做异或,然后只记录异或结果里"有意义的那一段"。两个相近浮点数的二进制表示高位通常完全一致,异或之后高位是一长串 0,只需要记"前导 0 有多少位、中间有效位有几位、有效位是什么"。相邻值越接近,异或结果里的 0 越多,占用越小。

这套编码对两种数据几乎失效:一是相邻两条差异很大的高噪信号(比如未做任何平滑的瞬时电流采样、随机抖动的延迟测量值),相邻异或结果接近随机;二是保留了太多无效小数的浮点数。一个很常见的情况是传感器本身精度只有 0.1,但上报保留了 15 位小数,末几位全是噪声,等于亲手把自己一半的压缩空间废掉。入库前做一次合理的取整,往往比调优任何参数都有效。

字符串:低基数字典编码

标签值、状态枚举、单位这些重复出现的字符串,走的是字典编码:块内维护一份取值表,每条记录只存一个整数下标。取值集合越小(比如状态码只有 5 种),下标用几个比特就能表示。集合越大越接近随机字符串,字典本身反而是负担。这也是"高基数字段不要做标签"的技术根因之一——字典会膨胀到比原字符串还大。

通用压缩叠在哪一层

zstd、lz4、snappy 这类字节级通用压缩通常叠在列式编码之后,再压一层。不同实现的策略不同:有的在块落盘时对整个块做块级通用压缩,有的只在特定列上叠。这里有个成本收益的取舍——lz4 解压快但压缩比一般,zstd 压缩比更好但写入侧 CPU 消耗更高。写入是持续发生的,解压是查询时才发生的,所以对写多查少的历史层,用更重的压缩比(换更强的 CPU 消耗)通常是划算的;对最近热块,用轻量算法换取更低的写入延迟更合适。

综合各类公开技术资料,列式时序数据的整体压缩比常见区间在 4 到 15 倍,规律采样的慢变量偏上限,乱序高噪的数据偏下限。这个区间受数据类型、块内点数、字段设计影响极大,落到自己身上唯一可靠的办法是拿真实数据实测,别照抄别人给的数字。

标签基数是真正的放大器:一行 schema 就能让容量翻几十倍

如果压缩比决定的是"每个点存多大",标签基数决定的是"一共存多少个序列",而序列数是所有后续开销的乘数。这是全文里唯一能让容量瞬间失控的变量。

序列数等于标签值的笛卡尔积

一条指标的身份由"指标名 + 全部标签取值组合"唯一确定,这个组合的总数就是序列数(cardinality)。看一组不起眼的组合:8 个机房 × 50 个机架 × 20 台机器 × 5 种状态 = 40000 条序列,前提是把这些层级全都写成独立标签。而如果业务真正关心的粒度就是"机器",那么本该只有 8 × 50 × 20 = 8000 条序列,多出来的 32000 条全是白付的。

真正的灾难来自那些天然高基数的字段:设备序列号、容器 pod 名、请求 trace id、订单号、用户 id。这些字段的取值范围随时间单向增长,序列数也就跟着时间单向增长,而且永远不会回落。把 trace id 放进标签,等于给数据库安排了一个每天稳定变胖的任务。

怎么自查:两个数字就能定位

第一个数字是活跃序列总数。绝大多数引擎都提供了元信息查询接口或者 /status 之类的端点能直接取到,按指标名分组再排序,一眼就能看出哪个指标是罪魁祸首。

第二个数字是平均每条序列的点数:总点数 ÷ 序列数。这个数是关键诊断指标。列式存储期望每条序列在一个块里塞进几千个点,如果这个数只有几百甚至几十,说明数据被切得太碎——每个点都要摊一份块内的索引、头信息和列边界开销,压缩率崩坏、块数暴涨、内存索引同步膨胀。这种情况几乎总是由基数失控引起的。

字段还是标签:一个可以立刻套用的判断口径

标签负责"身份",字段(field)负责"值"。落到具体决策上,问三个问题就够了:

  • 会不会用 WHERE 或者 GROUP BY 选中它?会筛选分组才值得当标签;永远只是随数据显示的,应当是字段。
  • 取值空间是否有限且收敛?有限集合(几十到几千、不随时间单调增长)可以做标签,无限增长的一律不做。
  • 去掉它会不会丢失观测维度?像 trace id 这类需要下钻到单请求的,正确归属是日志或追踪系统,不是指标库的标签,指标层只应该存聚合后的结果。

一个落地手法是把高基数主键降级:用 service、region、instance_type 做标签,把 pod_id 挪到字段里或者干脆去掉,需要下钻时再从日志侧关联。这样做丢的是"按 pod 聚合"的即时能力,换来的是两个数量级的容量差。

基数爆炸怎么传导到整机

序列数不是只影响磁盘。每条活跃序列都要在内存索引里占一小份常驻空间,不同引擎的单序列开销差别很大,但从零点几 KB 到几 KB 这个量级里浮动都很常见;热块的哈希表要能装下所有活跃序列;每次查询还要在索引上做一次集合运算。所以基数涨十倍,受影响的不只是磁盘——内存占用很可能先于磁盘触顶,表现为 OOM 或者缓存命中率断崖。这就是为什么"磁盘还剩一半但机器已经很难受"这种现象在时序场景里格外常见。

降采样:原始多久、分钟级多久、什么时候可以删原始

降采样(rollup / continuous aggregate / downsampling,叫法不同机制一样)是预先按粗粒度算好聚合结果并单独存一份。它的价值不只是省空间,更在于查询性能的确定性:无论背后原始点有多少,查一年的日均值永远只扫几百个点。

三档精度的分工

原始精度(10 秒)负责最近几天的排障和异常回溯,看的是"早上八点十分到底发生了什么"。分钟级负责一到三个月的中期看板,看趋势和对比。小时/天级负责一年以上的长期分析、容量规划和合规留痕。绝大多数查询其实落在后两档上,把这两档准备好,对查询体验的改善幅度最大。

只存聚合值是不够的。一条有用的聚合记录至少应该带上时间桶起始、样本计数、均值、最大值、最小值,最好再加标准差或者分位数。缺了计数后面没法做二次加权聚合(分钟级再聚合成小时级时均值不能简单平均,要加权);缺了最值和标准差,异常检测就只剩一条平平的均值线,尖峰被抹掉了。

写时算还是查时算

写时算(预计算)是后台任务在时间桶闭合后立刻算完写走,成本发生在写入侧,查询极快;查时算(物化视图)是查询发起时才按需计算,成本在查询侧,存储更省但首次查询慢。默认建议:分钟级及以上用写时算,秒级的临时探索性查询用查时算。因为历史层的数据写入一次读取多次且不会再变,预计算的优势非常明显;反过来如果某个聚合只有你自己偶尔跑一次,为它常驻一层存储是浪费。

降采样之后原始数据什么时候能丢

判断标准不是天数,是"还有没有人需要在秒内还原原始波形"。如果过去三个月的原始点一次都没被查过(多数引擎的查询日志能看到访问情况),那说明"原始精度"这一层已经没有用户了,删掉即可。

保守一点的做法是先软着陆:把原始层的保留期从"长期"改成 7 或 14 天,观察两周有没有人抱怨;没问题再往下压。另一个容易忘的前提是——降采样必须带有补跑(backfill)能力,而且要在删除旧数据之前就把新桶算好。顺序应该是:先建 minute 层 → 补跑历史 → 校验新旧口径一致 → 再缩短原始层的保留期。颠倒这个顺序就等于永久丢失。

采集端能不能先自己聚一遍

边缘侧预聚合是一个常被忽略的选项。如果采集端本身就是个能跑逻辑的盒子(工业网关、边缘服务器),让它在本地按分钟聚一次再上报,链路上的数据量直接除以 6(10 秒采样对应每分钟 6 条变 1 条),入口带宽、写入侧 CPU、存储占用同步下降。代价是丢掉了中心侧的秒级细节和一部分灵活性:以后想看 10 秒精度,只能改边缘设备。所以这套方案适合的是那些业务上本来就只需要分钟级的指标;真正需要秒级回溯的核心项,仍然走原始上报。

把容量算成一张能复算的表:3000 点 / 10 秒的完整演算

下面这套算式不需要任何工具,纸笔就能走完。先给公式:

  • 每天点数 = 采集点数 × 86400 ÷ 采样间隔(秒)
  • 原始日新增 = 每天点数 × 单条原始字节数 ÷ 1024³(GiB)
  • 含索引日新增 = 原始日新增 × (1 + 索引摊销率),索引摊销通常取 15%~25%
  • 压缩后日新增 = 数据主体 ÷ 数据压缩比 + 索引部分 ÷ 索引压缩比(索引类结构压缩效果差得多)
  • 分层总量 = Σ(各层压缩后日新增 × 各层保留天数)
  • 整机规划量 = 分层总量 × 副本数 × 余量系数(建议 1.3~1.5)

按示例一步步算

前提:3000 个采集点,10 秒间隔,单条原始约 60 字节,目标留存为原始 7 天 / 分钟级 90 天 / 小时级 2 年。

第一步,每天点数。3000 × 86400 ÷ 10 = 3000 × 8640 = 25,920,000 条/天。顺带一句,这个公式默认"每个采集点每次上报一条记录";如果按字段拆开上报(每个采集点的 5 个指标各发一条),记录数要再乘字段数,但单条字节数会降到 30~40 字节,两者相乘的净结果通常比宽表略高一点。

第二步,未压缩体量。25,920,000 × 60 = 1,555,200,000 字节 ≈ 1.56 GB/天(约 1.45 GiB)。

第三步,加索引摊销。除了值本身,每条还要摊一份倒排索引、块内偏移和元数据。按 20% 计:数据主体 1.56 GB/天,元信息部分 0.31 GB/天,合计未压缩约 1.87 GB/天。

第四步,各自压缩。数据主体属于规律采样、值变化平缓的类型,取常见区间的中间值 10 倍:1.56 ÷ 10 = 0.156 GB/天。索引类结构压缩效果差很多,按 2 倍计:0.31 ÷ 2 = 0.156 GB/天。原始层实际落盘 ≈ 0.31 GB/天。

第五步,算分钟级层。3000 序列每分钟 1 点,一天 1440 分钟:3000 × 1440 = 4,320,000 点/天。聚合记录字段更多(时间戳 + 计数 + 均值 + 最值 + 标准差),单条按 120 字节计:4,320,000 × 120 = 518,400,000 字节 ≈ 0.52 GB/天。聚合后的值方差更大、规律更弱,压缩比按 5 倍计:0.104 GB/天。

第六步,算小时级层。3000 × 24 = 72,000 点/天 × 120 字节 = 8.64 MB/天,压缩按 4 倍:2.16 MB/天,即 0.00216 GB/天。

第七步,按留存天数求和(单副本)。

  • 原始层:0.31 × 7 = 2.17 GB
  • 分钟级:0.104 × 90 = 9.36 GB
  • 小时级:0.00216 × 730 = 1.58 GB
  • 单副本合计 ≈ 13.1 GB

第八步,乘副本和余量。三副本:13.1 × 3 ≈ 39.3 GB。再乘 1.4 的余量给 WAL、compaction 临时文件、删块重写时的峰值占用:约 55 GB。这套盘子三年之内不用再加。

为什么现实里还是爆了:几个乘数

上面这套算法理想状态下完全站得住,那磁盘到底是怎么被撑爆的?把半年 180 天、不做任何降采样、有一点小疏漏的版本算一遍就明白了:

  • 压缩没生效,比如整条 JSON 当字符串存、或者用了行存模式:未压缩 1.87 GB/天。
  • 三副本:5.6 GB/天。
  • 一直全精度留存,180 天:约 1008 GB,接近 1 TB。
  • 再加上没清过的备份快照、还没被清理的 WAL 堆积、以及被塞到同一块盘上的业务数据,撑爆一块 480 GB 甚至 1 TB 的盘完全合情合理。

也就是说,理想算式和现实之间差了差不多 20 倍,而其中每一个乘数(压缩、副本、降采样、混部)都是可以在设计阶段改掉的。这就是"加盘"永远追不上的原因——加盘是给那个 20 倍买单,而分层留存是把 20 倍里的绝大部分直接抹掉。

留存策略路线 磁盘占用量级 查最近数据速度 查一年跨度速度 运维复杂度 适合的数据规模
路线一:原始精度长期全留 随天数线性增长,本例单副本一年约 110 GB,三副本落到 300 GB 量级 最快,命中原生点,无需任何聚合 最慢,要扫亿级原始点,分钟级等待很常见 最低,配个过期策略即可 序列数百级、半年内就归档删库的小规模场景
路线二:原始短期 + 分钟级中期 约为路线一的 1/8 到 1/15,本例三副本约 40 GB 快,近期走原始层 90 天内尚可,超过 90 天无数据或仍需全扫 中,一套 rollup 任务加两个保留策略 多数中小监控场景,看板以近一个月为主
路线三:原始短期 + 分钟级 + 小时/天级长期 比路线二只多百分之几,长期部分几乎不占空间 快,近期走原始层 秒级返回,90 天前自动落到粗精度层 较高,多层 rollup、补跑与查询端的精度降级提示 需要跨年对比、容量规划或合规留痕的中大型场景

从算式反推机器:内存、CPU、磁盘各自在买什么

表里那三条路线落到采购清单上,差别比想象中小。真正决定机器规格的不是磁盘能装多少,而是这三样东西各自的瓶颈点在哪。

内存:买的是"活跃序列能不能装下"

内存主要花在三处:热块(正在写入、还没落盘的那部分)、倒排/正向索引(每条活跃序列都要占一小份常驻空间)、以及操作系统页缓存(兜住冷块的读取)。前两项直接跟序列数成正比。一个粗略自查口径:先用元信息接口拿到活跃序列数,再按"每千条序列几 MB 量级"这个常见量级做粗算(具体系数因引擎实现差异很大,不建议跨产品套用),然后在此基础上再乘 2~3 倍给页缓存和突发。

按这套口径,前面那个 3000 序列的例子,16 GB 内存绰绰有余;序列数上到十万,建议 32 GB 起步;上到百万级,就要认真评估是否做分片而不是无脑加内存了。选机型时把这点看清楚很关键——同样标称"大容量机型",有的给的是盘,有的给的是内存,价格结构完全不同。一万网络深耕 19 年(成立于 2007 年),在这类"大容量存储优先"与"高内存优先"机型的比选上能给出的组合比较全,从多盘位 SATA 机型到高内存密度的裸金属都有;但具体机型、带宽与价格需实时询价,以官网实时报价为准。这里的取舍一句话说清:序列数是内存问题,历史长度是磁盘问题,别用错资源去解决。

CPU:买的是压缩、compaction 和 rollup

写入侧的 CPU 主要消耗在编码压缩和 WAL 序列化上,后台持续消耗在 compaction(小块合并)和降采样任务,查询侧消耗在解压和大范围聚合。这三件事都具备一定的并行度,所以核数比单核频率更值钱。经验上 8 核是起步线;写入超过每秒十万点、或者同时跑着十几个 rollup 任务,就该往 16 核以上走。CPU 长期跑在 60% 以上是个明确的信号:compaction 大概率已经在追赶,块数量在涨。

磁盘:容量之外,真正要盯的是顺序写带宽和 IO 隔离

前面说过,写入路径是追加写,所以对随机 IOPS 的要求并不高,一块企业级 SATA SSD 就够了。但有两件事会毁掉这个前提:一是 WAL 和数据块放在同一块盘上,随机的 fsync 和数据块的顺序写混在一起;二是有人在隔壁跑业务库或者做批量导出。NVMe SSD 的价值在这种情况下体现得更明显——不是因为它更快,而是它有更深的队列余量能扛住争抢,把抖动控制在可接受范围内。

扩容时历史数据要不要重分布

这是个早晚会问到的问题。多数时序实现支持按时间或者按 hash 分片,时间分片扩容最简单:新数据写新分片,老分片原地保留到自然过期,几乎零重分布成本,代价是写压力短期集中在新分片上。哈希分片则扩容必须搬运历史数据,代价是一次大规模读写扰动。选择建议很直接:如果你的扩容是"每半年加一次机器"式的渐进式,按时间分片;如果是"三年之内不动"式的一次性规划到位,hash 分片可以把三年内的负载摊平。

冷数据归档到对象存储,省钱的边界在哪

再往前一层:时间最老的那部分数据,查询频率可能一年只有几次,但合规要求必须留三年。这时候把最粗的那层(天级甚至月级)导出成列式文件(Parquet 之类)丢进对象存储,是最划算的做法。要认清的成本边界在于:接口请求费用和取回延迟。对象存储按请求次数计费,如果上层查询会频繁去拉这些文件,账单会很难看;而且冷归档取回通常有延迟,不能指望秒级返回。所以归档文件格式要按"一次拉一大段"来组织,粒度宁粗勿碎,同时在查询层明确告诉使用者这段数据走的是归档通道。

备份时序数据的特殊性

一个容易被忽略的点是:时序数据的备份成本跟数据总量的关系并不大,跟备份窗口的关系更大。因为数据以不可变的大块为单位,理想做法是备份"已经封闭的块"——这些块不会再变,做增量快照天然一致,且不需要停写。热块反而难备份:它在持续变化,复制出来的文件很可能是不完整的。所以备份策略应当设计成"只备份封闭块 + 依赖 WAL/副本兜住最近几小时",而不是试图给整个库做一致性快照。恢复演练同样重要:恢复完成之后,"最近两小时的数据能不能查、能不能接着写、rollup 任务能不能继续跑"这三件事必须真的验证一次,而不是只看恢复任务报了个成功。

时序数据落地最容易踩的五个坑

坑一:把高基数字段当标签用。具体表现是把 pod_id、序列号、trace id、订单号写进 tag。为什么发生:这些字段在原始日志里很显眼,写采集脚本时顺手就贴上去了,上线初期序列数少,完全看不出问题。怎么判断:查活跃序列总数,再看"总点数 ÷ 序列数"是否低得离谱(几百量级就危险);或者看某个指标的序列数是不是随时间单向增长从不回落。怎么规避:上线前就按前面那三条口径过一遍每个 tag——会不会用于筛选分组、取值空间是否收敛、去掉会不会损失观测维度。已经上线的,把高基数字段降为字段或者从指标层剔除,需要下钻时走日志和追踪系统关联。

坑二:采样间隔设成 10 秒,但业务其实只看分钟级。具体表现是数据采集了一年的秒级精度,看板上全是按天/小时的折线。为什么发生:采集端的默认配置没人 review,"采细点以后说不定用得上"的心态。怎么判断:拉查询日志看看过去三个月有没有人以小于 1 分钟的粒度查过历史数据;九成情况是零次。怎么规避:反向做一次需求确认——哪些指标需要秒级回溯(通常是告警排障相关的少数核心指标),其余一律在采集端或入库时就按分钟聚合。这一条往往能直接砍掉六倍的写入量。

坑三:只加盘不降采样。具体表现是每次磁盘告警就扩容,扩容后内存和 compaction 压力随数据量一起往上爬。为什么发生:加盘是运维侧最省事的动作,而且当次一定能解掉告警。怎么判断:如果每次加盘之后的"告警间隔"在缩短,或者加盘之后发现备份窗口和恢复时间同步拉长,就说明这是线性扩容的死循环。怎么规避:先挂上 rollup 任务跑两周,用真实数据验证新旧一致后,再缩短原始层保留期。降采样之后通常能直接释放一个量级的空间,而且降低了内存和 compaction 压力——这两点是加盘给不了的。

坑四:降采样任务没设计补跑,改口径后历史对不上。具体表现是把"最大值"从原始峰值改成了 P95,或者修改了异常值过滤规则,结果今年 3 月之前的曲线和之后的不在一个量级上。为什么发生:降采样任务默认只处理新数据,改口径时没人想起历史也要重算。怎么判断:在口径切换的时间点两侧取同一个聚合指标做差异对比,断点两侧应该几乎重合,出现台阶就是这个问题。怎么规避:给每个 rollup 任务都实现"按时间范围重算"的能力,并且在配置里显式记录口径版本号。改口径的标准流程是:新口径先在新的目标表上补跑历史 → 校验 → 切换查询指向 → 观察一段时间再落旧表。

坑五:留存策略写在配置文件里,但从来没验证过真的删了数据。具体表现是 TTL 设了 30 天,三个月后发现磁盘上还躺着半年前的文件。为什么发生:过期删除是后台任务,可能因为权限、只读挂载、磁盘满了、或者任务被卡住而静默失败,很多实现不会主动告警。怎么判断:不能只看配置项,要实际查:库报表里的磁盘占用、最老数据点的时间戳、以及数据目录下最老块文件的 mtime,三者对着看。怎么规避:把"最老数据点时间戳"做成一个监控项并设置告警;每季度做一次演练,在测试环境用同样配置跑一遍确认是真删。删完之后的空间释放是否及时,也要一并验证——有些实现的归还会有延迟。

关于留存、压缩与降采样的常见疑问

Q1:时序数据一般留多久合适,有没有通用标准?

A1:没有通用标准,只有一套能套用的分层建议:原始精度留 7 到 14 天,分钟级留 3 到 6 个月,小时级留 1 到 3 年,天级或月级随合规要求长期留。真正的依据是"谁在什么时间范围内会以什么粒度查"。做排障的人基本只看最近三天;做趋势对比的人看一个月到一季度;做容量规划和年度汇报的人看一到三年。把这三类人的需求分别对应到三档精度,后续想吃掉一部分历史数据就很少有人抱怨。反过来,如果某些数据依法必须留存(例如生产环境监测数据常见于两到三年),那应该保留的是最粗那一层而不是全精度,这样既满足合规又不吃存储。

Q2:压缩比到底能到多少,我怎么知道自己的实际压缩比?

A2:公开技术资料里,列式时序数据的整体压缩比常见区间在 4 到 15 倍,规律采样的慢变量偏上限,乱序或高噪数据偏下限,这个区间受数据类型、块内点数和字段设计影响很大,不建议直接套用别人给的数字。自查方法很简单:用引擎自带的磁盘占用统计(或者数据目录大小)除以"总点数 × 单条原始字节数",得到的就是实际比值。注意要分别看数据部分和索引部分,索引的压缩效果差得多,很多"感觉压缩没生效"其实是索引在拖后腿。如果算出来低于 4 倍,优先检查三件事:是否保留了过多无效小数位、标签基数是否失控、采样间隔是否抖动。

Q3:降采样会不会丢信息?丢了的部分还能找回吗?

A3:一定会丢,这一点不用回避——从 10 秒降到分钟级,6 条变 1 条,中间的细节不可能凭空留下。但丢的是"波形细节",保住的是"趋势和统计特征",前提是你存对了字段。如果只存均值,尖峰会被抹平,某个设备在这一分钟里瞬断了 20 秒这件事就看不出来;所以聚合记录里至少要带样本计数、最大值、最小值和标准差,有条件再补 P95/P99 分位数。至于能不能找回,取决于原始层还在不在:稳妥做法是把原始层保留期设得足够长(比如两周),降采样只是给历史提供一个便宜的替代存储,而不是立刻把原始删掉。

Q4:序列数多少算高基数?有没有一个判断的阈值?

A4:阈值没有普适值,要看机器规格和引擎实现,但有两个非常好用的相对指标。第一个是单机的活跃序列总数:几千到几万是常规量级,几十万要开始认真规划内存和分片,百万级以上基本必须做水平拆分。第二个更有价值——平均每条序列的点数(总点数 ÷ 序列数):如果这个值在几千以上说明数据密度健康;掉到几百甚至几十,说明序列被切得太碎,无论绝对值多少都已经"相对高基数"了。第三个观察维度是增长趋势:如果序列数随时间单调增长并且从不停歇(典型的 pod_id、trace id 症状),哪怕当前绝对值不高,半年后也一定会爆,趁早处理。

Q5:磁盘已经不够了,第一步该做什么?

A5:先别加盘,先用十分钟做三件事把现状摸清楚。第一,查活跃序列数和平均每条序列的点数,确认是不是基数问题;第二,查压缩后的实际磁盘占用和未压缩体量的比值,确认压缩有没有生效;第三,查最老数据点的时间戳,确认过期删除任务是不是真在工作。这三个检查会直接告诉你钱该花在哪:如果是基数问题,加盘完全无效,改 schema 才是正解;如果是压缩没生效,检查字段设计和采样抖动;如果是过期任务卡住了,修任务比采购新设备便宜得多。只有确认了"数据密度健康、压缩正常、过期正常、纯粹是量真的增长了"之后,加盘才是合理动作。

Q6:时序库能不能和业务库混部在同一台机器上?

A6:技术上能跑,但不建议。原因不是性能不够,是 IO 模式冲突:时序写入是持续的大块顺序追加,compaction 后台又在不断做读写重写;而业务库是典型的随机小 IO 加频繁 fsync。两者放在一起会互相抢 IO 队列和页缓存,表现得很难归因——业务侧偶发慢查询,时序侧偶发写入抖动,两边都在怀疑对方。如果一定要混部,至少做到两件事:时序库的数据盘和 WAL 各自独立,把 compaction 和 rollup 调度到业务低峰执行;同时把两边的 IO 指标分开采集,真出问题了才说得清是谁在抢谁。规模起来之后(每秒万点以上),拆分几乎是必然的。

Q7:历史数据的统计口径要改,怎么保证新老数据能对齐?

A7:核心是不能"原地改"。正确顺序是四步:第一,给每种 rollup 记录一个口径版本号,写进元数据;第二,新的口径先在新目标表上,从头补跑全部历史;第三,把新旧两套结果在同一时间范围内做差异校验,重点看断点是否平滑、环比有没有台阶;第四,校验通过后切换上层查询的指向,观察至少一个完整周期再落旧表。这里最容易忽略的是第二步的补跑本身对机器的压力——重算三年历史的 CPU 和 IO 消耗可能远超在线写入,务必限流、分批、放在低峰执行,并且先确认磁盘上有足够空间放两套数据。

Q8:数据要求留存三年,还不能用 S3 这类归档,怎么办?

A8:抓住一点——合规要求的通常是"可查证的结果",不是"原始波形"。所以满足三年留存的正确做法是留最粗的那层,而不是全精度。按前面的示例,天级/月级汇总留三年,占用往往只有几 GB,再把这一层导出成列式文件定期做离线副本,单台机器就能扛下来。但在这个方案里必须另外说清三件事:一是法定留存的数据要有访问控制和操作审计;二是留存下来的必须是不可篡改或至少能校验一致性的副本(导出文件的校验和要单独保存);三是明确冷数据的回流成本——如果三年后监管要求提供明细,你要能说清这些数据多久之内能取回。这三点比存储空间本身重要得多。

时序数据留存一文的数据来源、参考范围与估算口径

本文涉及的编码方式(时间戳的 delta-of-delta 编码、浮点值的 XOR / Gorilla 类编码、低基数字符串的字典编码)以及 zstd、lz4 等通用压缩算法的叠加层次,整理自时序数据库领域的公开技术资料与公开论文,用于说明压缩机制的原理,不代表任何具体产品的官方指标。文中所有压缩比均以"常见区间"表述,并注明受数据类型、采样规律性、字段设计与标签基数影响,未引用任何厂商公布的官方压缩比数字。

容量演算部分为可复算的通用估算:以 3000 个采集点、10 秒采样间隔、单条约 60 字节、压缩比按 8 到 12 倍的中间值取用为输入,逐步推导原始层 7 天、分钟级 90 天、小时级 2 年的三层留存总量,每一步中间结果均在文中列出,读者可按自家采集点数与采样间隔代入同一组公式重算。文中未使用任何实测跑分、未引用任何客户数据与虚构测试结果。服务器、带宽及其他硬件资源的费用,本文一律写明需询价,具体以签约时最新报价与合同为准。相关产品与方案信息可参见 https://www.idc10000.net/ 。

时序数据这篇手册里,一万网络给出的落地结论

如果只能记住一句:时序容量问题的本质是乘数问题,不是加法问题。加盘是在结果上做加法,永远追不上;而消除乘数——把标签基数收一收、让压缩真正生效、把不需要秒级精度的指标在采集端就聚掉、用三层留存替掉单一保留期——是一次性的、量级级别的收益。按文中的算式,同一批 3000 点 / 10 秒的数据,做对了约为 55 GB 的三年规划量,做错了半年就能吃掉接近 1 TB,中间差的 20 倍全部来自这四件事。

  • 先把账算出来,再决定买什么:用"每天点数 = 采集点数 × 86400 ÷ 采样间隔"这条式子,五分钟算出半年后、三年后的量,比等告警靠谱得多。
  • 序列数决定内存,历史长度决定磁盘:这两个数字要分开盯,用错资源去解决,钱花了却没效果。
  • 行动顺序不能乱:先自查基数与压缩比,再上 rollup 并补跑历史、校验口径一致,最后才缩短原始层保留期。
  • 余量按三到五成留:在算出来的量上再乘 1.3 到 1.5,给 WAL、compaction 临时文件、备份快照和突发写入留出缓冲。
  • 监控"最老数据点时间戳",而不是监控磁盘水位:前者能在过期任务静默失效的第一周就把问题暴露出来,后者往往要三个月后才报警。

时序数据库服务器与备机租用咨询:一万网络能提供的支持

一万网络深耕 19 年(成立于 2007 年),面向这类"写入密集 + 容量敏感"的时序场景,能提供的不只是机器本身:多盘位大容量存储机型用于放历史层与归档层,高内存密度机型用于扛活跃序列和索引,NVMe 机型用于隔离 WAL 与 compaction 的 IO 争抢,另有裸金属服务器可作为时序库独立部署的物理底座。节点覆盖华南、华东、华北、中国香港及海外,BGP 多线与 CN2 GIA 回国线路可选,跨地域采集汇聚的场景可以把入口节点放在离设备更近的位置。配套能力包括 7×24 中文工单、平均 5 分钟响应、硬件故障 10 分钟自动迁移、免费系统盘快照(每日 3 份、30 秒回滚)、免费备案协助、5 到 20G 免费 DDoS 防护、自营机柜最快 1 分钟上架,以及针对业务特性的部署协助。

如果你手上的项目已经有明确的采集点数、采样间隔和字段设计,可以直接套用本文的算式先算出三年规划量,再据此确定内存、CPU 与磁盘的配比;如果对这些参数还没有把握,也可以先把现状提供给我们,由工程师协助梳理采集粒度、标签基数和留存策略,再给出对应的机型建议。具体机型、带宽与价格需实时询价,以官网实时报价与合同为准。


上一篇:同一张卡并发从 4 提到 32,吞吐没翻倍延迟先翻了几倍:连续批处理与 KV cache 这笔显存账怎么算

下一篇:表和索引一天比一天大、同一条 SQL 却越来越慢:PostgreSQL 的膨胀与 autovacuum 到底谁该背这个锅