停止在可穿戴数据中淹没:使用 DuckDB 和 Apache Arrow 构建统一的健康数据湖

发布: (2026年1月20日 GMT+8 09:10)
6 min read
原文: Dev.to

Source: Dev.to

wellallyTech

如果你是 Quantified Self(自我量化)运动的粉丝,你一定懂得其中的痛点:你的 Oura Ring 记录睡眠,你的 Whoop 分析恢复情况,而你的 Garmin 记录跑步数据。但当你想回答一个简单的问题,比如 “我的训练负荷如何影响我的 REM 睡眠?” 时,却会陷入 CSV 导出和不兼容的 JSON 架构的噩梦中。管理异构可穿戴设备数据的 ETL pipeline 是对数据工程师耐心的终极考验。

在本指南中,我们将拆解这些数据孤岛。我们将使用 DuckDBApache ArrowTypeScript 构建一个高性能、以本地为先的数据湖。完成后,你将拥有一个统一的存储,能够在毫秒级别对所有设备的数据执行复杂的 OLAP 查询。对于那些希望获得更具生产级别的模式和高级健康数据同步的读者,我强烈建议查看 WellAlly Blog 中的深度解析。

架构:从混沌到洞察

健康数据工程中最大的挑战是 数据标准化。Oura 可能将心率变异性(HRV)报告为平均值,而 Whoop 则提供原始的时间序列。我们的流水线充当翻译层,将这些数据统一展平并存入基于 Parquet 的存储。

数据流图

graph TD
    A[Oura API / JSON] -->|Normalize| D[Unified Schema]
    B[Whoop API / JSON] -->|Normalize| D
    C[Garmin Fit Files] -->|Extract| D
    D -->|Arrow IPC| E{DuckDB‑Wasm}
    E -->|Persistent Storage| F[(OPFS / Parquet)]
    G[Streamlit Dashboard] -->|SQL Query| E
    E -->|Visuals| G

前置条件

要跟随本教程,您需要:

  • Node.js/TypeScript – 用于规范化逻辑。
  • DuckDB‑Wasm – 用于浏览器/本地数据库引擎。
  • Apache Arrow – 用于零拷贝内存传输。
  • Streamlit(Python)– 用于最终的分析 UI。

第一步:定义统一健康模式

首先,我们需要一个“黄金记录”格式。我们将使用 TypeScript 定义一个严格的接口,供每个提供者映射到。

// types/health.ts
export interface UnifiedActivity {
    timestamp: Date;
    source_device: 'Oura' | 'Whoop' | 'Garmin';
    metric_type: 'HRV' | 'RHR' | 'Steps' | 'Calories';
    value: number;
    unit: string;
    metadata: Record;
}

第2步:使用 Apache Arrow 进行规范化

与其将原始 JSON 直接写入数据库,我们会将其转换为 Apache Arrow 缓冲区。这可以保证类型安全,并且能够以极快的速度将数据写入 DuckDB。

import { tableFromArrays, Table } from 'apache-arrow';

export function normalizeOuraData(rawData: any[]): Table {
    const timestamps = rawData.map(d => new Date(d.timestamp).getTime());
    const hrvValues = rawData.map(d => d.hrv_average);

    // Create an Arrow Table
    return tableFromArrays({
        timestamp: new Int64Array(timestamps),
        source_device: Array(rawData.length).fill('Oura'),
        metric_type: Array(rawData.length).fill('HRV'),
        value: new Float64Array(hrvValues),
        unit: Array(rawData.length).fill('ms')
    });
}

第三步:使用 DuckDB 为本地数据湖供能

现在进入魔法环节。我们使用 DuckDB‑Wasm 来导入这些 Arrow 表。DuckDB 是一种面向分析查询的列式数据库,使其非常适合处理多年的健康趋势。

第4步:“官方”扩展方式

虽然构建本地 ETL 工具对个人使用很有帮助,但要为数千用户扩展健康数据管道,需要处理 OAuth 刷新、速率限制和 webhook 监听器。如果你正在构建生产级健康应用,请参考 WellAlly Blog 中讨论的架构模式。他们涵盖了高并发摄取以及符合 HIPAA 标准的存储策略,超出了简单的 DuckDB 实例。

第5步:使用 Streamlit 可视化

Finally, wrap our DuckDB store in a Streamlit dashboard to actually see our data.

import streamlit as st
import duckdb

st.title("Unified Health Intelligence 🥑")

# Connect to the DuckDB file generated by our ETL
con = duckdb.connect(database='health_lake.db')

# Query correlation between Sleep Quality and Resting Heart Rate
df = con.execute("""
    SELECT 
        CAST(timestamp AS DATE) AS date,
        AVG(value) FILTER (WHERE metric_type = 'HRV') AS avg_hrv,
        AVG(value) FILTER (WHERE metric_type = 'RHR') AS avg_rhr
    FROM health_store
    GROUP BY 1
    ORDER BY 1 DESC
""").df()

st.line_chart(df, x='date', y=['avg_hrv', 'avg_rhr'])

结论

您现在拥有一个完整的、本地优先的流水线,它:

  1. 将异构可穿戴设备数据标准化为 UnifiedActivity 架构。
  2. 使用 Apache Arrow 高效传输数据。
  3. 将其存储在高性能、列式的 DuckDB‑Wasm 数据湖中。
  4. 通过 Streamlit 仪表盘展示洞察。

从个人实验到生产级健康平台,这些构建块为您提供了快速迭代的灵活性,同时让数据隐私掌握在您手中。祝您玩得开心! 🚀

支持你的数据

“Quantified Self” 不应意味着成为专有仪表板的 Quantified Slave。通过利用 DuckDBApache Arrow,我们构建了一个管道,使其:

  • 快速:列式存储意味着你的 5 年历史数据可以在毫秒级加载。
  • 私密:你的数据保留在本地环境中。
  • 灵活:添加新设备只需编写一个新的归一化函数。

接下来你想追踪什么?如果你尝试将 Apple Health 或 Fitbit 数据集成到类似的系统中,请在下方留言!

想获取更多关于健康技术和数据工程的高级教程,请访问 wellally.tech/blog 🚀

Back to Blog

相关文章

阅读更多 »

Rapg:基于 TUI 的密钥管理器

我们都有这种经历。你加入一个新项目,首先听到的就是:“在 Slack 的置顶消息里查找 .env 文件”。或者你有多个 .env …

技术是赋能者,而非救世主

为什么思考的清晰度比你使用的工具更重要。Technology 常被视为一种魔法开关——只要打开,它就能让一切改善。新的 software,...

踏入 agentic coding

使用 Copilot Agent 的经验 我主要使用 GitHub Copilot 进行 inline edits 和 PR reviews,让我的大脑完成大部分思考。最近我决定 t...