物联网2万台设备时序数据瓶颈破解:金仓时序数据库选型与调优实践

发布时间:2026/10/12 4:09:40
物联网2万台设备时序数据瓶颈破解:金仓时序数据库选型与调优实践 刚开始做这个项目的时候说实话压力挺大。我们是一家做物联网平台的企业设备接入量从几千台快速增长到两万台之后原有架构的数据库层一直在报警写入抖动、大屏查询转圈、磁盘空间每天涨一个大头运维同事半夜被叫醒的次数比过去一年都多。当时摆在我面前的选择很简单要么继续在原有数据库上做各种“续命”式调优要么认认真真做一次技术选型换一个真正适合海量时序数据的存储引擎。经过大概三周的选型评测、两周的集群部署和压测调优我们最后落地的是金仓时序数据库。这篇博文把我从选型思路、压测基线、部署细节到踩坑记录、交付运营的整个过程完完整整写出来希望给同样被物联网时序数据折磨的团队一个可以抄作业的样本。1. 物联网企业在时序数据上最容易被击穿的三个瓶颈先说说我们当时遇到的真实问题。拿简单的数字来算一笔账平台接入两万台设备每台设备每5秒上报一次数据单个设备不是只上报一个点而是包括电压、电流、温度、功率四个测点。算下来每秒就是16000个写入点一天大约13.8亿行。这还不算某些高峰期网关数据积压后集中补报瞬时写入量能翻两三倍。这种规模对通用关系型数据库来说已经不是一个“索引建得好不好”的问题而是从存储引擎、写入链路到查询优化器全链路都不匹配的问题。1.1 高并发写入瞬时峰值才是真正的杀手先说写入瓶颈。很多人的直觉是每秒16000个点也不算多一台好点的机器轻松搞定。但这是理想情况实际业务里网关不是均匀上报的采集周期对齐之后每隔几分钟就会形成一个明显的波峰一旦网络抖动或者网关离线积攒的数据会在恢复之后几秒钟内全部涌进来。我们原来的数据库在这种流量模型下出现了两个典型症状一个是并发一高写入重试就开始增多应用日志里全是锁等待和超时另一个是磁盘IO经常瞬间打满p99延迟一度到了800毫秒以上。更麻烦的是通用关系型数据库为了支持事务和复杂查询每次写入都要更新多个二级索引写入放大了好几倍。数据量大的时候这些索引本身就成了负担后台的索引维护和落盘刷页会一直占着CPU业务高峰和后台任务撞在一起系统直接进入恶化循环。1.2 跨设备跨时间聚合看板查询从2秒变成12秒第二个瓶颈是查询。物联网场景的查询和我们平时写的业务查询很不一样。业务查询通常是指定一个主键查一行但物联网看板经常是“查过去15分钟所有设备平均功率”“查某类设备上周每天温度的最大值”这种跨设备、跨时间片段的聚合。这种查询有几个特点第一时间范围往往覆盖大量分区第二过滤条件里除了设备ID还可能有区域、型号、业务类型等多个维度第三结果要按时间窗口聚合。用传统数据库实现这类查询优化器常常没法利用索引只能做全扫描然后再在应用层做聚合结果就是我们的大屏看板加载时间从2秒一路涨到12秒产品经理每来问一次我心里就咯噔一下。这里还有一个很多人忽视的点时序数据的查询天然倾向于“按时间顺序扫描”如果能做预聚合和下推计算性能会好很多。而通用数据库没有这些能力它只会老老实实地把原始数据一行一行读出来。数据量到一定程度之后单纯加机器已经没用了查询的瓶颈往往出在跨节点任务的拆分和汇总协议上。1.3 存储与生命周期管理磁盘成本被忽视的最深坑第三个瓶颈是存储。我们原来的集群为了扛住写入做了多副本每份数据还要保留较长周期。时序数据的特点是只会追加几乎不更新所以日积月累下来磁盘扩容的频率越来越快。我们算过一笔账按原始数据每行大约200字节的存储开销来算每天大约要新增27GB数据一个月就是800多GB。为了容错还要留富余量实际分配往往是高峰值的两倍。更要命的是通用数据库没有灵活的生命周期管理。我们既不能让原始数据一直占着昂贵的高性能磁盘也没有办法自动把老数据压缩成粗粒度聚合数据后归档存储。最后只能用脚本定期手动清理一边要应付业务方查历史数据的需求一边又要忍住删除数据的肉疼这个平衡特别难把握。这三个瓶颈叠在一起已经不是“某个参数没调好”的问题而是架构选型的问题。所以在动了替换念头之后我们用了大概一周时间梳理需求搭出了自己的选型评估框架。2. 技术选型怎么看评估框架与最终选择理由很多人做技术选型容易犯一个错误就是先定产品再找理由。最后做出来的对比表怎么看都是自己想选的产品“恰好”每一项都赢。我自己的习惯是反过来先列评估维度再定权重然后统一用同一套测试脚本去压最后再看哪家最能满足权重最高的那几项。2.1 先把评估维度量化再说产品好坏时序数据库选型我关心六个维度。第一是写入性能尤其是高并发批量写入的吞吐和延迟不能只看理想峰值还要看p99。第二是查询能力包括多维度聚合查询的响应时间、是否能做时间窗口下推、是否支持类SQL语法。第三是存储效率包括压缩比、是否支持冷热数据分层这直接关系到硬件成本。第四是高可用和故障切换能力节点挂掉之后能不能自动恢复副本机制是否成熟。第五是运维复杂度部署、监控、备份、扩容是否方便这决定团队能不能长期接得住。第六是生态兼容性我们现有的应用是Java开发如果数据库能兼容标准SQL和主流驱动迁移成本会低很多。我们给这几项分别设了权重写入性能30%查询能力30%存储效率15%高可用15%运维与生态各占5%。为什么写入和查询各占30%因为这恰恰对应我们前面说的前两个瓶颈是硬指标不能妥协。2.2 当时考虑过的几条路线为什么被排除第一条路线是继续用通用关系型数据库靠分库分表和读写分离硬扛。这条路我们太熟了说白了就是在业务层做各种数据分片逻辑查询一旦跨分片就非常痛苦而且写入放大问题分库也解决不了只是把压力拆到更多机器上了。第二条路线是换开源时序数据库。这类产品在写入性能和压缩率上确实比通用数据库好很多社区也有不少成功案例。但我们考虑的主要顾虑是版本演进速度太快、不同版本的存储格式和接口兼容性不稳定企业内部还有一些数据存在本地的合规要求开源版本在某些高级功能上存在限制出了问题只能自己去啃源码团队的学习成本和管理成本都不小。第三条路线是上整套大数据组件体系。做完调研就排除了因为我们的场景主要是时序数据的写入、查询和聚合并不是复杂的离线计算。那一套体系对运维能力要求高至少需要两三个人专职维护对团队性价比不高。最后一圈走下来我们锁定了金仓时序数据库。核心原因有三个第一它在高并发写入和时序聚合查询上的表现和我们预期高度吻合第二它兼容标准SQL我让后端团队拿现成代码改个连接配置就能跑起一个测试demo迁移成本几乎可以忽略第三它支持分布式集群部署以后设备量再翻一倍我们也能按规划横向扩容不需要把业务拆个稀烂。2.3 选型阶段最值得参考的一份评测脚本这里分享一个我们的做法不要拿官方压测数字直接当成自己的结论。我们当时统一了压测环境在同样的服务器上用同样的数据规模和并发模型分别测试三种产品。数据规模是五百万个时序点写并发32线程每批500条查询模式则模拟看板接口按时间范围分组聚合、按设备ID过滤、按区域过滤三种。最终选型对比表大致长这样评估维度通用关系型开源时序库金仓时序数据库写入吞吐点/秒约2500p99波动大约8000需要自行调优约11800延迟平稳聚合查询响应秒级到十秒级秒级亚秒级存储压缩率差约无压缩良好良好支持冷热分层高可用机制依赖外部组件基本具备内置集群切换运维复杂度高分库分表复杂中高中可集中管理SQL兼容性高弱高迁移成本低这份对比表当然不适用于所有企业但它明确地反映出我们最看重的几个问题——写入吞吐、查询聚合、存储成本——金仓这三个环节都稳。后面的事实也证明当初没有只盯TPC数据、而是自建压测场景的做法确实避免了选型翻车。3. 部署与性能基线三节点集群的具体规划选型定了下一步就是环境搭建。我们最初的计划是先搭一个三节点集群跑预生产验证各项能力之后再平滑切换。整个部署周期花了大概五天其中有两天是在处理环境初始化和参数调优三天是在反复压测线上模拟数据。3.1 节点拓扑和硬件配置参考我们的部署架构是三个数据节点加上应用层原有的服务节点。每个数据节点的硬件规格是32核心CPU、128GB内存数据盘用SSD做RAID10网络走万兆内网。为什么不做RAID5因为时序数据库写入的随机IO特性决定了它对IOPS的要求很高RAID10牺牲一点空间换来写入稳定性和更快的故障重建这笔账很划算。这里多说一句节点数和副本数的设计先想清楚再动工。我们是三个节点数据副本数设置为2这样既能在单个节点故障时保证数据不丢又不会因为副本太多翻倍浪费磁盘。如果业务允许我建议宁可起步规模小一点、副本合理一点也不要图省事把一堆业务塞进一个单机实例里。3.2 环境初始化中容易被忽略的几个参数部署第一步是配置操作系统参数。有几个细节特别容易被忽略文件描述符上限、内存映射上限、交换分区倾向。文件描述符上限至少要调到65535以上否则高并发写入时连接数一上来进程就报Too many open files。内存映射方面的参数建议调到262144这个参数决定了单个进程可以创建多少内存映射区时序查询时会大量使用mmap来读文件不调大查询在压力较高时会出现间歇性失败。至于交换分区策略建议直接设置为0或者非常低的值温度。时序数据库是内存饥渴型的宁可让系统把多余的内存缓存用在数据热区也不要让进程去交换分区上挣扎。时间同步这个问题我要特别拿出来讲。时序数据对时间戳的一致性极其敏感尤其是做跨节点聚合时如果两个节点的时间偏差超过100毫秒查询结果就会出现错位和重复。我们当时用NTP做了同步还不够后来干脆部署了chrony服务把同步周期调到非常短并且在监控告警里加了一项“节点时间偏差超过50ms就报警”。这个坑我们后期真的踩过一次后面在踩坑记录里细说。3.3 用一份压测脚本建立性能基线环境搭好之后不要急着一上来就导业务数据先做压测。我们当时写了一个模拟写入程序用来模拟设备上报和网关补报两种流量模型。模拟程序的核心逻辑可以简化成下面这样import time import random from datetime import datetime, timedelta batch_size 500 thread_num 8 total_points 200_000 def write_batch(thread_id): for i in range(total_points // (thread_num * batch_size)): rows [] base datetime.now() for j in range(batch_size): rows.append({ collect_time: base timedelta(secondsj * 5), device_id: random.randint(1, 20000), region_id: random.randint(1, 20), voltage: round(random.uniform(210, 230), 2), current: round(random.uniform(1, 20), 2), temperature: round(random.uniform(20, 80), 2), power_w: 0.0 }) # 批量提交 insert_batch(rows)压测跑出来的基线要仔细记录之后上线做调优时所有对比都以这份基线为锚点。我们当时的基线是32个并发线程每批500条数据持续写入20分钟平均写入吞吐约11800点/秒p99写入延迟约60毫秒。查询方面在2000万点的数据集上跑“按区域聚合一小时平均功率”的查询响应时间在300到500毫秒之间。这个成绩对于当时的业务规模来说已经非常够用但我们后面又用了大概一周时间优化把吞吐又拉高了一大截第五节会详细写。4. 核心功能落地从表设计到存储策略的完整配置部署完成之后真正的工作才刚刚开始。时序数据库不是装好就完事表结构怎么设计、写入方式怎么选、数据生命周期怎么定每一样都直接影响后续性能。4.1 表结构设计时间戳是主键的一部分我们业务的核心数据表是设备测点表DDL设计长这样CREATE TABLE device_meter ( collect_time TIMESTAMPTZ NOT NULL, device_id BIGINT NOT NULL, region_id INT, voltage DOUBLE PRECISION, current DOUBLE PRECISION, temperature DOUBLE PRECISION, power_w DOUBLE PRECISION ) TAGS (region_id) PARTITION BY RANGE (collect_time);时序表的表和普通业务表最大的区别是时间戳要充分参与排序这是所有时序优化的基础。我们这里的分区键就用了时间范围按天分区一天一个物理分区。这样做的原因很简单查询和清理都能按分区直接裁剪。比如查最近3个小时只需要打开今天这一个分区不用碰其他数据。业务中常见的误区是为了“方便查询”给每个设备建一张表。千万不要这么干设备数量大一点表数量就会爆炸元数据管理就是个灾难。正确做法是所有设备共用一张表用设备ID作为普通过滤条件配合“时间范围分区 设备ID分片”的方案来组织数据。4.2 写入方式能批量绝不单条时序数据库在写入侧对批量操作非常友好我们当时的应用是Java后端在改写写入模块时所有插入都统一封装成了批量接口一次提交500到1000条数据。这里有一个关键点批次大小不是越大越好。我们测试过几个档位500条还是1000条性能差别不大但是单条插入和批量插入的性能差距在十倍以上。看一下我们优化前后的代码变化// 优化前逐条写入 for (DeviceMeter meter : meterList) { insertOne(meter); } // 优化后批量提交 PreparedStatement ps conn.prepareStatement( INSERT INTO device_meter(collect_time, device_id, region_id, voltage, current, temperature, power_w) VALUES (?, ?, ?, ?, ?, ?, ?)); for (DeviceMeter meter : meterList) { ps.setTimestamp(1, meter.getCollectTime()); ps.setLong(2, meter.getDeviceId()); ps.setInt(3, meter.getRegionId()); ps.setDouble(4, meter.getVoltage()); ps.setDouble(5, meter.getCurrent()); ps.setDouble(6, meter.getTemperature()); ps.setDouble(7, meter.getPowerW()); ps.addBatch(); if (count % 500 0) { ps.executeBatch(); } } ps.executeBatch();别小看这个改动它把写入的p99延迟直接降了一个数量级。原理也很容易理解单条写入每次提交都要经历一次网络往返和一次日志落盘批量写入可以把这些开销分摊到一批数据上日志提交次数缩减了500倍。4.3 预聚合与降采样查得快不是靠运气时序查询性能的核心秘密不在于硬件多好而在于能不能用预聚合把“每次现场算”变成“直接查结果”。我们设计了三层数据原始数据、中间聚合数据和长期汇总数据。中间聚合数据按15分钟、1小时两个维度分别聚合主要响应实时看板类的查询。举个例子业务方要看“今天截至现在每个区域的平均电压”如果每次都用原始数据实时聚合哪怕数据库性能再好也是浪费。我们在写入任务里增加了一个定时聚合逻辑每15分钟运行一次把原始数据按区域和时间窗口聚合成结果查询端直接查聚合表响应速度基本就是一次普通索引查询的水平整体延迟降低了一个数量级。长期汇总数据则按天聚合专门用于月末报表、年度分析这类低频场景。这套分层设计的核心思路是原始数据保持最高精度保留180天中间聚合数据保留2年按天汇总的数据保留5年甚至更久。等到原始数据超过保留期限直接删除分片不需要一条一条清理。4.4 生命周期管理和压缩策略数据保留期这个问题业务方一开始是抗拒的。他们的习惯是“所有数据都要留着万一哪天要用”。但实际上过去几年的数据被查的概率低到可以忽略。我们的做法是通过数据库的冷热分层把老数据转成高压缩、低介质的格式而不是直接删除。这里说一个实际的压缩效果。原始数据按行算一天的物理存储大约在27GB左右。启用压缩和分区归档之后同样一天的数据降到了3.5GB左右压缩率超过80%。加上预聚合表替代掉一部分高频查询原始表本身的读取次数少了很多磁盘压力大幅缓解。5. 性能调优实录数字对比和背后的优化原理这节内容是从我们优化笔记里摘出来的全部有实际压测数据支撑照着做基本都能复现。5.1 写入再提速批量大小与并发数的最优组合第一阶段我们用的是“32并发、每批500条”基线是11800点/秒。第二阶段调整了并发数和批量大小把每批提高到1000条并发压到16个线程。为什么不继续用32并发因为并发太高时虽然CPU利用率上去了但锁竞争和网络栈开销也在增加提交延迟并不线性下降。实测下来16并发加1000批量吞吐从11800点/秒提升到了15700点/秒p99延迟从60毫秒降到35毫秒。这说明写入侧真正稀缺的资源往往不是CPU而是日志落盘的顺序性和网络IO的往返次数。再往后我们把每批调到2000条吞吐没有再增长反而因为单批次太大内存缓冲压力增大小概率出现提交超时。所以不要盲目追求“越大越好”要根据自己的数据模型多测几组找到拐点。我们最终设定生产环境的写入参数是“16并发、每批1000条”这个配置在线上运行了几个月写入侧基本是一条平线。5.2 查询优化分区分桶裁剪加聚合下推查询慢绝大多数时候不是因为数据库慢而是因为查询扫了太多不该扫的数据。我们的优化思路有三板斧。第一板斧是时间范围先行。所有查询接口都要求调用方必须携带起止时间并且限制最大跨度。比如实时看板最多查24小时趋势分析最多查7天。从应用层把查询限制在业务合理的范围内数据库的分区裁剪才能真正发挥作用。第二板斧是让聚合下推。不要在应用层循环拿原始数据再聚合要让数据库在存储层完成聚合计算只返回结果。我们在SQL里用GROUP BY time_bucket(...) 这类方式写查询数据在磁盘上读出来之前就已经被裁剪和汇总传输给应用的数据量小到可以忽略。第三板斧是物化聚合表。对于高频看板查询我们建了15分钟和1小时的聚合表由定时任务维护。这个思路有点像数据仓库里“预计算Cube”的做法大幅减少查询时的计算量。优化前后的查询表现对比查询场景优化前优化后全区域实时平均功率15分钟窗口约380ms约95ms单设备近7天温度每日最大值约6.8s约480ms指定区域全天电压聚合曲线约2.1s约280ms全设备禁点日趋势原始表扫码约12s约1.2s这里面提升最明显的是“单设备近7天温度每日最大值”从6.8秒降到480毫秒靠的正是聚合表。原始查询要扫这个设备7天的所有明细数据接近35万行聚合表里只有7行预计算结果速度自然差出一个数量级。5.3 冷热分层的收益最终体现在成本上前面提到过一天27GB降到3.5GB那是启用了压缩的存储效果。冷热分层之后我们进一步把超过30天的原始数据迁移到低成本的归档目录只保留聚合数据在热区。这部分空间节省直接节约了硬件采购预算原来预计半年后要再采购一批磁盘现在这个计划直接取消了。这里要提醒一个问题冷热分层的阈值要结合业务查询习惯来设定不要拍脑袋。我们当时是统计了三个月查询日志发现99%的明细级查询集中在最近7天95%的中层聚合查询集中在一个月内所以才把原始数据热区设置为30天、压缩归档区设置为30到180天。大家可以直接抄这个思路但具体天数要根据自己的用户行为调。6. 交付前的踩坑记录与回滚预案写这篇博文最想分享的其实是这部分内容。部署和调优顺利的时候人人都会真正拉开差距的是踩坑之后能不能快速定位。6.1 时间漂移引发聚合错乱50毫秒引发的血案上线第三周我们接到运营报告某个大区的功率聚合曲线出现“锯齿状”毛刺数据忽高忽低。一开始怀疑聚合任务写错了查了一圈没发现问题。后来排查到节点时间校准情况发现一台节点的系统时间比标准时间快了大约80毫秒导致同一时间窗口的数据被分到了两个聚合桶里。这个问题暴露了两个短板一是我们没有在初始化阶段把所有节点的时间同步做成常态检查二是监控告警里漏掉了节点时间偏差这个指标。修正之后我们在三个节点上统一配置了chrony并在监控系统里增加了一个新的报警规则同时安排运维把节点时钟偏差的检查整理成一遍巡检脚本每分钟自动跑一次。从那之后这类问题再也没出现过。6.2 分区键选择不当导致的扫描放大另一个印象深刻的问题是有段时间看板查询偶尔会突然变慢。我们把慢查询日志捞出来发现一个共同点这些查询都跨了整整一天但实际上业务方只关心早上8点到10点两个小时的区间。问题出在分区键的定义上。我们最初把分区设为按天但查询条件里只传了collect_time没有指定分区前缀。数据库在解析时没办法裁剪到更小的分区只能扫全天数据。后来我们把分区策略改成“时间范围加设备所属大区分桶”的组合并在应用层强制要求查询带上region_id扫描范围大幅缩小慢查询基本消失了。这里有个经验想分享分区键的设计必须跟着查询条件走你要先收集线上查询的过滤条件集合再倒推哪些字段必须成为分区键的一部分。如果建表时拍脑袋后面就是无穷无尽的慢查询。6.3 慢查询占满连接池救火前先限制超时连接池被打满这种问题之前用通用数据库也遇到过。上线初期一个报表因为跨了30天数据直接在聚合表上跑了全范围扫描耗时接近120秒。这个查询占着连接不释放几轮下来连接池被耗尽了所有业务接口全部开始超时。我们的处理是分两步第一步立刻给连接池和数据库侧都配置了查询超时默认30秒超过就强制中断保命要紧第二步找到那个报表SQL强制改成按天粒度查询再由应用层拼接结果。这种“以空间换时间”的妥协方案并不优雅但它胜在稳定尤其是要快速恢复线上服务的时候。6.4 驱动版本与应用兼容性还有一个容易忽略的小坑驱动版本。我们后端当时升级了依赖管理工具把数据库驱动带库带到了新版本结果发现某个时间戳字段的返回值偶发异常。查了半天才发现新驱动对时区处理的行为发生了变更导致时间被转换成了UTC。这个问题的排查链路非常隐蔽靠的是对比应用日志和数据库服务端日志后来锁定是驱动版本不一致回退驱动后一切正常。这块的通用经验是不要轻易在生产环境只升级驱动而不升级服务端升级前一定要在预发环境跑全量回归尤其是涉及时间、日期、精度类字段的用例。6.5 备份恢复演练一定要做别等出事再后悔最后再强调一个组织协同层面的坑备份系统建完放在那里不等于可用。我们用了大概半天时间做了备份配置又安排了一次完整的恢复演练——备份文件倒是存在但恢复出来的数据时间点落后了四五个小时因为备份策略没有覆盖到最近几小时的增量数据。后来我们把备份策略改成了“全量加增量”的组合全量备份每天一次增量备份每15分钟一次并且每个月自动触发一次恢复演练写进运维SOP。这不是金仓特有的事情任何数据库系统都应该这么做但我在这个项目里深刻体会到时序数据库数据量大恢复耗时更长提前演练的价值比想象中更大。写在最后的几条经验这个项目做下来我最想留给自己团队的一句话是时序数据库选型和落地的核心不是找到一个“最好的数据库”而是找到一个与业务特征匹配的存储引擎并且把表结构、写入模型、聚合策略、生命周期管理都当成这个引擎的一部分去整体设计。另外如果一定要给后来者一个优先级建议我会说先管好数据生命周期再做细粒度性能优化。数据量不可控时任何查询优化和磁盘优化都是暂时的数据量被控制在合理范围很多性能问题会自动消失。我们这次能把查询延迟从秒级降到百毫秒级一大半功劳要记在压缩和冷热分层头上。最后分享一个小操作技巧上线初的监控不要只盯CPU和内存多盯p99延迟、慢查询数和连接池使用率。CPU平稳但p99漂移往往意味着系统内部已经出现了资源争抢早发现早处理能避免很多半夜电话。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询