驱动数字化 质变

从权威的技术洞察,到精准的软硬配置,为企业的每一次转型提供决策支持。

架构师笔记
彻底埋葬“按点位收费”的 Historian!用 DuckDB + Parquet 在边缘网关手搓“湖仓一体”

2026-04-20 20:32:00

#DuckDB #数据湖 #Parquet #边缘计算 #时序数据库 #OLAP #去SCADA化


一、 场景痛点:被 10 万个 Tag 逼上绝路的财务模型

上个月,在一家新能源电池厂的“激光飞焊数据采集”项目中,集成商(SI)的架构师面临着一个死局:

  • 业务需求:每台焊接设备有 50 个高频传感器,以 10kHz (0.1ms) 的频率输出电压、电流、熔池温度和光谱特征。车间有 20 台设备,总计 10 万个高频点位。

  • 灾难 1(买不起软件):如果用传统的 AVEVA PI System 或 GE Proficy,10 万点位的永久授权费高达 ¥150 万元,甲方直接拒批预算。

  • 灾难 2(硬件撑不住):集成商想用免费的 InfluxDB 或 Prometheus 顶上。结果每秒 1000 万条数据涌入,工控机的 16GB 内存瞬间爆满(OOM),时序数据库疯狂触发合并(Compaction),导致 CPU 100% 卡死。

架构师指令:对于极高频的工业时序数据,严禁在边缘端使用 B+ 树或 LSM-Tree 结构的传统时序数据库!


我们需要引入云原生数据工程的终极武器:“开放列式存储 (Parquet) + 进程内 OLAP 引擎 (DuckDB)”。让数据绕过复杂的数据库系统,直接以最优的物理格式躺在硬盘里。


二、 架构设计:Edge Lakehouse (边缘湖仓一体)

我们不装任何沉重的数据库软件,直接把操作系统的文件系统(或本地 MinIO)变成一个“数据湖”。

  1. 摄入层 (Ingestion):Python 或 Go 脚本在内存中接收高频数据,每攒够 100 万条(约几秒钟),直接打包压缩成一个 .parquet 列式文件,落盘到 SSD。

  2. 存储层 (Storage):按照 /year=2026/month=04/day=20/ 的目录结构存放 Parquet 文件。这种格式的压缩率极高(通常是 CSV 的 1/10)。

  3. 分析层 (Compute):使用 DuckDB。它不需要作为一个后台服务(Daemon)运行,它就是一个 30MB 的库。当你需要查询报表时,Python 脚本调用 DuckDB,用标准的 SQL 语句直接去硬盘上扫描那些 Parquet 文件

数据流向拓扑


PLC (10kHz) -> [Python: 攒批写入 Parquet] ->[NVMe SSD (边缘数据湖)] <--(DuckDB SQL 极速聚合)--> [Grafana 看板]


三、 核心实施步骤 (Copy & Paste)

下面演示如何用几十行 Python 代码,实现比传统时序数据库快 10 倍的边缘数据管道。

1. 高速摄入:批量写入 Parquet 文件

不要来一条存一条!在内存中用 pandas 或 pyarrow 攒批,然后瞬间落盘。

Python

import pyarrow as pa
import pyarrow.parquet as pq
import time
import uuid

# 模拟 10 万条高频采集数据存入内存 Buffer
def flush_buffer_to_lake(data_buffer, equipment_id):
    # 1. 转换为 Arrow Table (列式内存格式,零拷贝)
    table = pa.Table.from_pylist(data_buffer)
    
    # 2. 动态生成按天分区的目录路径 (Partitioning)
    today = time.strftime("%Y-%m-%d")
    file_path = f"/data/lakehouse/welding_data/dt={today}/{equipment_id}_{uuid.uuid4().hex[:8]}.parquet"
    
    import os
    os.makedirs(os.path.dirname(file_path), exist_ok=True)
    
    # 3. 极速压缩并写入磁盘 (Snappy 压缩算法极其适合浮点数)
    pq.write_table(table, file_path, compression='snappy')
    print(f"[{time.strftime('%H:%M:%S')}] 落盘成功: {file_path}")

# 业务循环:每秒钟调用一次 flush_buffer_to_lake


2. 秒级分析:用 DuckDB 直接用 SQL 查询文件夹

这是见证奇迹的时刻。你的硬盘里有一堆 .parquet 文件,你不需要导入任何数据库,直接用 DuckDB 查询。

Python
import duckdb

# 1. 连接 DuckDB (在内存中运行,无需起服务)
con = duckdb.connect(database=':memory:')

# 2. 暴力美学:直接用 SQL 查硬盘上的 Parquet 文件夹!
# 假设我们要查 1 号设备,过去 5 分钟内,电压的平均值和最大峰值
query = """
    SELECT 
        time_bucket(INTERVAL '1 minute', timestamp) AS time_window,
        AVG(voltage) AS avg_v,
        MAX(voltage) AS peak_v
    FROM read_parquet('/data/lakehouse/welding_data/dt=2026-04-20/*.parquet')
    WHERE equipment_id = 'WELD_01'
    GROUP BY time_window
    ORDER BY time_window DESC;
"""

# 3. 执行查询 (DuckDB 会自动利用 C++ 向量化执行引擎和多核 CPU 并发扫描文件)
result = con.execute(query).df()
print(result)


实测:在 Intel N97 工控机上,扫描 50GB 的历史波形数据并计算分钟级均值,DuckDB 仅需 1.2 秒,而 InfluxDB 查询同样跨度的数据直接超时崩溃。


四、 踩坑复盘 (Red Flags)

1. “小文件”噩梦 (The Small File Problem)

  • 现象:为了追求实时性,工程师把缓存设置得很小,每 10 毫秒生成一个 Parquet 文件。一天下来生成了 864 万个小文件。当你用 DuckDB 查询时,OS 的文件系统直接被 inode 查找卡死,查询极慢。

  • 对策Parquet 文件的最佳大小是 128MB 到 512MB。在边缘端,建议每 1 分钟或 5 分钟落盘一次。如果前端 OEE 大屏需要秒级实时性,请在架构中并联一个 Redis 流(只存最近 5 分钟热数据),让 DuckDB 查历史,Redis 查实时(Lambda 架构)。

2. 没有 Schema Evolution (结构演进) 机制

  • :今天传感器有 volt 和 curr 两个字段,明天业务升级,加了一个 temp 字段。直接写新的 Parquet 文件会导致前后文件结构不一致,DuckDB 查询时报错 Schema Mismatch。

  • 避雷:在大型项目中,不要直接读写纯 Parquet,必须引入 Apache Iceberg 充当元数据层。Iceberg 允许你像操作 SQL 表一样 ALTER TABLE ADD COLUMN,完美兼容底层文件的变更。

3. SSD 写放大与寿命耗尽

  • 警告:高频大块连续写入对 SSD 寿命是巨大考验。

  • 要求严禁使用消费级 QLC 固态硬盘! 必须采购标称 DWPD (每日写入量) > 1.0 的企业级 TLC 或工业级 pSLC M.2 硬盘。


五、 关联资源与选型

这套“边缘湖仓”架构的核心在于存储的吞吐率(I/O Bandwidth)和 CPU 的向量化计算能力,而不是内存大小。

  • 硬件底座推荐

    • 研华/控创 PCIe 4.0 边缘服务器 (支持双 NVMe RAID 0/1):这是跑 DuckDB 的绝佳载体。高达 7000MB/s 的读写速度,让 SQL 查询几十 GB 的文件就像在内存里一样快。


软件生态

  • Grafana 12+:原生内置了 DuckDB 数据源插件。你可以直接在 Grafana 的界面里写 SQL,查边缘盒子本地的 Parquet 文件,无需任何中间件。



一键克隆架构

还在手动拼凑数据管道?


我们开源了一套 "Edge Lakehouse Python SDK"


包含:基于 PyArrow 的自适应攒批缓冲池、按时间/文件大小自动切分 Parquet 的轮转机制、以及自动清理 30 天前旧数据的守护进程。