Apache IoTDB V2.0.11 版本解析:JDK 17、IoTDB Edge 与元数据租约

32 min

写在前面

Apache IoTDB V2.0.11 已发布,发布分支为 rc/2.0.11,tag 提交日期 2026-09-07。

从 V2.0.10 到 V2.0.11 共 305 个 commit(git rev-list --count v2.0.10..v2.0.11)。两个 tag 之间的 diff 是 2,647 个文件、+154,535 / −17,623 行,其中 Java 文件 2,479 个、测试文件 509 个。commit 数量比上一周期(407 个)少,但改动量没有下降。这些改动里相当一部分不是功能:javax 到 jakarta 的命名空间迁移和中文 i18n 铺开两项就占了大量行数。

本周期与 AINode 相关的 commit 只有 1 个(#17822,移除 Chronos2 DataLoader 的 pin_memory 选项),Release Notes 的 Features 部分也没有 AI 条目。对比 V2.0.8 的 Chronos-2 和 V2.0.10 的 Toto、Moirai2,这一版在 AI 侧没有新增能力。

本文基于 2.0.11 Release Notes 和本地 apache/iotdb 仓库中 v2.0.10..v2.0.11 的实际 diff 写成。文中 PR 编号均可在官方仓库检索。


变更一:最低 JDK 提升到 17

Release Notes 的 Break change 只有一行:

## Break change
- This version raises the minimum required JDK version to 17.

这条变更在六月份的预告文章里已经分析过,来源是 PR #17859(Upgrade minimum JDK to 17 and migrate JavaX to Jakarta)。当时的判断是:Jetty 12、Jersey 3、Logback 1.5 这批 Web 技术栈已经不再提供 JDK 8 + javax 的新版本,继续停留在旧基线等于放弃这些库的后续修复。

随 JDK 一起抬升的还有一批依赖。对比两个 tag 的 pom.xml

V2.0.10V2.0.11
maven.compiler.source / target1.817
TsFile2.3.12.4.0
Apache Ratis3.2.23.3.0
Apache Thrift0.14.10.23.0
iotdb-tools-thrift0.14.1.00.23.0.0

Thrift 从 0.14 到 0.23 跨了九个 minor 版本。iotdb-tools-thrift 需要同步跳版,因为 IoTDB 的 Thrift 代码生成器是自维护的,生成器版本必须和运行时协议库一致,否则生成的 stub 与运行时不匹配。

Release Notes 里 Break change 只有一行,但 diff 有 2,647 个文件,原因在于几块工作叠在一起:javax 到 jakarta 的机械替换、中文 i18n 铺开,以及一批新增功能和修复。前两块属于工程规整,不产生用户可见的新功能。对使用方来说,升级风险集中在兼容性上,而不是行为变化。

JDK 基线抬升也会传导到客户端。本周期 C++ 客户端有两处配套改动:#17987 改为默认用 OpenSSL 3.x 构建并捆绑运行时依赖;#17801 重构 SDK,引入线程安全的 SessionPool、启用 RPC 压缩、加固缓冲区边界。服务端换了 TLS 栈之后,客户端如果还在老 OpenSSL 上,握手阶段就会先出问题。


变更二:IoTDB Edge

Release Notes 对 Edge 版的描述是:把 ConfigNode 和 DataNode 合并到一个进程,两者合计内存控制在 512 MB 以内,目标负载约为 1 万个测点、1 Hz 读写。第一版主要调整 JVM 和默认配置参数,后续版本再向 DataNode 与 ConfigNode 的架构级融合演进。

动机是进程的固定开销。云端部署下 ConfigNode 和 DataNode 各自一个 JVM 没问题,但在边缘网关上,每个 JVM 的 metaspace、code cache、GC 结构和线程栈都是重复开销。

从源码看,Edge 版不是新写一个数据库,而是在现有 ConfigNode 和 DataNode 之上加了一层进程内启动编排:

distribution/src/assembly/edge.xml                          分发包定义(zip + dir)
distribution/src/assembly/resources/conf-edge/logback-edge.xml
iotdb-core/confignode/src/main/java/org/apache/iotdb/edge/EdgeNode.java
iotdb-core/node-commons/src/assembly/resources/conf/edge/iotdb-system.properties
scripts/conf/edge-env.sh
scripts/sbin/start-edge.sh / stop-edge.sh
scripts/tools/ops/daemon-edge.sh / destroy-edge.sh

EdgeNode.main() 处理的是两个服务如何在一个进程里有序启动。ConfigNode 先在后台线程启动,主线程以 500ms 间隔探测 ConfigNode 的内部 RPC 端口,最长等 300 秒;端口可连接后再等 5 秒,让单节点 ConfigNode 的 Ratis 选主完成,然后 DataNode 在主线程启动。两个服务各靠自己的非 daemon 线程维持 JVM 存活。任一节点致命失败会终止整个进程,源码注释里写明这是预期的单进程语义,进程内不做隔离。

内存参数在 scripts/conf/edge-env.sh

参数默认值说明
ON_HEAP_MEMORY224M堆上限(-Xmx
INIT_HEAP_MEMORY64M初始堆
OFF_HEAP_MEMORY96M直接内存(-XX:MaxDirectMemorySize

三者相加 320 MB,距离 512 MB 的目标留了约 200 MB 给 metaspace、code cache 和线程栈。脚本启用了 -XX:+CrashOnOutOfMemoryError,并倾向选用串行 GC,注释理由是固定内存开销最低。这几个变量都支持环境变量覆盖。另外 #17765 让 env 脚本能在容器里识别 cgroup 内存限额。

Edge 相关 commit 的时间集中在发布前十天:

2026-08-28  #18530  Disable unused pipe/sub logic
2026-08-31  #18538  Add IoTDB Edge distribution
2026-08-31  #18552  Add daemon and cleanup tools for IoTDB Edge
2026-09-02  #18566  Tune Edge compaction and TsFile defaults
2026-09-02  #18568  Update pipe config for edge version
2026-09-04  #18576  Change default edge ratis configuration
2026-09-04  #18580  Bound PartitionInfo snapshot buffers
2026-09-06  #18584  Make Edge query and load-event thread pools configurable
2026-09-07  tag v2.0.11

从第一个 Edge commit 到发版是 10 天。这批 commit 的内容集中在 Ratis 配置、压缩策略、TsFile 默认值、线程池规模和快照缓冲上限上,都是在真实资源约束下跑起来才会暴露的参数。

配套 CI 已建立:.github/workflows/edge-it.ymlintegration-test/src/main/java/org/apache/iotdb/itbase/category/EdgeIT.java,以及 Windows 侧的 test-edge-windows.ps1check-edge.ps1


变更三:元数据租约与自隔离

Release Notes 里有一条 High Availability:为表管理、设备管理、可写视图管理、TTL 管理、数据库管理和用户权限提供高可用。对应的是 commit d02e6af5(A lease-based self-fencing framework to enable high availability for table metadata operations)和 #18425(authority 模块高可用)。

要理解这个框架解决的问题,先看元数据的流向。IoTDB 的表结构、设备属性、模板、TTL、权限由 ConfigNode 持有,并主动推送到各 DataNode 缓存。DataNode 在查询和写入时读本地缓存,不每次都问 ConfigNode。如果某次推送因为网络分区或节点假死没有送达,该 DataNode 会继续按旧 schema 和旧权限服务,而集群已经按新元数据前进了。

租约方案的核心是两个时间常数的约定:

T_fence    DataNode 侧。超过这个时间没收到 ConfigNode 心跳,就放弃信任本地元数据缓存。
           配置项 metadata_lease_fence_ms,默认 20000ms。

T_proceed  ConfigNode 侧。没 ack 的 DataNode 静默超过这个时间,就认为它已经自我隔离,可以继续广播。

安全前提是 T_proceed >= T_fence:只要 ConfigNode 决定继续广播,那个没 ack 的 DataNode
一定已经先一步自我隔离,不会出现它按旧元数据服务、而集群按新元数据前进的情况。

框架的代码分在两端。DataNode 侧是 MetadataLeaseManager,跟踪租约状态并在超时后触发自隔离,配套 MetadataLeaseMetrics 暴露距上次心跳的时长、MetadataLeaseFencedException 作为隔离期间的异常。ConfigNode 侧是 MetadataBroadcastVerdict(决策逻辑)、ClusterCachePropagator(广播执行)和 DataNodeContactTracker(跟踪各 DataNode 最后成功心跳时间)。

MetadataBroadcastVerdict 的判定规则写在类注释里:未 ack 的 DataNode 被认为支持自隔离,这一前提由调用方保证;当所有未 ack 的 DataNode 静默时间都超过 T_proceed 时返回 PROCEED,否则返回 WAIT 直到重试预算耗尽,再返回 FAIL。这段代码是个约 40 行的纯函数,没有锁和 IO。

MetadataLeaseManager 内部维护三个状态:NORMALCACHE_CLEARINGCACHE_CLEARED。心跳超时后进入清缓存流程,重新收到心跳并完成元数据拉取后回到 NORMAL。两处实现细节:

  • 时钟用 System.nanoTime()。类注释说明是为了不受墙上时钟调整影响。租约判定如果依赖系统时间,一次 NTP 校时就可能误触发或漏触发。
  • 隔离动作是一个 action 列表,由各子系统注册。租约管理器本身不关心有哪些缓存要清。diff 里能看到按子系统拆分的测试:DataNodeTableCacheLeaseTestPartitionCacheLeaseTestClusterAuthorityFetcherLeaseTest
  • 隔离期间的行为是 fail-closed,明确拒绝并抛 MetadataLeaseFencedException,不做降级。

这个方案相比让元数据走共识协议的取舍是:共识方案一致性最强,但元数据读在查询和写入的每条路径上都会发生,每次走多数派确认会明显抬高延迟和 ConfigNode 负载。租约方案用有界的过期时间换掉了每次共识,正常路径下 DataNode 读本地缓存没有额外开销,代价是故障期间存在一个最长 T_fence 的窗口,这段时间内集群可能拒绝服务。


变更四:SQL 与查询

本周期 SQL 层面的新增不多,有几处可以对一下语法和实现的差异。

只包含 SELECT 子句的 SQL 语句

Release Notes 的表述是 SQL 语句支持只包含 SELECT 子句。对应 #17437(Support table-less SELECT queries without FROM clause),也就是查询可以不带 FROM、直接由 SELECT 子句构成。

对比两个 tag 的 RelationalSql.g4querySpecification 规则逐字相同,FROM 早就是可选的:

querySpecification
    : SELECT setQuantifier? selectItem (',' selectItem)*
      (FROM relation (',' relation)*)?        <- 可选
      (WHERE where=booleanExpression)?
    ...

缺的是执行链路。#17437 只改了 5 个文件、77 行:QueryPlannerValuesNodeTableOperatorGeneratorUnaliasSymbolReferences。这和 V2.0.10 里 CTE、集合操作的情况类似,语法先就位,执行后补齐。

EXPLAIN ANALYZE 的 JSON 输出

#17430。语法变化是在 EXPLAIN 和 EXPLAIN ANALYZE 后面插入一个可选的 (FORMAT identifier)

v2.0.10:  EXPLAIN (query | executeStatement | executeImmediateStatement)
v2.0.11:  EXPLAIN ('(' FORMAT identifier ')')? (query | executeStatement | executeImmediateStatement)

格式名用的是 identifier 而不是枚举,说明格式是开放的,后续加别的格式不需要改语法。配套测试里有 ExplainJsonCliOutputIT

NEXT 填充

#17810([IOTDB-17798] Implement table model NEXT fill)。语法规则是 NEXT timeBoundClause? timeColumnClause? fillGroupClause?。它复用了已有的 timeBoundClausetimeColumnClausefillGroupClause,说明填充功能的语法骨架此前已经搭好,NEXT 是往里加的一种策略。

别名与 GROUP BY ALL

  • GROUP BY ALL#17937,自动按所有非聚合列分组。语法层新增了一条产生式,ALL 单独分支,其余仍走 explicitGroupBy
  • SELECT 别名用于 GROUP BY 和 ORDER BY:#17843
  • SELECT 列表中的横向列别名:#17960(IOTDB-17797)。

别名支持是分析器层面的改动。从 churn 看,StatementAnalyzer.java 本周期 +1,181 / −204 行,是改动最大的非新增文件之一。

M4 表函数

#17656 引入 M4TableFunction,802 行,位置在 node-commonsudf/builtin/relational/tvf/ 下。

M4 是时序可视化里的降采样聚合方法,每个窗口取 Min、Max、Mean、Median 四个点,用四个点保留窗口的形态特征。它的参数包括 DATATIMECOLSIZESLIDEORIGIN,同时支持时间窗口(TimeWindowM4DataProcessor)和计数窗口(CountWindowM4DataProcessor),支持分组列和多值列。分区列允许 BLOB 类型,代码注释解释是 M4 只需要在分区列上读写、不需要比较。

把降采样下推到数据库里,可以省掉把全量数据拉到前端再抽样这一步。

其他语句与函数

  • #17292:自相关、偏度、线性回归等统计类聚合函数
  • #16545PERCENTILE 聚合函数
  • #17456CAPACITY 表值函数支持 SLIDE 参数
  • COUNT DATABASE#17705),不过这个实现在 2.0.10 分支上同样存在

变更五:运维、安全与内存

配置热加载

#17975 支持集群运行时配置热加载。配套修了 #18108SHOW CONFIGURATION 之前显示原始值而不是生效值。这两个缺一不可:只有 SHOW CONFIGURATION 能看不能改,只有热加载能改但看不准。

本周期还顺带修了几处热加载的边界问题:#18326(拒绝 NaN 磁盘告警阈值)、#18336(WAL 限流阈值回退)、#18327(无效 WAL 限流阈值持久化)、#18091(procedure 清理器与磁盘阈值)。

通信加密

三个 PR 补上了加密能力:

  • #18026:Thrift 客户端双向 TLS
  • #18080:Pipe sink 双向 SSL
  • #17854:通用 SSL/TLS 配置

单向 TLS 只能验证客户端连的是真服务端,双向 TLS 还能验证服务端接受的是真客户端。跨机房、跨网络的 Pipe 同步链路需要后者。

安全方面还有两处:#17508 修了 JDBC StringUtils.consistentToString 的 BigDecimal 拒绝服务问题;#17823 新增 THREAT_MODEL.md,打通 AGENTS.md → SECURITY.md → THREAT_MODEL.md 路径。

内存控制

本周期标题里带 memory 的 commit 有 26 个,覆盖的层次比较多:

协议层    #17911  RPC 自动扩容缓冲区的内存控制
          #18194  修复 RPC 缓冲内存控制未生效
查询层    #17788  查询涉及 mods 时的内存控制
          #18249  对齐 MemTable 位图内存占用
          #18052  内存不足时仍允许 SHOW QUERIES 执行
          #18039  元数据 OOM 诊断信息增强
同步层    #18090  Pipe 接收端内存保护
          #18069  Pipe 日志收敛器 ConfigNode 内存控制
          #18144  Pipe 接收端临时内存管理
          #18141  内存压力下限制 TsFile 解析重试
容器层    #17765  env 脚本识别 cgroup 内存限额

其中 #17911 把控制点放在协议层。分布式查询中 RPC 缓冲区会随数据量自动扩容,没有上限的话一个大结果集就可能吃掉大量直接内存。放在协议层意味着不管上层算子怎么改,这一层都有兜底。

集群运维

  • #17702:新增 SHOW CREATE PIPESHOW CREATE DATABASESHOW CREATE TOPICALTER TOPIC,30 个文件、+1,432 行
  • #17637:新增 SHOW REPAIR DATA PARTITION TABLE PROGRESS
  • #18046MIGRATE REGION 支持多 region
  • #18114:修复并发删除快照目录时误判磁盘满并转为只读
  • #18097:区域组清理改为恢复安全提交加重试
  • #17981:避免 Pipe sink task id 把凭据写进日志
  • #18002:修复删除包含别名和 metrics map 的问题

SHOW CREATE PIPE 对运维的用处是迁移或复制 Pipe 配置时不用再从 SHOW PIPES 的输出里拼 SQL。


变更六:客户端

Node.js 客户端

Release Notes 提到新增 Node.js 客户端,仅支持 2.x 版本。对应独立仓库 apache/iotdb-client-nodejs,npm 包名 @iotdb/client,版本号与数据库同步为 2.0.11。

package.json 和 README 看,能力包括树模型和表模型双 API、SessionPool(多节点轮询负载均衡与故障切换)、TableSessionPool(表模型专用,带 database 上下文管理)、SSL/TLS 支持和完整的 TypeScript 类型定义。

把这个和 V2.0.10 的 C 语言驱动 SDK 放在一起看,客户端覆盖的层次是:Java、Python、C++、C# 覆盖服务端和长期存在的集成场景,C 驱动覆盖边缘设备,Node.js 覆盖 Web 应用和脚本化数据处理。Node.js 客户端此前只能通过 REST 或 JDBC 桥接。

其他客户端修复

  • #18096:C++ 客户端查询 DATE 1000-01-01 崩溃
  • #17759:C++ 客户端把 FLOAT 推断列读成 DOUBLE
  • #18005:C++ 客户端 tablet 边界与会话关闭语义
  • #18078:JDBC 重连后复用失效的 statement id
  • #17956:Session C 增加 DATE/BLOB 支持与 RowRecord getter

#180961000-01-01 这个边界值触发的崩溃,属于只有真拿到这个值才会复现的问题。


变更七:中文 i18n

这一项没有出现在 Release Notes 的功能列表里,但从代码量看是本周期最大的单项工程之一。

指标V2.0.10V2.0.11
英文 i18n 消息键总数5,1789,413
i18n 目录新增和删除行+17,036 / −1,815

消息键数量增加了 82%。DataNodeQueryMessages 的中文版本周期新增 3,199 行、英文版新增 2,391 行,是改动量最大的两个文件。

关键动作是 #18110(Complete zh locale i18n: convert leftover literals, review translations, add zh-compile CI),做了三件事:把残留的硬编码字面量转成消息键、复核现有翻译、新增 zh-compile CI。

第三件事影响最长远。i18n 消息键是 public static final String 常量,中英文两套文件必须逐一对应,少一个键就编译失败。新增的 .github/workflows/zh-locale-compile.yml 覆盖 masterrel/*rc/* 分支的 push 和 PR。把中文 locale 的编译纳入 CI,等于用编译器保证新增的消息一定有中文翻译。


Release Notes 与代码不一致的几处

逐条比对 Release Notes 和 diff 之后,有四处对不上。

一、regionId 列表的语法归属

Release Notes 写的是 EXTEND REGION 和 REMOVE REGION 支持 regionId 列表。但这两条语法在 v2.0.10 就已经支持了:

v2.0.10 就已经是这样(IoTDBSqlParser.g4:587-593)
extendRegion
    : EXTEND REGION regionIds+=INTEGER_LITERAL (COMMA regionIds+=INTEGER_LITERAL)* TO targetDataNodeId=INTEGER_LITERAL
    ;
removeRegion
    : REMOVE REGION regionIds+=INTEGER_LITERAL (COMMA regionIds+=INTEGER_LITERAL)* FROM targetDataNodeId=INTEGER_LITERAL
    ;

本周期真正改的是 MIGRATE REGION

v2.0.10:  MIGRATE REGION regionId=INTEGER_VALUE FROM fromId=INTEGER_VALUE TO toId=INTEGER_VALUE
v2.0.11:  MIGRATE REGION regionIds+=INTEGER_VALUE (',' regionIds+=INTEGER_VALUE)* FROM fromId=INTEGER_VALUE TO toId=INTEGER_VALUE

对应 PR #18046,标题是 Support multiple regions in MIGRATE REGION and multiple DataNodes in REMOVE DATANODE。Release Notes 把语句名写串了。

二、两处命名变更未列入 Break change

这是本周期风险最高的地方。有两个对外可见的名字改了,但 Break change 部分只提了 JDK 17。

SQL 关键字改名,来自 #17988

SCHEMA_REGION_GROUP_NUM  ->  MAX_SCHEMA_REGION_GROUP_NUM
DATA_REGION_GROUP_NUM    ->  MAX_DATA_REGION_GROUP_NUM

三个语法文件(IdentifierParser.g4IoTDBSqlParser.g4RelationalSql.g4)里的词法记号都改了。

配置项改名,来自 #18138

partition_table_recover_max_read_megabytes_per_second
  ->  partition_table_recover_max_read_mb_per_sec

影响面是写死了 WITH (SCHEMA_REGION_GROUP_NUM = N) 的建库脚本、在 conf 里配了旧配置项的集群,以及自动化运维平台里硬编码这些标识符的模板。改名本身有理由:MAX_ 前缀让语义更准确,mb_per_sec 和别的配置项命名风格更一致。但这对存量脚本是硬断裂,本该和 JDK 17 一起列进 Break change。

三、Bugs 列表与 2.0.10 有重叠

2.0.11 的 Bugs 列表里有些条目在 2.0.10 分支上已经修过。原因在发布分支的结构上:两个 tag 的 merge-base 是 7e488ffcac(2026-05-28),rc/2.0.10rc/2.0.11 是从 master 不同位置切出来的两条独立维护线,同一批修复会被 cherry-pick 到两条线上,只是 commit hash 不同。

比如 #17583(LIST USER 并发修复)在两个 tag 上都能找到。所以在 2.0.11 的 Bugs 列表里看到某个 bug,不代表它是 2.0.11 才修的。判断某个修复实际进入了哪个版本,可靠的方法是:

git log v2.0.10 --oneline --grep="#<PR号>"    # 有输出 = 2.0.10 已含
git log v2.0.11 --oneline --grep="#<PR号>"    # 有输出 = 2.0.11 已含

本文的版本归属判断都用了这个方法。

四、COPY TO 的改动

Release Notes 写的是支持用 SQL 把查询结果写到指定路径的 TsFile。但 COPY ... TO '<path>' 的语法在两个 tag 之间逐字未变。

本周期 copyto 相关的实际改动只有 3 个文件、54 行增量,核心是 CopyToTsFileOptions 新增了一个字段:

private boolean generateNewTableName = false;

逻辑是当查询里识别不出唯一的目标表时(onlyOneQueriedTable == null),把表名标上 AUTO_GEN_MARK 写入 TsFile 元数据:

if (onlyOneQueriedTable == null) {
    targetTableName = DEFAULT_TABLE_NAME;
    generateNewTableName = true;
} else {
    targetTableName = onlyOneQueriedTable.getTableName();
}

这个功能本身早已存在,本周期只是让无明确来源表的导出结果在元数据里标注得更准确。


问题修复

305 个 commit 里有 172 个(56%)标题含 fix。以下几类影响面较大。

内存与资源

  • ConfigNode 线程泄漏:IoTV2 3C3D 三副本下跑树模型 SQL 用例,ConfigNodeRPC-Processor 线程数涨到 3,000,集群无法连接,CN 和 DN 都报 NPE
  • PipeTsFileEpochProgressIndexKeeper 保留超过 50,000 个 TsFileResource:2 副本边读边写边删、停掉一个 DataNode,54 小时后 OOM,产生 23 GB dump(ON_HEAP_MEMORY="20G"
  • IoTConsensus 进程退出:3C3D 2 副本并发读写删加停一个 DataNode,单 DN 进程退出,25 GB dump

后两条出现在同一个测试场景里,指向的是系统性的内存问题。这类问题需要长时间破坏性压测才能暴露。

查询

  • Frame size (69670459) larger than protect max size (67108864) 导致查询挂起。超限是预期内的情况,应该返回明确错误
  • 整型和浮点型在过滤条件中比较行为不一致(树模型和表模型)
  • CAST(1.1 AS FLOAT)UnsupportedOperationException: DoubleColumn
  • 时间过滤下对齐 LAST 查询挂起(#18213
  • 窗口函数状态跨批次重置(#17813
  • Driver Scheduler 就绪队列预留泄漏(#17919

集群与共识

  • LIST USERCREATE USERDROP USER 并发执行返回 301: Ratis request failed Unknown,CN 日志报 ConcurrentModificationException
  • 重启后某节点停在 Unknown 状态且无日志,主线程被阻塞,但该节点仍能正常做 compaction
  • SHOW REGIONSCompressionRatio 在缩容期间和重启后显示负数或 NaN
  • LOAD TSFILE 在接收端有写权限时仍报权限失败,且数据未同步
  • 缩容时监控面板上 IoTConsensusQueue 的内存显示负值

第二条里节点仍能正常 compaction 这一点,说明用单一功能是否正常来判断节点健康不可靠。

同步与存储

  • attribute 字段 null 值导致接收端数组越界,阻塞整条同步管道
  • 空值插入字段的行无法同步到接收端
  • TTL 过期数据的幂等逻辑错误导致重复同步
  • 单个 Pipe 子任务内存不足触发同步临时停止
  • 修改 write-back-sink 的用户名或密码后阻止数据接收
  • 加载操作异常重复广播删除标记
  • Schema 快照导致的表设备恢复问题(#18324

其他修复

  • #18239:DataNode 启用 CRC32 硬件指令
  • #18045:compaction 硬链接失败时回退为复制
  • #18223:修复 TierManager 目录重置时的并发访问
  • #18290:修复 IoTConsensus 配置的并发访问
  • #18304:新增 CorruptedTsFileException,异常里带上文件路径

升级注意事项

前提:JDK 17

这是本版本唯一的硬性阻断项。仍然运行 JDK 8 或 11 的节点必须先升级 JVM,再升级 IoTDB。

java -version
grep -r "JAVA_HOME" $IOTDB_HOME/conf/*.sh

跨 JDK 基线升级建议分两步走,先把当前版本(比如 2.0.10)在 JDK 17 上跑通,隔离掉 JDK 升级带来的问题,再升级到 2.0.11。

另外两点需要单独确认:

  • 自定义 UDF 和插件是通过 jar 加载的用户代码,需要自行确认 JDK 17 兼容性
  • 如果有基于 IoTDB REST 接口的服务端扩展,注意 javax 到 jakarta 是包名断裂,不是版本升级。原来 import javax.ws.rs.GET; 的代码在新版本下是找不到类,不是 deprecated 警告,需要重新编译

两处静默改名

旧名字不会报错,只会静默失效,这是最需要检查的地方。

# 建库脚本里的旧 SQL 关键字
grep -rnE "SCHEMA_REGION_GROUP_NUM|DATA_REGION_GROUP_NUM" --include="*.sql" --include="*.sh" .

# conf 里的旧配置项
grep -rn "partition_table_recover_max_read_megabytes_per_second" $IOTDB_HOME/conf/

用 SQL 改配置

conf 文件不是唯一的入口。IoTDB 提供 SET CONFIGURATION 语句:

SET CONFIGURATION "heartbeat_interval_in_ms"="500";
SET CONFIGURATION "enable_seq_space_compaction"="false";
SET CONFIGURATION "enable_unseq_space_compaction"="false" ON 0;   -- 只改 0 号节点

配置模板里每个条目都标了 effectiveMode,决定了它能不能这样改:

122 项  hot_reload   可以改,立即生效
197 项  restart      可以改,写回文件,重启后生效
 27 项  first_start  只在首次启动时读取,改不了

SET CONFIGURATION 会拒绝两类键:模板里不存在的(写错了会当场报出来),以及 first_start 的。剩下的都会被接受并写回 iotdb-system.properties,所以重启后依然有效,不需要手工编辑 文件。模板头部就是这么建议的:

# Manually modifying configuration file is not recommended, which may cause node restart fail.
# If you need to modify the cluster name, it's recommended to use 'set configuration "cluster_name=xxx"' sql.

改完用 SHOW CONFIGURATION 确认生效值。注意它显示的是运行时实际生效的值,而不是文件里的 原始值(这个区分本身是 2.0.11 才修对的,见 #18108):

SHOW CONFIGURATION;
SHOW CONFIGURATION ON 0;

对升级来说,这意味着 conf 合并的工作量可以小很多:只有 restart 和 first_start 的项必须 落到文件里,hot_reload 的项升级后执行一条 SQL 就行。

升级步骤

备份程序目录,替换,改配置,启动。data 目录原地不动。

# 1. 记录当前版本
./sbin/start-cli.sh -e "show version"

# 2. 备份程序目录(不含 data,加起来几百 MB)
BK=/opt/iotdb.backup.$(date +%Y%m%d)
mkdir -p $BK
cd $IOTDB_HOME
cp -r lib sbin conf tools $BK/

# 3. 替换。必须先删除再拷贝
cd $IOTDB_HOME
rm -rf lib sbin conf tools
cp -r apache-iotdb-2.0.11-all-bin/lib   ./lib
cp -r apache-iotdb-2.0.11-all-bin/sbin  ./sbin
cp -r apache-iotdb-2.0.11-all-bin/conf  ./conf
cp -r apache-iotdb-2.0.11-all-bin/tools ./tools

# 4. 把备份里改过的配置项改回新 conf
diff $BK/conf/iotdb-system.properties conf/iotdb-system.properties

# 5. 启动并验证
./sbin/start-standalone.sh            # 分布式则分别启 confignode 和 datanode
./sbin/start-cli.sh -e "show version"

第 3 步必须先删除再拷贝。lib 里的 jar 文件名带版本号,2.0.11 有 43 个 jar 改了名,cp 只覆盖 同名文件,旧 jar 会全部留下。而启动脚本把 lib 下所有 jar 都收进 classpath:

CLASSPATH=""
for f in "${IOTDB_HOME}"/lib/*.jar; do
  CLASSPATH=${CLASSPATH}":"$f
done

glob 按文件名排序展开,旧版本号(2.0.10、0.14.1、3.2.2)恰好排在新版本号(2.0.11、0.23.0、 3.3.0)前面,而 classpath 里先出现的优先。结果是 lib 从 114 个 jar 变成 157 个,新 jar 明明 在目录里却不会被加载:升级看起来成功,show version 也查得到 2.0.11,但跑的是 2.0.10 的代码。

sbin 和 tools 下的脚本名是固定的,conf 也是固定文件名,本来不删也能覆盖;统一删掉只是让 流程一致,免得以后哪次改版又出现改名。

备份只覆盖这四个目录。不要连 data 一起备份:生产环境的数据动辄上 TB,复制一份要等量磁盘 和数小时,很可能在升级前先把磁盘写满。这次升级也不修改 data 目录,新版本只是读它。

回退

把四个目录换回去,然后启动:

cd $IOTDB_HOME
rm -rf lib sbin conf tools
BK=/opt/iotdb.backup.20260914
cp -r $BK/lib ./lib
cp -r $BK/sbin ./sbin
cp -r $BK/conf ./conf
cp -r $BK/tools ./tools
./sbin/start-standalone.sh

前提是回退前新版本没有写过数据。本周期 TsFile 从 2.3.1 升到了 2.4.0,用旧版本打开新版本 写过的数据目录通常不可行。如果新版本已经写入,回退要先处理这份数据,这一步没有通用的解法。

各场景的注意事项

  • 使用 Pipe 同步:升级后在接收端做一次同步验证,确认跨版本协议兼容
  • 使用 C++ 客户端:本周期改为默认 OpenSSL 3.x,客户端需要同步升级
  • 使用 AINode:本周期没有 AI 侧变更,无需跟进
  • 边缘场景:IoTDB Edge 第一版,后续版本会继续演进架构

参考资料

站内相关