InfluxDB(五)——分片、压缩与降采样三大核心技术
目录一、先搞懂分片、压缩、降采样是什么1. 分片Shard—— 把大文件切成小块1.1 为什么要分片1.2 分片是什么1.3 分片的能力与不足2. 压缩 —— 数据库默默帮你节省空间2.1 压缩解决了分片的什么问题2.2 压缩的原理3. 降采样Downsampling—— 生产环境的核心优化3.1 MySQL的做法3.2 InfluxDB 的做法Flux 落盘第一步写降采样 Task结果持久化第二步查询聚合结果3.3 核心区别对比3.4 降采样解决了压缩的什么问题3.5 降采样的能力与不足3.6 生产标准方案双桶架构4.定时任务Task二、三者关系分片 → 压缩 → 降采样在前面的章节中我们已经成功通过 Java 实体类 AirSensorData 将空气传感器数据写入 InfluxDBmeasurement 为 airSensors包含 sensor_idtag、temperature 、humidity、co 等指标数据格式稳定、接入通畅。也参照官方文档使用 Task 定时同步过空气传感器数据模拟时序数据每日的上送。但进入生产环境必须解决三大问题海量时序数据占用空间爆炸查询越来越慢历史明细数据无需永久保存只需保留统计结果原始数据存在异常值、缺失值需要自动清洗这三大痛点是时序数据库从 “能用” 走向 “生产可用” 的必经之路。而 InfluxDB 恰好提供了一套原生、高效、零外部依赖的解决方案它由四个关键技术组成分片、压缩、降采样、Task 定时任务。InfluxDB 提供了三把“手术刀”分片Sharding、压缩Compaction、降采样Downsampling。它们分别从存储结构、数据编码、数据粒度三个维度下手互相配合缺一不可。在动手写 Task 之前我们得先弄清楚这三把刀到底在切什么一、先搞懂分片、压缩、降采样是什么1. 分片Shard——把大文件切成小块1.1 为什么要分片分片顾名思义就是把超大的整体数据按规则拆分成多个小分片分散存储在不同节点。例如传统的单机关系数据库MySQL默认没有原生分片能力所有业务数据最终都落在单节点单个 / 少数大数据文件里会出现三大致命问题查询慢查 1 天的数据也要扫描整个大文件删不掉删除过期数据需要遍历整个文件做标记删除速度极慢且会产生大量碎片扩不动受单节点磁盘、CPU、内存限制无法分布式存储单节点容量有上限分片的出现完美解决了这些问题它把连续的时间流切成一个个独立的、不可修改的文件块Shard每个 Shard 对应一个固定的时间窗口如 1 天分片的本质给数据打上 时间标签让数据有了生命周期和位置属性。1.2 分片是什么InfluxDB是时序数据库所有数据都按时间组织分片是InfluxDB存储引擎的按时间切割数据的物理存储最小单元单位。写入数据时InfluxDB不会吧所有数据塞进一个巨大的文件中而是按照时间范围切成一个个独立的“文件块”这就是分片分片是按时间范围切分的物理存储单元。——所有数据写入时就被自动归入对应的时间分片InfluxDB 不需要你手动指定每个分片覆盖多长时间它会根据Bucket 的保留策略自动计算Bucket 保留时间默认Shard Group Duration≤ 2 天1 小时2 天 ~ 6 个月1 天≥ 6 个月7 天❌误区Shard 越小越灵活但也不是越小越好。过多的 Shard 会拉低查询效率OpenShard 开销大需要根据数据量取舍。我的保留策略是30天-默认1天通过 Java 客户端Measurement(name airSensors)写入数据时InfluxDB 内部做了这些事① 确定归属Bucket→ 数据写入指定的 Bucket比如 echola-bucket→ 这个 Bucket 的保留策略决定了Shard Group Duration比如保留30天 → 默认1天② 按时间路由到Shard Group→ 时间戳 2026-05-01T01:08:21 的数据→ 归属于 Shard Group 2026-05-01覆盖这一天 00:00:00 ~ 23:59:59③ 进入该 Shard Group 的唯一Shard单机版→ 这个 Shard 负责存储 2026-05-01 这整天的所有数据→ 目录结构大概是这样/data/echola-bucket/airSensors/123/ ← shard ID123 ├── 000000001-000000001.tsm ← TSM 数据文件 ├── index/ ← TSI 索引文件 └── wal/ ← WAL 预写日志每个 Shard 内部包含TSM 文件列式存储的数据文件WAL 文件预写日志先写这里再刷入 TSM索引文件TSI加速按 tag 查询时间轴 ───────────────────────────────────────────── | Shard 1 | Shard 2 | Shard 3 | | 7天前~6天前 | 6天前~5天前 | 5天前~4天前 |用一个简单的例子有 100GB / 天的原始数据30 天就是 3TB。不分片这 3TB 数据存在 1 个大文件里分片分成 30 个 1 天的 Shard这 3TB 数据存在 30 个小文件里总大小还是 3TB一分不少这就是分片留下的第一个、也是最大的问题它解决了 数据怎么放 的问题但完全没有解决 数据占多大 的问题1.3 分片的能力与不足说明核心能力1. 过期时以Shard 为最小单元整块删除瞬间释放磁盘空间不会像 MySQL DELETE 那样锁表、产生碎片2. 查询时只扫描目标时间范围内的 Shard避免全表扫描3. 支持冷热数据分离热数据存 SSD冷数据存 HDD主要不足1.不分担查询压力数据量越大单个 Shard 内部扫描依然越慢。2.数量过多有副作用Shard 越碎查询时要打开的文件句柄越多反而拖慢性能那如何解决数据占空间大的问题呢——压缩2. 压缩 —— 数据库默默帮你节省空间当数据写入 InfluxDB 时完整的流程是数据到达根据时间戳找到对应的 Shard数据先写入 WAL 日志未压缩数据进入内存缓存Cache当 Cache 满了达到 25MB默认 或 10 分钟无写入时数据被刷入磁盘自动执行压缩生成一个 TSM 文件删除对应的 WAL 日志段每个 Shard 都是一个完全独立的微型数据库拥有自己完整的存储栈一个Shard的完整结构 ├── WAL目录预写日志 ├── Cache内存缓存 └── TSM文件目录磁盘持久化数据 ├── 000000001-000000001.tsm ├── 000000002-000000002.tsm └── ...也就是说所有持久化到磁盘的 Shard 文件天生就是压缩后的。不存在 未压缩的 Shard 这种东西。当Shard中的数据变成“冷Shard”后数据一旦从Cache写到磁盘中形成只读的TSM文件。随后InfluxDB会在后台自动对这些只读文件进行多级压缩与合并以优化存储空间和查询性能官方定义冷 Shard 的精确触发条件当一个 Shard最后一次写入操作发生后经过compact-full-write-cold-duration时间默认 4 小时InfluxDB 会将其标记为 冷 Shard并触发全量压缩以目前的原始桶echola-bucket为例子Shard Group Duration1 天保留策略30 天cache-snapshot-write-cold-duration默认 10 分钟compact-full-write-cold-duration默认 4 小时那么一个 Shard 的完整生命周期是时间点事件状态数据位置压缩阶段压缩比2026-05-08 00:00:00Shard 创建开始接受写入热 Shard内存 CacheWAL无0:12026-05-08 00:25:00Cache 达到 25MB第一次刷盘热 Shard内存 CacheWAL1 个 TSM 文件实时压缩~5:12026-05-08 23:59:59Shard Group 时间窗口结束温 Shard内存 CacheWAL 多个 TSM 文件实时压缩~5:12026-05-09 00:09:59连续 10 分钟无写入温 Shard多个 TSM 文件实时压缩~5:12026-05-09 00:10:00触发冷 Cache 快照最后 1MB 数据刷盘清空 Cache 和 WAL温 Shard所有数据都在磁盘的多个 TSM 文件中实时压缩~5:12026-05-09 04:09:59连续 4 小时无写入即将变冷多个 TSM 文件实时压缩~5:12026-05-09 04:10:00触发全量压缩合并所有 TSM 文件冷 Shard1 个大的最优压缩 TSM 文件全量压缩~10:1~100:12026-06-07 00:00:00保留期结束Shard 被物理删除已删除---可以看见即使 Shard Group 的时间窗口已经结束InfluxDB 仍然会等待 4 小时确保不会再有任何迟到的数据写入然后才会将其标记为只读并进行全量压缩2.1 压缩解决了分片的什么问题压缩是纯数据编码方式的优化它利用时序数据的强冗余特性在不丢失任何信息的前提下把每个数据点的大小压缩到原来的 1/10~1/100回到刚才的例子100GB / 天的原始数据分成 30 个 1 天的 Shard未压缩总大小 3TB压缩后总大小 300GB平均压缩比 10:1压缩在分片的基础上直接把存储成本降低了一个数量级。更重要的是压缩不仅不影响性能反而会同时提升写入和查询性能写入时需要写入磁盘的数据量减少了 90%磁盘 IO 瓶颈大幅缓解查询时需要从磁盘读取的数据量减少了 90%查询速度提升 10 倍以上2.2 压缩的原理InfluxDB 的 TSM 文件针对时序数据做了专门优化时间戳使用 Delta 编码存储相邻时间戳的差值而非完整时间戳。数值使用 XOR 压缩存储相邻数值的异或结果对于变化平缓的传感器数据压缩效果极佳。但注意自动压缩只能减少体积不能减少数据条数。如果你的传感器每秒上报一条数据压缩后依然是每秒一条查询依然会慢。无论怎么压缩数据点的总数是不变的。以空气传感器的时间戳为例假设一个 TSM 块包含 1000 个连续的空气传感器数据点上报间隔为 10 秒时间戳序列纳秒 T0: 1715155200000000000 (2026-05-08 14:00:00) T1: 1715155210000000000 (T0 10秒) T2: 1715155220000000000 (T1 10秒) T3: 1715155230000000000 (T2 10秒) ... T999: 1715165190000000000 (T998 10秒)TSM 文件中的实际存储内容[块头] 第一个完整时间戳: 1715155200000000000 (8字节) 第一个Delta值: 10000000000纳秒 (4字节) Delta-of-Delta值: 0 (1比特) 重复次数: 998次 (2字节) [数据部分] ... (数值和标签数据)惊人的计算结果1000 个时间戳总共只需要存储8 4 0.125 2 14.125 字节平均每个时间戳14.125 字节 ÷ 1000 0.014125 字节 0.113 比特压缩比(1000 × 8 字节) ÷ 14.125 字节 ≈566:1压缩已经把每个数据点压到了物理极限时间戳从 8 字节压缩到平均 2 比特压缩比 32:1浮点数从 8 字节压缩到平均 1.5 字节压缩比 5:1标签从几十字节压缩到几个比特压缩比 1000:11000 台设备每秒上报 1 个点一天就是 8640 万个点一年就是 315 亿个点。即使每个点只占 1 字节一年也需要 31.5GB10 年就是 315GB。对于需要保留 5 年、10 年甚至更久历史数据的行业如环保、电力、工业来说即使有压缩长期数据量仍然会大到无法承受。这就是压缩留下的问题它解决了 每个点占多大 的问题但完全没有解决 有多少个点 的问题。InfluxDB 存储引擎在后台对已关闭只读的 Shard 自动执行高比例压缩。时序数据时间连续、值相近压缩比极高通常 1:10 甚至更高。说明核心能力1.全自动、零配置静默削减磁盘成本。2.压缩比极高时序列式存储天然有利于编码压缩算法主要不足1.只减体积不减行数一秒一条的数据压缩完依然是一秒一条扫描开销丝毫未降。2.只在后台对冷数据生效刚写入的热分片体积较大存在“写入放大”阶段生产环境压缩最佳实践永远不要关闭压缩InfluxDB 2.x 默认开启压缩关闭它没有任何好处不要使用通用压缩算法TSM 的专用压缩算法比 GZIP、LZ4 效率高得多定期执行 Compaction合并小的 TSM 文件提升压缩比和查询性能查看压缩效果使用influxd inspect report-db命令查看每个桶的实际压缩比由于保留策略是30天30 天之前的所有数据会被 InfluxDB 自动、永久、物理删除。InfluxDB 的过期数据清理并非逐行DELETE而是以Shard为最小单元进行整块删除——当整个 Shard 内所有数据的时间戳都超出保留期限存储引擎直接删除该 Shard 对应的 TSM 文件和索引释放磁盘空间。这种机制效率极高但也意味着一旦过期数据不可恢复对于真实的业务需求只保留30天显然是远远不够的一般查询都是近一天、近7天、近一月和近一年等。但又不能设置为数据永久保存海量的数据名字会快速撑满磁盘随着时间推移查询跨度稍大就会出现查询缓慢、IO压力飙升的问题这就引申出来了——降采样3. 降采样Downsampling—— 生产环境的核心优化降采样的核心逻辑是“丢明细、留统计”—— 用低粒度的聚合数据如每小时平均温度代替高粒度的原始数据如每秒一次的温度读数从而大幅减少数据条数提升查询效率。3.1 MySQL的做法传统MySQL统计一天内的各个设备每小时的平均温度是将一天的所有温度数据取出来把一天的所有温度数据取出来再按照小时分组求平均。SELECT sensor_id, DATE_FORMAT(record_time, %Y-%m-%d %H:00:00) AS hour, AVG(temperature) AS avg_temp, MAX(temperature) AS max_temp, MIN(temperature) AS min_temp FROM air_sensor_data WHERE record_time 2026-05-01 00:00:00 AND record_time 2026-05-02 00:00:00 GROUP BY sensor_id, hour;在数据量少时这样查询是没有问题但是有些设备的上送是按照s级计算当数据量越来越多缺点就暴漏出来了查询越来越慢1 个传感器每2s上送一次一天 43,200行100 个传感器一天 432w 行。查一周需要扫描 3024w 万行接口响应必定超时一般要么就是写定时任务脚本把原始数据按照小时级算出来统计值将其存储到另一张表直接去查询中间表本质上就是“降采样”但需要自行写代码管理定时调度处理失败重试3.2 InfluxDB 的做法Flux 落盘而InfluxDb的降采样就可以原生解决这些问题不再需要外部脚本。它的实现方式就是上一节提到的TaskaggregateWindow第一步写降采样 Task结果持久化先创建一个聚合桶echola-agg-bucketTask→Create Task→ 选择New Task在Task中创建一个airSensor-Hour-AGG的任务每小时聚合一次算 mean/max/min/num创建成功后激活Active任务TaskRun Task后如果执行成功左侧会显示✅悬停在Tsak点点击进入后即可点击View Logs查询运行日志信息option task {name: AirSensor-Hour-AGG, every: 1h, offset: 1m} // 原始数据源 data from(bucket: echola-bucket) | range(start: -8h) | filter(fn: (r) r._measurement airSensors) | filter( fn: (r) r._field temperature or r._field humidity or r._field co, ) // 四个聚合并行计算最后合并写入 union( tables: [ // 1. 平均值 data | aggregateWindow(every: 1h, fn: mean, createEmpty: false) | map(fn: (r) ({r with _field: r._field _mean})), // 2. 最大值 data | aggregateWindow(every: 1h, fn: max, createEmpty: false) | map(fn: (r) ({r with _field: r._field _max})), // 3. 最小值 data | aggregateWindow(every: 1h, fn: min, createEmpty: false) | map(fn: (r) ({r with _field: r._field _min})), // 4. 数据条数判断是否有数据缺失 data | aggregateWindow(every: 1h, fn: count, createEmpty: false) | map(fn: (r) ({r with _field: r._field _num})), ], ) | set(key: _measurement, value: airSensors_hourly) | set(key: agg_type, value: 1h) | to(bucket: echola-agg-bucket)这里的时区是| range(start: -8h)而不是| range(start: -1h)是由于InfluxDB存储是按照UTC时间与北京时间有8小时偏移。注意本文Flux查询使用的UTC时间先忽略时差问题后面再细讲。对于MySQL方案InfluxDB有三个优势存储与计算都在数据库内部完成——不需要把 3024w 行的数据通过网络拖到代码中计算直接在存储引擎上聚合执行速度快几十倍按时间自动忽略过期数据不读死数据——分片机制让InfluxDB在的数据时天然跳过不属于查询范围的Shard而MySQL需要靠索引进行扫描聚合结果自动落入新的Bucket不同策略独立管理——原始桶30天自动删除聚合桶保留1年。这条“自动流水线”在 MySQL 里要写一堆脚本和 Cron 才能勉强模拟。Task执行之后聚合桶里存储的数据就是数据格式大概是这样_measurementsensor_id_field_value_timeairSensors_hourlyTLM0100temperature_mean24.62026-05-01T12:00:00ZairSensors_hourlyTLM0100humidity_mean35.22026-05-01T12:00:00ZairSensors_hourlyTLM0100co_mean0.472026-05-01T12:00:00ZairSensors_hourlyTLM0101temperature_mean32.02026-05-01T12:00:00Z...............第二步查询聚合结果查某个传感器全天逐小时温度均值from(bucket: echola-agg-bucket) | range(start: 2026-05-01T00:00:00Z, stop: 2026-05-02T00:00:00Z) | filter(fn: (r) r._measurement airSensors_hourly) | filter(fn: (r) r.sensor_id TLM0100) | filter(fn: (r) r._field temperature_mean)可以看到直接查询出每个小时的平均温度如果同时查询温度均值最大值最小值需要使用Pivot展开from(bucket: echola-agg-bucket) | range(start: 2026-05-07T00:00:00Z, stop: 2026-05-08T00:00:00Z) | filter(fn: (r) r._measurement airSensors_hourly) | filter(fn: (r) r.sensor_id TLM0100) | filter(fn: (r) r._field temperature_mean or r._field temperature_max or r._field temperature_min) | pivot( rowKey: [_time], columnKey: [_field], valueColumn: _value )pivot之后的结果和MySQL的SELECT几乎一样一行里同时有temperature_mean、temperature_max、temperature_min三列返回的数据格式大概是数据格式_timetemperature_meantemperature_maxtemperature_min2026-05-01T12:00:00Z50.250.948.72026-05-01T13:00:00Z52.553.651.42026-05-01T14:00:00Z53.254.252.13.3 核心区别对比MySQLInfluxDB聚合时机查询时现算读时聚合Task 提前算好写时聚合数据落盘不落盘每次查都算算完写入聚合桶永久保留扫描量每次扫原始表的全量明细只扫聚合桶的统计记录查询速度数据量大时明显变慢始终很快因为扫描量固定历史数据原始数据删除后无法回溯聚合数据保留期独立不受原始桶影响3.4 降采样解决了压缩的什么问题降采样是纯数据密度的优化它通过聚合统计把高频的原始数据变成低频的统计值直接减少数据点的数量还是刚才的例子1000 台设备每秒 1 个点原始数据8640 万点 / 天30 天 25.9 亿点压缩后 300GB小时级聚合2.4 万点 / 天30 天 72 万点压缩后 30MB天级聚合1000 点 / 天1 年 36.5 万点压缩后 15MB年级聚合1000 点 / 年10 年 1 万点压缩后几百 KB降采样在压缩的基础上又把长期存储成本降低了 3~4 个数量级3.5 降采样的能力与不足降采样的本质缺陷它是有损操作只能处理历史数据降采样是唯一会丢失信息的技术它永久丢失了原始数据的精度无法恢复它只能处理已经结束的时间窗口无法处理正在写入的实时数据这就是为什么我们必须保留 30 天的原始数据用于最近的故障排查和异常点溯源。而 30 天以上的数据我们只关心趋势不关心每个单独的点。降采样是把高精度的明细数据如每秒一条按时间窗口聚合成低精度的统计数据如每小时一条均值、最大值。说明核心能力1.数据条数数量级减少查询速度飞升。2.可永久保留统计趋势让“查近一年”成为可能。3.可在聚合时顺便清洗异常值主要不足1.不省存储只省条数从源头上减少了行数间接省空间但它本身不负责编码压缩。2.统计信息不可还原算完均值就丢了原始明细想追溯异常瞬间必须回查原始桶。3.窗口边界敏感Task 的时间窗口和延迟窗口如果设计不好会重复写入或漏数据3.6 生产标准方案双桶架构生产环境绝对不会只用一个桶 30 天保留而是用而是用「原始桶短保留 聚合桶长保留」的双桶标准方案。原始桶echola-bucket:保留 30 天明细数据用于最近的精细查询、故障排查保留策略 30 天Shard Group Duration 1 天。聚合桶echola-agg-bucket保留 1 年甚至更久的聚合数据用于长期趋势分析保留策略 1 年Shard Group Duration 7 天。表格更直观一点空气传感器场景桶名称用途保留策略 (RP)Shard Group Duration写入模式查询场景echola-bucket原始明细数据30 天1 天设备直写 / 网关批量写入最近 30 天故障排查、异常点溯源、分钟级精细分析echola-agg-bucket多层聚合数据3 年7 天仅由系统 Task 自动写入禁止人工写入1 个月3 年趋势分析、报表生成、大屏展示原始桶1 天 Shard高频写入场景下1 天 Shard 能最大化写入性能且 30 天保留期结束后整个 Shard 文件可直接物理删除回收磁盘速度最快聚合桶7 天 Shard聚合数据写入量极低约为原始数据的 1/100~1/10007 天 Shard 能减少元数据数量提升长期查询性能3 年聚合保留期满足环保、工业等行业对历史数据的合规要求如需更长可延长至 5 年禁止人工写入聚合桶保证数据一致性所有聚合数据必须由系统 Task 自动生成降采样的聚合策略设计以空气传感器数据为例我们可以设计多层聚合一般都是小时、天、周、月、年聚合粒度时间窗口保留时长核心聚合指标典型应用小时级1 小时3 年平均值、最大值、最小值、中位数、95 百分位数、数据点数日趋势分析、小时峰值统计天级1 天北京时间 00:00~23:593 年平均值、最大值、最小值、中位数、95 百分位数、日均值、超标时长日报表、月度对比周级1 周周一至周日3 年周平均值、周最大值、周最小值、超标天数周报表、季度趋势月级1 自然月3 年月平均值、月最大值、月最小值、超标天数、优良天数月报表、年度考核年级1 自然年永久年平均值、年最大值、年最小值、年度优良率年报、年度对比分析上面已经有了小时级的聚合那现在就可以直接使用上一聚合结果计算无需直接读取原始数据大幅提升性能天极聚合基于小时级数据每天凌晨 00:05 执行周级聚合基于天级数据每周一凌晨 00:10 执行月级聚合基于天级数据每月 1 日凌晨 00:15 执行年级聚合基于月级数据每年 1 月 1 日凌晨 00:30 执行关键设计原则时区对齐所有聚合窗口必须使用北京时间 (UTC8)而非默认 UTC 时间这是国内生产环境最容易踩的坑增量聚合Task 只处理上一个完整的时间窗口避免重复计算和数据覆盖指标精简每个粒度只保留业务真正需要的指标不要盲目聚合所有可能的指标数据完整性校验每个聚合结果必须包含num字段用于判断原始数据是否完整天聚合Task直接基于airSensors_hourly数据生成保持了union多聚合并行、_field后缀命名的一致逻辑option task {name: AirSensor-Daily-AGG, every: 1d, offset: 5m} // 小时聚合数据源从聚合桶读取 hourly_data from(bucket: echola-agg-bucket) | range(start: -1d) | filter(fn: (r) r._measurement airSensors_hourly) | filter( fn: (r) r._field ~ /.*_mean$/ or // 匹配所有_mean后缀字段 r._field ~ /.*_max$/ or // 匹配所有_max后缀字段 r._field ~ /.*_min$/ or // 匹配所有_min后缀字段 r._field ~ /.*_num$/, // 匹配所有_num后缀字段 ) // 四个聚合并行计算基于小时聚合结果最后合并写入 union( tables: [ // 1. 对小时_mean求天平均 hourly_data | filter(fn: (r) r._field ~ /.*_mean$/) | aggregateWindow( every: 1d, fn: mean, createEmpty: false, location: timezone.location(name: Asia/Shanghai) // 北京时间对齐 ) | map(fn: (r) ({r with _field: r._field _daily})), // 2. 对小时_max求天最大 hourly_data | filter(fn: (r) r._field ~ /.*_max$/) | aggregateWindow( every: 1d, fn: max, createEmpty: false, location: timezone.location(name: Asia/Shanghai) ) | map(fn: (r) ({r with _field: r._field _daily})), // 3. 对小时_min求天最小 hourly_data | filter(fn: (r) r._field ~ /.*_min$/) | aggregateWindow( every: 1d, fn: min, createEmpty: false, location: timezone.location(name: Asia/Shanghai) ) | map(fn: (r) ({r with _field: r._field _daily})), // 4. 对小时_num求天总和总数据条数 hourly_data | filter(fn: (r) r._field ~ /.*_num$/) | aggregateWindow( every: 1d, fn: sum, createEmpty: false, location: timezone.location(name: Asia/Shanghai) ) | map(fn: (r) ({r with _field: r._field _daily})), ], ) | set(key: _measurement, value: airSensors_daily) | set(key: agg_type, value: 1d) | to(bucket: echola-agg-bucket)以此内推聚合同中可以有多层聚合数据echola-agg-bucket ├── airSensors_hourly # 小时级聚合measurement ├── airSensors_daily # 天级聚合measurement ├── airSensors_weekly # 周级聚合measurement ├── airSensors_monthly # 月级聚合measurement └── airSensors_yearly # 年级聚合measurement这样当查询 “近一年的温度趋势” 时直接从聚合桶查小时级数据数据量从 “365 天 × 24 小时 × 3600 秒 3153 万条” 降到 “365 天 × 24 小时 8760 条”查询速度提升数千倍。如果使用从聚合桶查月级数据单台设备的数据量从原始的365天 × 24小时 × 3600秒 3153万条骤降至12条查询速度提升260 万倍。可以看到明显提升了数据查询的效率二、三者关系分片 → 压缩 → 降采样用一句话总结分片决定数据如何存储压缩让存储空间更小降采样让数据条数更少Task 让这一切自动执行它们配合起来才能完美解决生产三大问题分片→ 快速删除过期明细不卡顿压缩→ 降低存储成本降采样→ 保留历史统计结果丢弃无用明细Task→ 定时执行降采样 数据清洗一个数据点的完整生命周期一个温度数据点从传感器产生到最终归档的完整旅程传感器在 2026-05-08 14:35:22 上报温度 25.3℃数据进入 InfluxDB根据时间戳分配到 2026-05-08 这个 Shard数据在内存中缓存当 Cache 满了后刷入磁盘自动压缩从 16 字节压缩到约 2 字节2026-05-08 15:00小时级聚合 Task 执行读取 14:00~15:00 的压缩数据计算出 14 点的平均温度 25.1℃平均温度写入聚合桶的 2026-05-08Shard自动再次压缩2026-05-09 00:05天级聚合 Task 执行读取前一天的 24 个小时级数据计算出日平均温度 24.8℃日平均温度写入聚合桶的 2026-05Shard自动再次压缩30 天后原始桶的 2026-05-08Shard 被整个物理删除回收磁盘空间3 年后聚合桶的小时级数据被删除只保留天级及以上数据10 年后只保留年级聚合数据用于历史对比用一个例子来说明以现在的空气传感器项目为例假设1000 台设备每台每秒上报 1 个数据点原始数据写入量100GB / 天原始桶保留 30 天聚合桶保留 3 年技术组合原始桶 30 天大小聚合桶 3 年大小总存储量节省比例什么都不做3TB1095TB1098TB0%只有分片3TB1095TB1098TB0%分片 降采样3TB10GB3.01TB99.7%分片 降采样 压缩300GB1GB301GB99.97%看到差距了吗降采样把总数据量从 1098TB 降到了 3TB减少了 99.7%压缩又在这个基础上再减少了90%从 3TB 降到了 300GB这就是为什么即使有了分片和降采样压缩仍然是必不可少的 —— 它能在降采样的基础上再给你带来10 倍以上的存储节省。技术核心作用数据损失适用范围存储节省分片生命周期管理 精准查询无所有数据0%降采样减少长期数据点数有丢失精度历史数据90%~99%压缩减少每个数据点的大小无所有数据90%~99%三者关系一句话分片管删除效率、压缩管磁盘体积、降采样管查询速度。只有一个生产必然在某个维度上失控三者齐了才构成完整无短板的生产存储方案