Apache IoTDB V2.0.11 版本解析:JDK 17、IoTDB Edge 与元数据租约
写在前面
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.10 | V2.0.11 |
|---|---|---|
maven.compiler.source / target | 1.8 | 17 |
| TsFile | 2.3.1 | 2.4.0 |
| Apache Ratis | 3.2.2 | 3.3.0 |
| Apache Thrift | 0.14.1 | 0.23.0 |
iotdb-tools-thrift | 0.14.1.0 | 0.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.shEdgeNode.main() 处理的是两个服务如何在一个进程里有序启动。ConfigNode 先在后台线程启动,主线程以 500ms 间隔探测 ConfigNode 的内部 RPC 端口,最长等 300 秒;端口可连接后再等 5 秒,让单节点 ConfigNode 的 Ratis 选主完成,然后 DataNode 在主线程启动。两个服务各靠自己的非 daemon 线程维持 JVM 存活。任一节点致命失败会终止整个进程,源码注释里写明这是预期的单进程语义,进程内不做隔离。
内存参数在 scripts/conf/edge-env.sh:
| 参数 | 默认值 | 说明 |
|---|---|---|
ON_HEAP_MEMORY | 224M | 堆上限(-Xmx) |
INIT_HEAP_MEMORY | 64M | 初始堆 |
OFF_HEAP_MEMORY | 96M | 直接内存(-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.yml、integration-test/src/main/java/org/apache/iotdb/itbase/category/EdgeIT.java,以及 Windows 侧的 test-edge-windows.ps1 和 check-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 内部维护三个状态:NORMAL、CACHE_CLEARING、CACHE_CLEARED。心跳超时后进入清缓存流程,重新收到心跳并完成元数据拉取后回到 NORMAL。两处实现细节:
- 时钟用
System.nanoTime()。类注释说明是为了不受墙上时钟调整影响。租约判定如果依赖系统时间,一次 NTP 校时就可能误触发或漏触发。 - 隔离动作是一个 action 列表,由各子系统注册。租约管理器本身不关心有哪些缓存要清。diff 里能看到按子系统拆分的测试:
DataNodeTableCacheLeaseTest、PartitionCacheLeaseTest、ClusterAuthorityFetcherLeaseTest。 - 隔离期间的行为是 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.g4,querySpecification 规则逐字相同,FROM 早就是可选的:
querySpecification
: SELECT setQuantifier? selectItem (',' selectItem)*
(FROM relation (',' relation)*)? <- 可选
(WHERE where=booleanExpression)?
...缺的是执行链路。#17437 只改了 5 个文件、77 行:QueryPlanner、ValuesNode、TableOperatorGenerator、UnaliasSymbolReferences。这和 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?。它复用了已有的 timeBoundClause、timeColumnClause、fillGroupClause,说明填充功能的语法骨架此前已经搭好,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-commons 的 udf/builtin/relational/tvf/ 下。
M4 是时序可视化里的降采样聚合方法,每个窗口取 Min、Max、Mean、Median 四个点,用四个点保留窗口的形态特征。它的参数包括 DATA、TIMECOL、SIZE、SLIDE、ORIGIN,同时支持时间窗口(TimeWindowM4DataProcessor)和计数窗口(CountWindowM4DataProcessor),支持分组列和多值列。分区列允许 BLOB 类型,代码注释解释是 M4 只需要在分区列上读写、不需要比较。
把降采样下推到数据库里,可以省掉把全量数据拉到前端再抽样这一步。
其他语句与函数
- #17292:自相关、偏度、线性回归等统计类聚合函数
- #16545:
PERCENTILE聚合函数 - #17456:
CAPACITY表值函数支持SLIDE参数 COUNT DATABASE(#17705),不过这个实现在 2.0.10 分支上同样存在
变更五:运维、安全与内存
配置热加载
#17975 支持集群运行时配置热加载。配套修了 #18108,SHOW CONFIGURATION 之前显示原始值而不是生效值。这两个缺一不可:只有 SHOW CONFIGURATION 能看不能改,只有热加载能改但看不准。
本周期还顺带修了几处热加载的边界问题:#18326(拒绝 NaN 磁盘告警阈值)、#18336(WAL 限流阈值回退)、#18327(无效 WAL 限流阈值持久化)、#18091(procedure 清理器与磁盘阈值)。
通信加密
三个 PR 补上了加密能力:
单向 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 PIPE、SHOW CREATE DATABASE、SHOW CREATE TOPIC和ALTER TOPIC,30 个文件、+1,432 行 - #17637:新增
SHOW REPAIR DATA PARTITION TABLE PROGRESS - #18046:
MIGRATE 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
#18096 是 1000-01-01 这个边界值触发的崩溃,属于只有真拿到这个值才会复现的问题。
变更七:中文 i18n
这一项没有出现在 Release Notes 的功能列表里,但从代码量看是本周期最大的单项工程之一。
| 指标 | V2.0.10 | V2.0.11 |
|---|---|---|
| 英文 i18n 消息键总数 | 5,178 | 9,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 覆盖 master、rel/*、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.g4、IoTDBSqlParser.g4、RelationalSql.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.10 和 rc/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 USER、CREATE USER、DROP USER并发执行返回301: Ratis request failed Unknown,CN 日志报ConcurrentModificationException- 重启后某节点停在 Unknown 状态且无日志,主线程被阻塞,但该节点仍能正常做 compaction
SHOW REGIONS中CompressionRatio在缩容期间和重启后显示负数或 NaNLOAD 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
doneglob 按文件名排序展开,旧版本号(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 第一版,后续版本会继续演进架构
参考资料
- Apache IoTDB 2.0.11 Release Notes
- V2.0.10 → V2.0.11 完整 Changelog(305 commits)
- JDK 17 与 Jakarta 迁移 PR #17859
- IoTDB Edge 单 JVM 合并 PR #18538
- 元数据租约自隔离框架 commit d02e6af5
- Authority 模块高可用 PR #18425
- 集群运行时配置热加载 PR #17975
- Thrift 客户端双向 TLS PR #18026
- 中文 i18n 与 zh-compile CI PR #18110
- Node.js 客户端仓库 apache/iotdb-client-nodejs
- IoTDB 官网下载页