想象你的手机相册:一家人出门旅游,每分钟拍一张,一天下来几千张照片。手机提示“存储空间不足”时,你怎么办?聪明的做法是——按小时挑一张“代表照”,比如中午拍一张、下午拍一张;再把整趟旅行整理成一本“旅行相册”,以后想回忆,翻相册就行,不用在一万张原图里翻到手指发麻。时序数据库面对的数据,恰恰就是这种“每秒都在拍照片”的场景:服务器的CPU使用率、机房的温度、APP的访问量,每一秒都在产生新数据。一年下来,一个指标就有三千多万条记录。降采样与连续聚合,就是给数据做“相册整理”:降采样负责把高频数据按时间窗口提炼成低频概要,连续聚合负责“旅行一结束就立刻装订好相册”,让未来的大跨度查询不用再翻原片。今天,我们就用这本“相册”的整理之道,把降采样和连续聚合讲透。
掌握时序数据降采样与连续聚合,你就掌握了监控与IoT系统“降本增效”的核心杠杆——既能省下九成存储,又能让大跨度查询毫秒返回,这是时序平台架构师的基本功。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
数据膨胀难题
手机的相册正在被照片塞爆:每分钟一张,一天就是一千多张,一年三十多万张。每张照片都舍不得删,可手机越来越慢、云盘越来越贵,翻旧照片也越来越难——这就是时序数据面临的真实处境。服务器CPU、内存、磁盘利用率,或机房里一排传感器的温度读数,都以“每秒一条”的节奏持续写入。单个指标一年就有三千多万条记录,而一个生产系统往往监控着成百上千个指标。
把镜头拉近看技术本质。这种按时间顺序持续追加、只增不改的数据叫时序数据。它的写入永远很轻快,痛点全在“存”和“查”上:全量保留代价高昂,跨月、跨年的查询要在海量记录里逐条扫描,慢得让人抓狂。更扎心的是,这些数据里绝大多数是“重复的日常”——CPU在 30% 到 40% 之间小幅波动,这种细节对长期趋势分析几乎没有价值。
数据膨胀就像相册越堆越厚,想靠“多买手机内存”硬扛绝非长久之计。真正聪明的做法,是像整理相册一样,把“平凡的照片”浓缩成几张“代表照”——这正是降采样要做的事。
- 总数据量 VV 表示一段时间内累积的全部数据点条数,单位是“条”
- 每秒采样率 rr 指每秒写入的数据条数,比如一台机器每秒上报 1 条 CPU 指标
- 总时长 TT 是采集持续的总秒数,1 天是 86400 秒,1 年约 3154 万秒
- 乘法运算把每秒条数与总秒数相乘,就得到数据总量,例如每秒 1 条、连续 1 年,约产生 3153 万条
降采样机制
整理相册时,你不会把每一张照片都保留在“精选集”里:每个小时挑一张最清晰的当作代表,一天就只剩二十多张,好找又好存。降采样就是时序世界里的“精选照片”:把时间轴切成固定长度的小窗口,每个窗口内的所有数据点,被一个聚合值替代。
这个“切窗口”的动作叫时间窗口划分。假设采集频率是每秒一条,我们把时间切成每 60 秒一个桶,第 1 秒到第 60 秒进 1 号桶,第 61 秒到第 120 秒进 2 号桶,以此类推。每个桶里原本有 60 个原始点,经过聚合后只输出 1 个代表值,数据量立刻缩减为原来的六十分之一。这就是“精度换规模”:我们放弃了秒级的细枝末节,换来了数量级的存储下降。
降采样不是在“毁掉”数据,而是在替数据做减法:把琐碎的秒级波动折叠成清晰的分钟级趋势。配合保留策略使用——原始数据只留 7 天,降采样后的概要数据留 1 年——相册就能既装得下历史,又翻得动当下。
- 窗口 WW 是时间轴上的一段固定区间,比如“第 0 秒到第 59 秒”
- 窗口内点数 n_wn_w 表示落在窗口 W 内的原始数据点个数,窗口 60 秒、每秒 1 条时,n_w = 60
- 求和符号把窗口内所有点的值 v_t 逐项相加,v_t 读作“时刻 t 的取值”
- 取平均用总和除以点数,得到该窗口的代表值,记作 \bar{v}_w
聚合函数族
整理代表照时,选哪一张取决于你想留住什么:想记录“最灿烂的一刻”就挑最好看的,想表达“普遍状态”就挑最普通的。降采样窗口里的聚合函数,就是一个个不同口味的“选片标准”。均值(AVG)给出窗口内的典型水平,适合看整体趋势;最大值(MAX)和最小值(MIN)锁定极端情况,是异常告警的哨兵;计数(COUNT)和求和(SUM)核算总量,比如一分钟内请求总数。
还有一个高手偏爱的角色——分位数(P95)。它回答的问题是:“95% 的情况下,延迟都在这个值以内?”均值会被极端值拉偏,P95 却能诚实地暴露长尾。举个例子:某窗口内请求延迟分别是 10ms、12ms、11ms、300ms、13ms,均值高达 69ms,看着吓人;而 P95 会告诉你“95% 的请求都落在 13ms 附近”,这才是真实体验。监控场景里,P95 聚合是发现“少数慢请求拖垮体验”的利器。
选片标准决定了相册的性格,聚合函数决定了数据概要的“口味”。但无论选哪种口味,每次手动计算都要从头扫一遍窗口内所有数据——如果有一个“自动装订相册”的机制,让代表值提前算好、随新照片自动更新,那该多省事?这正是连续聚合的登场时刻。
- 最大值符号max 表示从窗口 W 的所有 v_t 中挑出数值最大的那个,用于捕捉峰值
- 概率条件P(v_t ≤ x) 表示“窗口内取值不超过 x 的点占比”,是一个 0 到 1 的概率
- 下确界 min在满足“超过 95%”这个条件的所有 x 里,取最小的那一个
- 综合含义q_{95}(W) 就是窗口 W 的 P95 分位值——95% 的数据点都比它小,它代表大多数情况的上限
连续聚合引擎
旅行结束后,你立刻花一个晚上把几千张照片整理成一本相册。之后每拍一张新照片,你只是把新照片夹进相册最后一页,绝不会推翻整本相册重做。连续聚合就是时序数据库里的“自动装订机”:系统预先定义好常用时间窗口(比如 1 分钟均值、1 小时最大值),后台任务持续计算并把结果物化成一张实实在在的表——这类由查询结果“实体化”而来的表,就叫物化视图。
物化视图与普通视图最大的区别是“真的存储”。普通视图只是一句 SQL 的快捷方式,查询时照样扫原始数据;物化视图则把计算结果落盘成一张独立的表,查询直接读它。为了保证画面不过期,数据库会按刷新调度周期性地后台重算。而重算的聪明之处在于增量更新:新数据到达时,只刷新受影响的少数窗口,已经算好的历史窗口一概不动。传感器每秒钟都在产生新数据,增量更新让连续聚合的维护成本几乎可以忽略不计。
有了这本“自动更新的相册”,当老板问起“过去一年 CPU 平均使用率怎么样”,系统不用再去翻 3 千万张原始照片,直接翻开装订好的年度相册,一秒钟就给出答案。接下来,我们仔细看看查询是怎么走上这条快车道的。
- 增量开销 C_inc只处理新到达的 Δt 秒数据,成本与 Δt 成正比
- 全量开销 C_full每次重算要遍历整个历史区间 T 的全部原始数据
- 远小于符号新数据通常只有秒级规模,历史却是年级规模,因此增量成本远低于全量成本
查询优化法
你永远不会为了回忆“去年夏天去了哪”而逐张翻看三万张照片——你直接翻开旅行相册,找到“去年七月”那一页。连续聚合对查询的优化,正是把“逐张翻照片”变成“翻相册”。当用户发起一个跨越数周、数月甚至数年的大跨度查询,数据库会识别出查询的时间范围与聚合方式,优先读取预计算好的物化视图,而不是扫描原始数据表——这个行为叫查询下推。
结果直接复用带来两个显著收益:一方面,大跨度查询免扫原始数据,磁盘 IO 从“读几百 GB”降到“读几 MB”;另一方面,响应时间从秒级甚至分钟级跃升到毫秒级。在监控大屏、告警规则和报表系统中,这种差异是肉眼可见的:左边同事的图表还在转圈,右边你的图表已经渲染完毕。聚合粒度越粗、时间跨度越大,收益越明显。
查询优化让“翻相册”成为默认路径,但这一切的前提是工程上把机制落地到位。不同的业务场景,相册的整理方式也不同:监控系统有监控的整法,物联网有物联网的整法。我们来聊聊实战。
- 原始点数 N_raw窗口聚合前,原始表里的总数据条数
- 窗口长度 mm 代表聚合窗口的秒数,窗口越大,压缩越狠
- 降采样后点数 N_down用总条数除以窗口长度,得到聚合后的概要条数
工程实践
把相册整理流程做成一条自动化流水线:相机不停拍,后台每隔几小时自动精选,满一年自动装订成册,旧的原片按规矩进冷宫。时序数据库的工程实践也是如此。监控场景是最典型的用户:Prometheus 这类监控系统采集 CPU、内存、网络流量等指标,原始数据保留 7 天用于排障,同时定义连续聚合任务,产出 5 分钟均值、1 小时均值和每天最大值,保留一年用于容量规划。
物联网场景同样受益巨大:智慧楼宇里上千个温湿度传感器、电表、水表每秒上报数据,按小时聚合后一张表就能容纳整栋楼十年的环境趋势。实际效果可以用一个数字概括——存储成本降九成。窗口取 60 秒时,数据量缩减到原来的 1/60,存储成本下降超过 98%。配置指引上,核心原则是“近细远粗”:新数据用秒级窗口保留,越老的数据用越粗的窗口,冷热分层各得其所。
工程落地之后,我们还要冷静地问一句:把相册从三万张精简到三百张,真的没有代价吗?有。精度与成本之间的拔河,是降采样永恒的主题。
- 节省率 SS 表示降采样后节省的存储比例,1 代表全部省下,0 代表没省
- 占比计算N_down / N_raw 表示概要数据占原始数据的比例,是分子的缩减倍数
- 代入窗口 m由于 N_down = N_raw / m,占比简化为 1/m,因此 S = 1 - 1/m
- 实例验证m = 60 秒时,S ≈ 98.3%,也就是存储体积缩减到原来的 1.7%
取舍权衡
相册整理得越狠,省下的空间越多,但丢失的细节也越多。如果你把照片全部按“每小时一张”精选,恰好错过了孩子第一次站起来的瞬间,那这本相册再省空间也失去了意义。降采样的取舍同样微妙:窗口越大,数据越少、查询越快,但求均值会掩盖短暂尖峰——一次 500ms 的延迟抖动,混在 60 秒的均值里几乎无影无踪。
所以优秀的时序数据库都提供“粗细结合”的保留策略:关键语义必须保全。告警场景下,MAX 聚合必须保留,因为“是否突破阈值”是生死线;容量规划里,日均值和 P95 更有价值。陈旧数据并非垃圾——一年前的 CPU 趋势对判断服务器扩容节奏依然重要,只是不需要秒级精度。权衡的本质是给不同年龄段的数据分配不同的“相册精细度”:热数据原样珍藏,温数据摘要留存,冷数据只留骨架。
这就是降采样与连续聚合的完整哲学:用可控的精度牺牲,换取数量级的成本与性能收益,再把最关键的语义牢牢攥在手里。相册不必每张都留,但值得留的,一张都不能丢。
- 原始数据体积r × D_raw,每秒 r 条数据、保留 D_raw 天,所占的空间
- 概要数据体积(r / m) × D_down,每秒 r 条聚合成每秒 r/m 条、保留 D_down 天
- 总空间两部分相加,就是这套保留策略的总存储占用
- 调整旋钮调大 m 或缩短 D_raw,存储下降、精度下降;反之精度回升、成本上升
AI 应用场景
云监控平台指标趋势分析
阶段:数据治理与存储优化
解决的问题:传统做法把一年内所有秒级指标全量保存在热存储中,存储成本随机器数量线性爆炸;“查询近 30 天 CPU 平均使用率”需要扫描几十亿行,报表响应以分钟计。AI 能力形态表现为连续聚合:预计算 5 分钟均值与 1 小时均值的物化视图,趋势类查询自动命中预计算结果。
具体案例:某云厂商可观测性平台对 2000 台服务器的 CPU、内存指标做降采样,原始数据保留 7 天,连续聚合结果保留 1 年。改造后,30 天趋势报表从 80 秒降至 0.2 秒,存储成本下降 85%。
智慧楼宇物联网传感网
阶段:数据生命周期管理
解决的问题:上千个温湿度、电表、水表传感器每秒上报数据,全量存储一周就耗尽机房磁盘。通过窗口降采样按小时求均值与极值,既保留环境变化趋势,又保留“设备异常高温”的告警语义,大跨度能耗分析无需触碰原始数据。
具体案例:某智慧园区部署 1200 个传感器,原始数据每秒 1 条、保留 3 天,降采样为小时均值后保留 3 年,历史能耗曲线查询响应从分钟级变为秒级。
电商大促流量监控与容量规划
阶段:时序洞察与容量规划
解决的问题:大促期间每秒订单量、QPS、接口延迟剧烈波动,全量历史数据难以支撑“去年双十一同时段表现如何”这类长跨度对比查询。连续聚合预计算分钟级均值与 P95 延迟,既保留长尾信息,又让跨年容量对比即时可用。
具体案例:某电商平台在大促前用连续聚合重建近三年大促时段的分钟级摘要,容量规划团队在 10 秒内完成历史同期对比,精准预测了本次大促的峰值流量规模。
Python 演示
import mathimport randomimport time class TimeSeriesDB: """小型时序数据库: 存原始数据 + 降采样""" def __init__(self): self.data = [] self.window_sec = 60 def ingest(self, points): # 批量写入 (timestamp, value) 点 self.data.extend(points) def raw_points(self): return len(self.data) def down_sample(self, window_sec, mode="avg"): # 按窗口把高频数据聚合为低频概要, 返回 [(窗口起始秒, 聚合值)] result = [] idx = 0 n = len(self.data) while idx < n: window_id = self.data[idx][0] // window_sec vals = [] while idx < n and self.data[idx][0] // window_sec == window_id: vals.append(self.data[idx][1]) idx += 1 if mode == "avg": agg = sum(vals) / len(vals) elif mode == "max": agg = max(vals) elif mode == "min": agg = min(vals) else: agg = sum(vals) result.append((window_id * window_sec, round(agg, 2))) return result class ContinuousAggregator: """连续聚合: 预计算窗口统计, 新数据只做增量更新""" def __init__(self, window_sec): self.window_sec = window_sec self.sum_map = {} self.count_map = {} self.max_map = {} def insert(self, points): for ts, v in points: w = ts // self.window_sec self.sum_map[w] = self.sum_map.get(w, 0.0) + v self.count_map[w] = self.count_map.get(w, 0) + 1 if w in self.max_map: self.max_map[w] = max(self.max_map[w], v) else: self.max_map[w] = v def query(self, start_w, end_w): # 直接读预计算摘要, 不扫描原始数据 out = [] for w in range(start_w, end_w + 1): if w in self.sum_map: avg = self.sum_map[w] / self.count_map[w] out.append((w * self.window_sec, round(avg, 2), self.max_map[w])) return out def gen_sensor_data(n=90000, base=30.0, amp=10.0): # 模拟温度传感器每秒上报一条数据 rng = random.Random(42) points = [] for i in range(n): v = base + amp * math.sin(i / 300.0) + rng.gauss(0, 1.5) points.append((i, round(v, 2))) return points def main(): raw = gen_sensor_data(90000) # 90,000 条秒级原始数据 db = TimeSeriesDB() db.ingest(raw) t0 = time.time() ds = db.down_sample(60, "avg") # 降采样: 60 秒窗口求平均 t1 = time.time() agg = ContinuousAggregator(60) agg.insert(raw) # 预计算所有窗口摘要 t2 = time.time() res = agg.query(0, 1499) # 查最近 25 小时的窗口摘要 t3 = time.time() print("原始点数:", db.raw_points()) print("降采样后:", len(ds), "点, 耗时 %.4fs" % (t1 - t0)) print("连续聚合查询窗口数:", len(res), ", 耗时 %.6fs" % (t3 - t2)) print("数据量缩减至原规模: %.2f%%" % (len(ds) / db.raw_points() * 100)) if len(res) > 84: print("示例窗口 5040s: avg=%.2f max=%.2f" % (res[84][1], res[84][2])) if __name__ == "__main__": main()
db.down_sample(60, "avg")按 60 秒窗口求均值,是降采样的核心入口self.sum_map[w] = self.sum_map.get(w, 0.0) + v增量累加窗口总和,新数据只更新它所在的窗口self.count_map[w] = self.count_map.get(w, 0) + 1增量统计窗口内点数,为求均值提供分母agg.insert(raw)一条命令完成全量预计算,等价于“旅行结束后立刻装订相册”agg.query(0, 1499)直接读预计算摘要表,查询结果在内存字典中瞬时返回
常见误区
误区降采样只是把数据“压缩”了一下,查询结果应该和原始数据一模一样,顶多文件变小了。
正解降采样是真正的“丢数据”——每个窗口只保留一个代表值,窗口内的细节被永久丢弃。就像每小时挑一张代表照,其余 59 张必然消失。查询均值还能反映趋势,但要查“某天 10:03:17 那一秒的精确值”,降采样后的数据给不出答案。明确这一点,才能正确设计保留策略。
为何常见:日常中“压缩图片”不会改变内容,容易让人把数据压缩和降采样混为一谈。
误区我写个 cron 任务,每五分钟跑一次 SQL 聚合,不就实现连续聚合了吗?没必要用数据库内置功能。
正解定时脚本是全量重算,每次都要扫历史数据,成本高且数据有窗口空白期。真正的连续聚合由数据库内建调度器驱动,采用增量更新:新数据到达只刷新受影响窗口,历史窗口分毫不动,查询还能自动下推到预计算结果上。用脚本模拟,既慢又容易出错。
为何常见:很多人只见过“定时跑批”这种通用 ETL 思路,不知道数据库层面有专门的原生优化。
误区窗口越大,数据越少,存储越省,干脆把窗口设成 24 小时,一年只有 365 个点,多痛快。
正解窗口过大的代价是趋势失真和语义丢失。24 小时均值会让一天的负载曲线变成一条直线,深夜低谷和白天高峰全被抹平,告警和容量规划都失去依据。P95、MAX 等函数在超大窗口下同样失去意义。窗口选择应匹配业务决策粒度:告警看秒级极值,趋势看分钟均值,规划看日级汇总。
为何常见:只盯着“省存储”这个单一指标,忽略了数据概要的核心使命是“仍能回答业务问题”。
误区既然有了降采样概要,原始数据就没什么用了,删掉能省最多空间。
正解降采样数据回答“趋势”问题,原始数据回答“取证”问题——某次故障的精确现场、某次突刺的完整波形,只在原始数据里存在。合理做法是分层保留:原始数据保留短周期(7 天),概要数据保留长周期(1 年)。直接把原始数据全部删掉,等于把监控系统的“事故黑匣子”也扔了。
为何常见:只看到存储成本,没看到原始数据在故障排查中的不可替代价值。
误区连续聚合是预计算好的,查询它任何时候都会得到和全量计算完全一致的结果。
正解连续聚合的结果有“时效性窗口”。正在写入数据的窗口尚未闭合,它的聚合值是“截至当前的部分统计”;需要等窗口结束并刷新完成后,结果才与全量计算一致。很多系统的设计是“当前窗口的数据延迟可见”,比如查询最近 1 小时均值时,最后一分钟可能稍旧。理解刷新调度,才能设计出告警可接受的延迟。
为何常见:把“预计算”等同于“静态不变”,忽略了数据持续流入带来的动态更新问题。
面试题
Q1. 降采样的核心目标是什么?
- A.提高数据采样精度
- B.减少数据量降低存储成本← 正确答案
- C.加速数据写入速度
- D.改变数据字段类型
答案:B。降采样把高频数据按窗口聚合为低频概要,直接目标是压缩数据量、降低存储与查询成本,相当于给相册做精简。它不提高精度,也不改变字段类型,写入速度不是降采样要解决的问题。
Q2. 某系统每秒采集一次 CPU 数据,以 60 秒窗口做均值降采样,降采样后的数据分辨率为?
- A.每分钟一个数据点← 正确答案
- B.每秒一个数据点
- C.每小时一个数据点
- D.每天一个数据点
答案:A。每秒一条原始数据,按 60 秒窗口聚合,每个窗口输出一个均值点,因此分辨率是“每分钟一个点”,相当于从 60 张原图中挑一张代表照。
Q3. 关于连续聚合相比普通全量重算的优势,下列说法正确的有?
- A.查询可直接复用已计算的窗口结果← 正确答案
- B.每次查询仍要扫描全部原始数据
- C.新数据到达时只需增量更新受影响的窗口← 正确答案
- D.不需要存储任何预计算结果
答案:AC。连续聚合把窗口统计物化为独立表,查询直接复用;新数据只刷新受影响窗口,成本远低于全量重算。B 是普通查询的劣势,D 与连续聚合的本质(预计算存储)相悖。
Q4. 下列哪个函数适合作为降采样的聚合函数?
- A.平均值← 正确答案
- B.快速排序
- C.数据去重
- D.事务提交
答案:A。平均值把窗口内多个值浓缩为一个代表值,是经典聚合函数。排序和去重不产生窗口统计量,事务提交是数据库写入机制,与聚合无关。
Q5. 关于连续聚合的维护开销,描述正确的有?
- A.新数据到达只需刷新受影响的时间窗口← 正确答案
- B.每次刷新都必须重建全部历史窗口
- C.窗口统计信息可以用累加、计数等方式增量维护← 正确答案
- D.维护开销与数据到达速率完全无关
答案:AC。增量更新只处理新数据所在的窗口,维护成本与新增数据量成正比;均值可用“累加和 + 计数”增量维护。B 是低效的全量重算,D 忽略了数据速率是开销的决定因素。
Q6. 降采样窗口从 60 秒调整为 300 秒,输出数据量约为原来的?
- A.五分之一← 正确答案
- B.五倍
- C.保持不变
- D.六十分之一
答案:A。窗口越大,输出的聚合点越少。N_down = N_raw / m,从 60 秒调到 300 秒,m 扩大 5 倍,点数缩减为原来的 1/5,相当于相册从每小时一张变成每五小时一张。
Q7. 在时序监控中,P95 分位聚合主要用于解决什么问题?
- A.表示 95% 的请求延迟低于该值,暴露长尾← 正确答案
- B.计算系统的总网络流量
- C.备份原始数据到冷存储
- D.校验写入数据的完整性
答案:A。P95 分位值回答“95% 的情况都在这个阈值以内”,能揭示被均值掩盖的长尾延迟。它不计算总量、不做备份、也不做完整性校验——它是聚合函数族中“看极端体验”的利器。
Q8. 某监控系统原始数据保留 7 天,降采样概要保留 1 年,这种设计体现了什么思想?
- A.冷热分层的数据保留策略← 正确答案
- B.数据库的主从复制机制
- C.事务的隔离级别控制
- D.索引的自动重建策略
答案:A。近期数据保留全细节(热),远期只保留概要(冷),正是冷热分层保留策略。它和主从复制、事务隔离、索引重建没有任何关系——本质是“近细远粗”的相册管理思路。
Q9. 时序数据规模膨胀会带来哪些直接问题?
- A.存储成本随数据量急剧上升← 正确答案
- B.大跨度时间范围查询变慢← 正确答案
- C.磁盘空间被快速消耗殆尽← 正确答案
- D.数据采样精度自动提升
答案:ABC。数据越多,存储成本越高、磁盘越紧张,全量扫描式的大跨度查询也越慢。膨胀不会自动提升精度,反而迫使系统降低精度来维持成本,所以 D 错误。
Q10. 查询“过去 30 天 CPU 平均使用率”时,下列哪条执行路径最优?
- A.扫描 30 天的全部原始数据表
- B.读取预计算的日级聚合物化视图← 正确答案
- C.用索引逐条随机读取原始记录
- D.每次查询时临时重建整张聚合表
答案:B。连续聚合已把日级均值物化为独立表,查询直接读表即可,扫描量最小。A、C 都要触碰海量原始数据,D 的“每次重建”开销比查询本身还高。这就像直接翻相册而不是翻三万张原片。