摄取1亿次心跳:在不破产的情况下扩展可穿戴技术
Source: Dev.to
“Continuous”的数学
让我们实事求是地看数字。如果设备每秒发送一次心跳负载(1 Hz):
- 1 用户 = 86,400 次写入/天。
- 1,000 用户 = 86.4 百万次写入/天。
负载大小:即使是一个只有 100 字节的 JSON 包,也意味着每天数 GB 的摄入流量。
标准关系型数据库(如 MySQL 或普通的 Postgres)针对事务完整性(ACID)进行优化,而不是每秒处理数百万条小写入。B‑Tree 索引的开销本身就会严重削减写入吞吐量。
策略 1:停止 “喋喋不休” 协议(批处理)
第一个错误是把可穿戴设备当成聊天应用来使用。不要为每一次心跳都打开一个 WebSocket 或 API 请求。网络开销(TCP 握手、HTTP 头部)往往比实际数据还大。
解决办法: 在设备端进行缓冲。理想情况下,设备应收集 60 秒的数据后一次性发送压缩块。
在后端,避免一次只 INSERT 一行数据,而是使用批量插入。下面是一个虽然写得不够优雅但有效的 Node.js 示例,展示了两者的差别:
// DON'T DO THIS
data.points.forEach(async (point) => {
await db.query('INSERT INTO heartbeats VALUES (...)', [point]); // RIP Database
});
// DO THIS
const format = require('pg-format');
const values = data.points.map(p => [p.userId, p.value, p.timestamp]);
const query = format('INSERT INTO heartbeats (user_id, bpm, time) VALUES %L', values);
await db.query(query); // One network round trip, one transaction
这个简单的改动可以将你的 IOPS(每秒输入/输出操作次数)降低约 50–100 倍。
Strategy 2: 使用时间序列数据库(TSDB)
我喜欢 Postgres。但对于原始指标来说,你需要一种 仅追加 且按时间顺序组织的数据处理方式。
像 TimescaleDB(基于 Postgres)或 InfluxDB 之类的工具在这里就是救星。它们使用 “hypertables” 或专门的存储引擎,将数据按时间块进行分区。
这怎么省钱?压缩。 时间序列数据高度重复:
{ "time": "10:00:01", "bpm": 70 }
{ "time": "10:00:02", "bpm": 70 }
{ "time": "10:00:03", "bpm": 71 }
TSDB 使用 delta‑of‑delta 压缩。它们不是每次都存储完整的时间戳和完整的整数,而是只存储它们之间的微小变化。我们已经看到,仅仅将普通的 Postgres 表切换为压缩的 hypertables,就能让存储成本下降 90 %。
如果你想阅读更多关于数据库选择的技术深度解析,请查看我的 技术指南和教程。
策略 3:下采样的艺术(汇总)
Here is the hard truth: Nobody needs second‑by‑second resolution from 3 months ago. A doctor might need granular data for yesterday’s arrhythmia event, but for a trend report from last year they only need the daily average, min, and max.
事实是: 没有人需要三个月前的秒级分辨率。 医生可能需要昨天心律失常事件的细粒度数据,但对于去年的趋势报告,他们只需要每日的平均值、最小值和最大值。
The Fix: Continuous aggregates. Don’t calculate averages on the fly (that’s slow). Pre‑calculate them as data comes in and drop the raw data later.
解决方案: 连续聚合。不要实时计算平均值(那很慢)。在数据进入时预先计算,并在之后删除原始数据。
In SQL (Timescale syntax), it looks like this:
-- Create a view that updates automatically
CREATE MATERIALIZED VIEW hourly_heartrate
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', time) AS bucket,
user_id,
avg(bpm) AS avg_bpm,
max(bpm) AS max_bpm
FROM heartbeats
GROUP BY bucket, user_id;
-- Add a retention policy to delete raw data after 7 days
SELECT add_retention_policy('heartbeats', INTERVAL '7 days');
Now, your storage doesn’t grow infinitely. You keep high‑resolution data for a week (for immediate alerts) and low‑resolution data forever (for long‑term trends).
现在,您的存储不会无限增长。您可以保留一周的高分辨率数据(用于即时警报),并永久保留低分辨率数据(用于长期趋势)。
结论
为“持续监控”构建不仅仅是写代码;它还涉及物理学和经济学。
- Buffer 在客户端进行缓冲以减少网络请求。
- Batch 在服务器端批处理以节省 IOPS。
- Compress 使用时序数据库进行压缩以节省磁盘空间。
- Downsample 对旧数据进行降采样以保持数据库快速。
虽然过度工程很诱人,但严格的数据生命周期策略往往比华丽的 Kubernetes 集群更有价值。
祝编码愉快。 🚀