一套引擎撑起树表双模型:Apache IoTDB 2.0 与 TsFile 的选型答卷

在工业现场做时序数据库选型,最容易卡在两头:存量采集代码是按「设备树」组织的,新接进来的数仓与报表同事却只想写标准 SQL 按表查,两边都不愿改,最后常常退化成同时维护两套库。

常规做法要么全走树模型、再用视图硬模拟表,要么全改表模型、把设备台账逻辑重写一遍;文件层面还常常停在 Parquet 或各家私有格式,历史数据回补要先导出转换,精度和时区一旦对不上账就很难查。

本文分享一套以 Apache IoTDB 2.0 为底座的落地拆解:一套统一存储引擎 + 树/表两种数据模型 + 一种 TsFile 文件格式,配上可直接复制的建库建表、写入与部署命令。

一、选型卡点:模型割裂与文件格式不通用

真正让人犹豫的从来不是性能数字,而是三处割裂:

  • 树模型习惯:按 root.库.设备.测点 组织,天然贴合设备台账,网关按设备对齐写入最省事。
  • 表模型习惯:按 库.表(标签, 时间, 字段) 组织,BI、Spark、报表工具直接对接,写 SQL 的人零学习成本。
  • 文件层割裂:在线库和离线分析用不同列式格式时,回补历史数据要走一遍导出转换,容易丢精度、丢时区。

把两种诉求摆到同一张表里看,会发现差异只在「入口视角」:

维度 树模型 表模型
组织方式 root.factory.line1.d01.temperature plant.line1(device_id TAG, ts TIME, temperature FIELD)
查询入口 设备路径 + SQL 扩展 标准 SQL,按标签过滤
主要角色 采集网关、设备管理 数仓、BI、报表
落盘文件 TsFile 同一套 TsFile

核心结论: 模型差异是视角差异,不是两套数据库------这正是 2.0 敢把它们放进同一个引擎的前提。

二、一套引擎:元数据与存储链路怎么统一

两套模型能共存,靠的是引擎层把差异收敛到元数据与同一条读写链路:

  • 元数据统一:树模型的测点与表模型的列登记在同一份 Catalog 里,库、用户、权限、时间分区只管一次。
  • 写入统一:无论哪套语法,最终都先写 WAL、再进 MemTable,攒够阈值刷成 TsFile。
  • 读取统一:查询计划先按时间分区裁剪,再用 TsFile 内嵌统计做块级过滤,最后归并出结果。

整条链路可以压缩成一行:

SQL 解析 → 统一计划与优化 → WAL 落日志 → MemTable 累积 → Flush 生成 TsFile → 读时归并

核心结论: 引擎只有一套,模型只是入口,落盘只有一种文件。

三、树模型落地:设备路径写入与对齐查询

树模型适合设备台账清晰、按台采集的现场,落地时抓住三点:

  • 先建库再建点:测点声明 DATATYPE / ENCODING / COMPRESSION,把编码与压缩前置到写入之前。
  • 设备对齐写:一条 INSERT 带上同一设备的多个测点,减少网络往返,吞吐比逐点插高一个量级。
  • 查询限时间窗:查询带上时间范围才能吃到分区裁剪,别写无边界的全路径扫描。

下面这段在 CLI 里可直接执行(默认账号 root/root,语法以所用小版本文档为准):

bash 复制代码
# 进入 CLI
./sbin/start-cli.sh -h 127.0.0.1 -p 6667 -u root -pw root

# 建库 + 建测点:编码与压缩在建点时就定好
CREATE DATABASE root.factory.line1;
CREATE TIMESERIES root.factory.line1.d01.temperature DATATYPE=DOUBLE, ENCODING=GORILLA, COMPRESSION=ZSTD;
CREATE TIMESERIES root.factory.line1.d01.status DATATYPE=BOOLEAN, ENCODING=PLAIN;

# 设备对齐批量写入
INSERT INTO root.factory.line1.d01(timestamp, temperature, status)
VALUES (1710000000000, 36.5, true), (1710000060000, 36.8, false);

# 带时间窗的查询
SELECT temperature, status FROM root.factory.line1.d01 WHERE time > now() - 3600000;

核心结论: 声明式压缩编码 + 设备对齐写入,是树模型吞吐的两个主要来源。

四、表模型落地:关系建表与标签字段设计

表模型让数仓同事用熟悉的 SQL 直接接进来,注意三件事:

  • 三类列分工:TAG 做等值过滤,TIME 是唯一时间列,FIELD 才是随时间变化的测点。
  • 控制标签基数:工单号、批次号这类高基数值塞进 TAG 会让元数据膨胀,只留真正高频过滤的列。
  • 复用同一通道:同一套账号、同一个 Session,迁移存量代码主要改 DDL 与 INSERT。

下面用 Python 官方 Session 库演示建表、写入与查询:

python 复制代码
from iotdb.Session import Session

s = Session(host="127.0.0.1", port=6667, username="root", password="root")
s.open()

# 关系式建表:TAG / TIME / FIELD 三类列
s.executeNonQueryStatement("""
CREATE TABLE plant.line1 (
  device_id STRING TAG,
  ts TIMESTAMP TIME,
  temperature DOUBLE FIELD,
  status BOOLEAN FIELD
)
""")

# 表模型按表 + 标签写入
s.insertRecord("plant.line1", ["d01"], 1710000000000, ["temperature", "status"], [36.5, "true"])
s.insertRecord("plant.line1", ["d01"], 1710000060000, ["temperature", "status"], [36.8, "false"])

rs = s.executeQueryStatement("SELECT temperature FROM plant.line1 WHERE device_id='d01' ORDER BY ts DESC LIMIT 10")
while rs.next():
    print(rs.row_to_string())

s.close()

两种模型的差异可以压缩成下面这张对照表:

事项 树模型 表模型
命名空间 root.factory.line1.d01 plant.line1
设备标识 路径中的一段 device_id TAG
时间戳 路径下的时间轴 ts TIMESTAMP TIME
批量写入 INSERT INTO 路径 INSERT INTO 表(列)

核心结论: 换了建表语法,没换引擎,也没换落盘的文件。

五、一种文件:TsFile 的落盘结构与生态复用

所有模型最终都落成 TsFile,这让在线查询和离线分析第一次对上账:

  • 时序原生布局:按时间分区、分块(chunk)存放,块内保留时间与数值统计,谓词下推能直接跳过整块。
  • 压缩编码内置:lz4/zstd 压缩叠加 GORILLA、RLE、TS_2DIFF 编码,高频双精度测点能压到很低的体积。
  • 文件可搬运:一个 .tsfile 就是一份自洽数据,离线生成后直接 LOAD 回集群,不必重放网络流量。

离线文件回补集群只需一条命令:

bash 复制代码
# 把离线生成的 TsFile 直接灌进集群(历史回补、跨集群搬运通用)
./sbin/start-cli.sh -e "LOAD '/export/backfill/2024-03-15.tsfile' INTO root.factory.line1;"

# 落盘形态:时间分区目录下成对出现主文件与 .mods 增量文件
ls data/datanode/data/full/region-0/0/0/0

核心结论: 一次落盘、多处复用:在线查询、离线回补、跨集群搬运都是同一个文件。

和通用列式格式的差异也值得列清楚:

对比项 TsFile Parquet / ORC
时间分区 原生按时间分区 需自行分目录约定
块级统计 时间 + 数值双维统计 以列统计为主
增量更新 .mods 记录删除与覆盖,读时归并 通常需重写文件
复用方式 本地库、Spark/Flink 直读 通用大数据生态

六、部署压测:从单机双进程到集群最小拓扑

落地时先把拓扑定死,再谈调参与压测,顺序反了数字就没有可比性:

  • 单机两进程:ConfigNode 管元数据与成员关系,DataNode 管数据,先在一台机器跑通再横向扩。
  • 奇数仲裁组:ConfigNode 3 个起步做元数据仲裁,DataNode 按写入带宽线性增加。
  • 压测用官方工具:iotdb-benchmark 按「设备数 × 测点数 × 采集周期」造数,比手写脚本更接近现场。
bash 复制代码
# 单机起步:先起元数据节点,再起数据节点
./sbin/start-confignode.sh -d
./sbin/start-datanode.sh -d

# 集群扩展:给 3 台机器拉起 ConfigNode 组
for ip in 10.0.0.11 10.0.0.12 10.0.0.13; do
  ssh "$ip" "/opt/iotdb/sbin/start-confignode.sh -d -cn cn1"
done

# 按现场节奏造数压测:设备 2 万、每台 20 测点、1 秒一个采集周期
./tools/iotdb-benchmark.sh -td 20000 -t 20 -p 1000 -ts COMPRESSED

压测与上线阶段最常见的三类问题,可以直接对照下表处置:

症状 原因 处置
建表后提示库不存在 表模型库未创建,或库名带了 root. 前缀 先 CREATE DATABASE,再切换到该库执行
写入慢且 WAL 涨得快 逐点逐行写入,网络往返过多 改为设备对齐或按表批量 INSERT
磁盘占用高于预期 小文件过多、合并未跟上 调大 memtable 刷盘阈值,观察合并任务

核心结论: 先拓扑、后参数、再压测,写入吞吐和查询延迟才有可复现的基线。

结语

回看这套方案,价值不在某一个单点功能,而在于把选型时最耗人的三件事一次性收拢:写入侧不用在树模型与表模型之间二选一,同一套引擎、同一份元数据、同一套权限都能承载;数据侧不必再为在线库和离线分析准备两套格式,TsFile 既能被集群直接查询,也能被离线工具读走;运维侧从单机双进程到 3 节点仲裁组的路径是确定的,压测数字可以横向对比。

对正在做工业物联网平台的团队来说,这套结构能直接省掉「视图模拟表」「导出转换回补」「双库对账」三类长期成本。

一套引擎、两种模型、一种文件:把选型分歧收敛成架构共识。

相关推荐
凤山老林2 小时前
Spring Boot 集成 Apache CXF 发布与消费 SOAP WebService:WSDL、WS-Security
spring boot·后端·apache·webservice·soap·apache cxf
ZENERGY-众壹3 小时前
光伏监控平台实测,三款时序数据库在 50 万测点下的真实表现
数据库·架构·运维开发·时序数据库·光伏运维
inquisiter1 天前
KernelGpt的部分尝试
java·前端·apache
十碗饭吃不饱1 天前
Apache POI XWPFTableCell 表格单元格排版踩坑总结
apache
底层玩家老张2 天前
45:1压缩、19.1倍查询:1956亿行时序数据复盘
数据库·经验分享·时序数据库·存储压缩·写入优化
betazhou2 天前
Apache Seatunnel 抽取SQL server实时同步到Oracle-多表同步(五)
数据库·oracle·sqlserver·apache·seatunnel
涛思数据(TDengine)2 天前
陶建辉获聘中国通信标准化协会本体工程专家委员会首批委员
人工智能·时序数据库·tdengine·工业互联网·工业ai
青企通3 天前
电商小程序购物车与库存扣减:SKU 模型、合并规则与防超卖实践
小程序·apache
云边有个稻草人3 天前
不止于时序存储:DolphinDB 如何成为 AI 时代的工业智能底座
时序数据库·实时计算·工业物联网·流批一体·dolphindb·数据库选型·工业大数据