This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 8ca049d8b8c Fix/unconsensus tsfile mods (#18008)
8ca049d8b8c is described below
commit 8ca049d8b8c4c87adac19fedf83a45572194be33
Author: Hongzhi Gao <[email protected]>
AuthorDate: Wed Jun 24 14:55:26 2026 +0800
Fix/unconsensus tsfile mods (#18008)
* Fix snapshot loader to keep tsfile and mods on the same data dir
When IoTConsensus snapshot fragments land in different receive folders,
share fileTarget across dirs so companion mods follow their tsfile. Add unit
and cluster IT coverage for delete visibility after region migrate.
* Revert unrelated occupied space cache IT config helpers
These setters were only needed for an uncommitted manual IT and are not
used by the snapshot loader fix.
* Skip missing recv paths when selecting snapshot receive folder
* Inline getOccupiedSpace call in MinFolderOccupiedSpaceFirstStrategy
* Fix IoTDBSnapshotTest to match 4-arg createLinksFromSnapshotToSourceDir
signature
The test reflectively invoked the old 3-arg
createLinksFromSnapshotToSourceDir(String, File[], FolderManager), but the
production method now takes a Map<String,String> fileTarget as a 4th
argument,
causing NoSuchMethodException. Look up the current signature, pass a
fileTarget
map, and assert the shared fileKey is recorded exactly once at one of the
data
dirs.
* Make IoTConsensus recv folder strategy follow dn_multi_dir_strategy and
treat missing folder as empty
Previously IoTConsensusServerImpl hardcoded
MIN_FOLDER_OCCUPIED_SPACE_FIRST_STRATEGY
for the snapshot receive folder. Thread the configured multi_dir_strategy
through
ConsensusConfig so the receiver follows dn_multi_dir_strategy (default
SequenceStrategy).
Add DirectoryStrategyType.fromClassName as the single class-name-to-type
mapping and
reuse it in TierManager.
Also fix JVMCommonUtils.getOccupiedSpace to return 0 for a non-existent
folder instead
of letting Files.walk throw NoSuchFileException, which previously cascaded
into a false
DiskSpaceInsufficientException and flipped the node to read-only while
receiving a
snapshot into a not-yet-created directory.
* Address review: warn on unrecognized multi-dir strategy and guard
occupied-space race
- DirectoryStrategyType.fromClassName now logs a warning when a non-null
strategy name is
unrecognized before falling back to SequenceStrategy.
- JVMCommonUtils.getOccupiedSpace re-checks file existence inside
mapToLong, since filter()
and mapToLong() are not evaluated atomically and a file may be deleted in
between.
* Drop redundant comment in getOccupiedSpace
* Use i18n UtilMessages for unrecognized multi-dir strategy warning
---
.../it/env/cluster/config/MppDataNodeConfig.java | 6 +
.../iotdb/it/env/cluster/node/DataNodeWrapper.java | 2 -
.../it/env/remote/config/RemoteDataNodeConfig.java | 5 +
.../apache/iotdb/itbase/env/DataNodeConfig.java | 2 +
...TDBRegionMigrateWithDeletionMultiDataDirIT.java | 180 +++++++++++++++++++++
.../iotdb/consensus/config/ConsensusConfig.java | 20 ++-
.../apache/iotdb/consensus/iot/IoTConsensus.java | 5 +
.../consensus/iot/IoTConsensusServerImpl.java | 5 +-
.../db/consensus/DataRegionConsensusImpl.java | 3 +
.../dataregion/snapshot/SnapshotLoader.java | 23 ++-
.../db/storageengine/rescon/disk/TierManager.java | 18 +--
.../dataregion/snapshot/IoTDBSnapshotTest.java | 97 ++++++++++-
.../apache/iotdb/commons/i18n/UtilMessages.java | 2 +
.../apache/iotdb/commons/i18n/UtilMessages.java | 2 +
.../disk/strategy/DirectoryStrategyType.java | 32 +++-
.../MinFolderOccupiedSpaceFirstStrategy.java | 3 +-
.../apache/iotdb/commons/utils/JVMCommonUtils.java | 11 +-
.../disk/strategy/DirectoryStrategyTypeTest.java | 63 ++++++++
.../iotdb/commons/utils/JVMCommonUtilsTest.java | 27 ++++
19 files changed, 471 insertions(+), 35 deletions(-)
diff --git
a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppDataNodeConfig.java
b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppDataNodeConfig.java
index 5e418072a7d..4caef4cc1f9 100644
---
a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppDataNodeConfig.java
+++
b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppDataNodeConfig.java
@@ -143,4 +143,10 @@ public class MppDataNodeConfig extends MppBaseConfig
implements DataNodeConfig {
setProperty("query_cost_stat_window", String.valueOf(queryCostStatWindow));
return this;
}
+
+ @Override
+ public DataNodeConfig setDnDataDirs(String dnDataDirs) {
+ setProperty("dn_data_dirs", dnDataDirs);
+ return this;
+ }
}
diff --git
a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/node/DataNodeWrapper.java
b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/node/DataNodeWrapper.java
index dac6cf3fcc3..d205044bd52 100644
---
a/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/node/DataNodeWrapper.java
+++
b/integration-test/src/main/java/org/apache/iotdb/it/env/cluster/node/DataNodeWrapper.java
@@ -44,7 +44,6 @@ import static
org.apache.iotdb.it.env.cluster.ClusterConstant.DEFAULT_DATA_NODE_
import static
org.apache.iotdb.it.env.cluster.ClusterConstant.DEFAULT_DATA_NODE_PROPERTIES;
import static
org.apache.iotdb.it.env.cluster.ClusterConstant.DN_CONNECTION_TIMEOUT_MS;
import static org.apache.iotdb.it.env.cluster.ClusterConstant.DN_CONSENSUS_DIR;
-import static org.apache.iotdb.it.env.cluster.ClusterConstant.DN_DATA_DIRS;
import static
org.apache.iotdb.it.env.cluster.ClusterConstant.DN_DATA_REGION_CONSENSUS_PORT;
import static
org.apache.iotdb.it.env.cluster.ClusterConstant.DN_JOIN_CLUSTER_RETRY_INTERVAL_MS;
import static
org.apache.iotdb.it.env.cluster.ClusterConstant.DN_METRIC_INTERNAL_REPORTER_TYPE;
@@ -125,7 +124,6 @@ public class DataNodeWrapper extends AbstractNodeWrapper {
immutableNodeProperties.setProperty(IoTDBConstant.DN_SEED_CONFIG_NODE,
seedConfigNode);
immutableNodeProperties.setProperty(DN_SYSTEM_DIR,
MppBaseConfig.NULL_VALUE);
- immutableNodeProperties.setProperty(DN_DATA_DIRS,
MppBaseConfig.NULL_VALUE);
immutableNodeProperties.setProperty(DN_CONSENSUS_DIR,
MppBaseConfig.NULL_VALUE);
immutableNodeProperties.setProperty(DN_WAL_DIRS, MppBaseConfig.NULL_VALUE);
immutableNodeProperties.setProperty(DN_TRACING_DIR,
MppBaseConfig.NULL_VALUE);
diff --git
a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteDataNodeConfig.java
b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteDataNodeConfig.java
index bba4c964f95..aa73d962dba 100644
---
a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteDataNodeConfig.java
+++
b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteDataNodeConfig.java
@@ -98,4 +98,9 @@ public class RemoteDataNodeConfig implements DataNodeConfig {
public DataNodeConfig setQueryCostStatWindow(int queryCostStatWindow) {
return this;
}
+
+ @Override
+ public DataNodeConfig setDnDataDirs(String dnDataDirs) {
+ return this;
+ }
}
diff --git
a/integration-test/src/main/java/org/apache/iotdb/itbase/env/DataNodeConfig.java
b/integration-test/src/main/java/org/apache/iotdb/itbase/env/DataNodeConfig.java
index d57015b1396..01a6114c206 100644
---
a/integration-test/src/main/java/org/apache/iotdb/itbase/env/DataNodeConfig.java
+++
b/integration-test/src/main/java/org/apache/iotdb/itbase/env/DataNodeConfig.java
@@ -53,4 +53,6 @@ public interface DataNodeConfig {
DataNodeConfig setDataNodeMemoryProportion(String dataNodeMemoryProportion);
DataNodeConfig setQueryCostStatWindow(int queryCostStatWindow);
+
+ DataNodeConfig setDnDataDirs(String dnDataDirs);
}
diff --git
a/integration-test/src/test/java/org/apache/iotdb/confignode/it/regionmigration/pass/daily/iotv1/IoTDBRegionMigrateWithDeletionMultiDataDirIT.java
b/integration-test/src/test/java/org/apache/iotdb/confignode/it/regionmigration/pass/daily/iotv1/IoTDBRegionMigrateWithDeletionMultiDataDirIT.java
new file mode 100644
index 00000000000..43b2d1bc992
--- /dev/null
+++
b/integration-test/src/test/java/org/apache/iotdb/confignode/it/regionmigration/pass/daily/iotv1/IoTDBRegionMigrateWithDeletionMultiDataDirIT.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.confignode.it.regionmigration.pass.daily.iotv1;
+
+import org.apache.iotdb.consensus.ConsensusFactory;
+import org.apache.iotdb.isession.SessionConfig;
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.ClusterIT;
+import org.apache.iotdb.itbase.env.BaseEnv;
+
+import org.apache.tsfile.utils.Pair;
+import org.awaitility.Awaitility;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import static
org.apache.iotdb.confignode.it.regionmigration.IoTDBRegionOperationReliabilityITFramework.getAllDataNodes;
+import static
org.apache.iotdb.confignode.it.regionmigration.IoTDBRegionOperationReliabilityITFramework.getDataRegionMapWithLeader;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({ClusterIT.class})
+public class IoTDBRegionMigrateWithDeletionMultiDataDirIT {
+
+ private static final String MULTI_DATA_DIRS =
+
"data/datanode/data/disk0,data/datanode/data/disk1,data/datanode/data/disk2";
+
+ @Before
+ public void setUp() throws Exception {
+ EnvFactory.getEnv()
+ .getConfig()
+ .getCommonConfig()
+ .setDataReplicationFactor(2)
+ .setDataRegionConsensusProtocolClass(ConsensusFactory.IOT_CONSENSUS);
+
EnvFactory.getEnv().getConfig().getDataNodeConfig().setDnDataDirs(MULTI_DATA_DIRS);
+ EnvFactory.getEnv().initClusterEnvironment(1, 3);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ EnvFactory.getEnv().cleanClusterEnvironment();
+ }
+
+ @Test
+ public void testRegionMigratePreservesDeletionWithMultiDataDirs() throws
Exception {
+ try (Connection connection = EnvFactory.getEnv().getTableConnection();
+ Statement statement = connection.createStatement()) {
+ statement.execute("CREATE DATABASE test");
+ statement.execute("USE test");
+ statement.execute("CREATE TABLE t1 (s1 INT64 FIELD)");
+ statement.execute("INSERT INTO t1 (time, s1) VALUES (100, 100), (200,
200), (300, 300)");
+ statement.execute("FLUSH");
+ statement.execute("DELETE FROM t1 WHERE time <= 200");
+ statement.execute("FLUSH");
+
+ Map<Integer, Pair<Integer, Set<Integer>>> dataRegionMapWithLeader =
+ getDataRegionMapWithLeader(statement);
+ int dataRegionIdForTest =
+
dataRegionMapWithLeader.keySet().stream().max(Integer::compareTo).orElseThrow();
+ assertDeletionVisibleOnAllReplicas(statement, dataRegionIdForTest, 1);
+
+ Pair<Integer, Set<Integer>> leaderAndNodes =
dataRegionMapWithLeader.get(dataRegionIdForTest);
+ Set<Integer> allDataNodes = getAllDataNodes(statement);
+ int leaderId = leaderAndNodes.getLeft();
+ int followerId =
+ leaderAndNodes.getRight().stream().filter(id -> id !=
leaderId).findFirst().orElseThrow();
+ int destDataNodeId =
+ allDataNodes.stream()
+ .filter(id -> id != leaderId && id != followerId)
+ .findFirst()
+ .orElseThrow();
+
+ statement.execute(
+ String.format(
+ "migrate region %d from %d to %d", dataRegionIdForTest,
leaderId, destDataNodeId));
+
+ final int finalDestDataNodeId = destDataNodeId;
+ Awaitility.await()
+ .atMost(10, TimeUnit.MINUTES)
+ .pollDelay(1, TimeUnit.SECONDS)
+ .pollInterval(2, TimeUnit.SECONDS)
+ .untilAsserted(
+ () -> {
+ try (ResultSet showRegions = statement.executeQuery("SHOW
REGIONS")) {
+ boolean migrated = false;
+ while (showRegions.next()) {
+ if (showRegions.getInt("RegionId") == dataRegionIdForTest
+ && showRegions.getInt("DataNodeId") ==
finalDestDataNodeId) {
+ migrated = true;
+ break;
+ }
+ }
+ Assert.assertTrue(migrated);
+ }
+ });
+
+ assertDeletionVisibleOnAllReplicas(statement, dataRegionIdForTest, 1);
+ }
+ }
+
+ private void assertDeletionVisibleOnAllReplicas(
+ Statement statement, int dataRegionId, int expectedCount) throws
Exception {
+ Set<Integer> replicaDataNodeIds = getReplicaDataNodeIds(statement,
dataRegionId);
+ for (int dataNodeId : replicaDataNodeIds) {
+ DataNodeWrapper dataNodeWrapper =
+ EnvFactory.getEnv().dataNodeIdToWrapper(dataNodeId).orElseThrow();
+ Awaitility.await()
+ .atMost(2, TimeUnit.MINUTES)
+ .pollDelay(500, TimeUnit.MILLISECONDS)
+ .pollInterval(1, TimeUnit.SECONDS)
+ .untilAsserted(() -> assertDeletionVisibleOnReplica(dataNodeWrapper,
expectedCount));
+ }
+ }
+
+ private void assertDeletionVisibleOnReplica(DataNodeWrapper dataNodeWrapper,
int expectedCount)
+ throws Exception {
+ try (Connection connection =
+ EnvFactory.getEnv()
+ .getConnection(
+ dataNodeWrapper,
+ SessionConfig.DEFAULT_USER,
+ SessionConfig.DEFAULT_PASSWORD,
+ BaseEnv.TABLE_SQL_DIALECT);
+ Statement dataNodeStatement = connection.createStatement()) {
+ dataNodeStatement.execute("USE test");
+ try (ResultSet countResultSet = dataNodeStatement.executeQuery("SELECT
COUNT(s1) FROM t1")) {
+ Assert.assertTrue(countResultSet.next());
+ Assert.assertEquals(expectedCount, countResultSet.getLong(1));
+ }
+ try (ResultSet deletedRangeResultSet =
+ dataNodeStatement.executeQuery("SELECT s1 FROM t1 WHERE time <=
200")) {
+ Assert.assertFalse(deletedRangeResultSet.next());
+ }
+ }
+ }
+
+ private Set<Integer> getReplicaDataNodeIds(Statement statement, int
dataRegionId)
+ throws Exception {
+ Set<Integer> replicaDataNodeIds = new HashSet<>();
+ try (ResultSet showRegions = statement.executeQuery("SHOW REGIONS")) {
+ while (showRegions.next()) {
+ if ("DataRegion".equals(showRegions.getString("Type"))
+ && showRegions.getInt("RegionId") == dataRegionId) {
+ replicaDataNodeIds.add(showRegions.getInt("DataNodeId"));
+ }
+ }
+ }
+ Assert.assertFalse(replicaDataNodeIds.isEmpty());
+ return replicaDataNodeIds;
+ }
+}
diff --git
a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/config/ConsensusConfig.java
b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/config/ConsensusConfig.java
index f834e41a4c9..d54299992fe 100644
---
a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/config/ConsensusConfig.java
+++
b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/config/ConsensusConfig.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.consensus.config;
import org.apache.iotdb.common.rpc.thrift.TConsensusGroupType;
import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+import org.apache.iotdb.commons.disk.strategy.DirectoryStrategyType;
import java.util.List;
import java.util.Optional;
@@ -35,6 +36,7 @@ public class ConsensusConfig {
private final RatisConfig ratisConfig;
private final IoTConsensusConfig iotConsensusConfig;
private final IoTConsensusV2Config iotConsensusV2Config;
+ private final DirectoryStrategyType directoryStrategyType;
private ConsensusConfig(
TEndPoint thisNode,
@@ -44,7 +46,8 @@ public class ConsensusConfig {
TConsensusGroupType consensusGroupType,
RatisConfig ratisConfig,
IoTConsensusConfig iotConsensusConfig,
- IoTConsensusV2Config iotConsensusV2Config) {
+ IoTConsensusV2Config iotConsensusV2Config,
+ DirectoryStrategyType directoryStrategyType) {
this.thisNodeEndPoint = thisNode;
this.thisNodeId = thisNodeId;
this.storageDir = storageDir;
@@ -53,6 +56,7 @@ public class ConsensusConfig {
this.ratisConfig = ratisConfig;
this.iotConsensusConfig = iotConsensusConfig;
this.iotConsensusV2Config = iotConsensusV2Config;
+ this.directoryStrategyType = directoryStrategyType;
}
public TEndPoint getThisNodeEndPoint() {
@@ -87,6 +91,10 @@ public class ConsensusConfig {
return iotConsensusV2Config;
}
+ public DirectoryStrategyType getDirectoryStrategyType() {
+ return directoryStrategyType;
+ }
+
public static ConsensusConfig.Builder newBuilder() {
return new ConsensusConfig.Builder();
}
@@ -101,6 +109,8 @@ public class ConsensusConfig {
private RatisConfig ratisConfig;
private IoTConsensusConfig iotConsensusConfig;
private IoTConsensusV2Config iotConsensusV2Config;
+ private DirectoryStrategyType directoryStrategyType =
+ DirectoryStrategyType.MIN_FOLDER_OCCUPIED_SPACE_FIRST_STRATEGY;
public ConsensusConfig build() {
return new ConsensusConfig(
@@ -113,7 +123,8 @@ public class ConsensusConfig {
Optional.ofNullable(iotConsensusConfig)
.orElseGet(() -> IoTConsensusConfig.newBuilder().build()),
Optional.ofNullable(iotConsensusV2Config)
- .orElseGet(() -> IoTConsensusV2Config.newBuilder().build()));
+ .orElseGet(() -> IoTConsensusV2Config.newBuilder().build()),
+ directoryStrategyType);
}
public Builder setThisNode(TEndPoint thisNode) {
@@ -155,5 +166,10 @@ public class ConsensusConfig {
this.iotConsensusV2Config = iotConsensusV2Config;
return this;
}
+
+ public Builder setDirectoryStrategyType(DirectoryStrategyType
directoryStrategyType) {
+ this.directoryStrategyType = directoryStrategyType;
+ return this;
+ }
}
}
diff --git
a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensus.java
b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensus.java
index dde577e39f7..946f6de7c1c 100644
---
a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensus.java
+++
b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensus.java
@@ -26,6 +26,7 @@ import
org.apache.iotdb.commons.concurrent.IoTDBThreadPoolFactory;
import org.apache.iotdb.commons.concurrent.ThreadName;
import org.apache.iotdb.commons.concurrent.threadpool.ScheduledExecutorUtil;
import org.apache.iotdb.commons.consensus.ConsensusGroupId;
+import org.apache.iotdb.commons.disk.strategy.DirectoryStrategyType;
import org.apache.iotdb.commons.exception.DiskSpaceInsufficientException;
import org.apache.iotdb.commons.exception.StartupException;
import org.apache.iotdb.commons.request.IConsensusRequest;
@@ -97,6 +98,7 @@ public class IoTConsensus implements IConsensus {
private final int thisNodeId;
private final File storageDir;
private final List<String> recvSnapshotDirs;
+ private final DirectoryStrategyType recvFolderStrategyType;
private final IStateMachine.Registry registry;
private final Map<ConsensusGroupId, IoTConsensusServerImpl> stateMachineMap =
new ConcurrentHashMap<>();
@@ -127,6 +129,7 @@ public class IoTConsensus implements IConsensus {
this.thisNodeId = config.getThisNodeId();
this.storageDir = new File(config.getStorageDir());
this.recvSnapshotDirs = config.getRecvSnapshotDirs();
+ this.recvFolderStrategyType = config.getDirectoryStrategyType();
this.config = config.getIotConsensusConfig();
this.registry = registry;
this.service = new IoTConsensusRPCService(thisNode,
config.getIotConsensusConfig());
@@ -195,6 +198,7 @@ public class IoTConsensus implements IConsensus {
new IoTConsensusServerImpl(
path.toString(),
recvSnapshotDirs,
+ recvFolderStrategyType,
new Peer(consensusGroupId, thisNodeId, thisNode),
new TreeSet<>(),
registry.apply(consensusGroupId),
@@ -309,6 +313,7 @@ public class IoTConsensus implements IConsensus {
new IoTConsensusServerImpl(
path,
recvSnapshotDirs,
+ recvFolderStrategyType,
new Peer(groupId, thisNodeId, thisNode),
new TreeSet<>(peers),
registry.apply(groupId),
diff --git
a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensusServerImpl.java
b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensusServerImpl.java
index 5b818db6301..0feb0098621 100644
---
a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensusServerImpl.java
+++
b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensusServerImpl.java
@@ -172,6 +172,7 @@ public class IoTConsensusServerImpl {
public IoTConsensusServerImpl(
String storageDir,
List<String> recvSnapshotDirs,
+ DirectoryStrategyType recvFolderStrategyType,
Peer thisNode,
TreeSet<Peer> configuration,
IStateMachine stateMachine,
@@ -191,9 +192,7 @@ public class IoTConsensusServerImpl {
snapshotDirs.add(storageDir);
}
- this.recvFolderManager =
- new FolderManager(
- snapshotDirs,
DirectoryStrategyType.MIN_FOLDER_OCCUPIED_SPACE_FIRST_STRATEGY);
+ this.recvFolderManager = new FolderManager(snapshotDirs,
recvFolderStrategyType);
this.thisNode = thisNode;
this.stateMachine = stateMachine;
this.cacheQueueMap = new ConcurrentHashMap<>();
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/DataRegionConsensusImpl.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/DataRegionConsensusImpl.java
index 080cd9c2095..a0735084fa5 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/DataRegionConsensusImpl.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/DataRegionConsensusImpl.java
@@ -25,6 +25,7 @@ import org.apache.iotdb.commons.conf.CommonConfig;
import org.apache.iotdb.commons.conf.CommonDescriptor;
import org.apache.iotdb.commons.consensus.ConsensusGroupId;
import org.apache.iotdb.commons.consensus.DataRegionId;
+import org.apache.iotdb.commons.disk.strategy.DirectoryStrategyType;
import org.apache.iotdb.commons.memory.IMemoryBlock;
import org.apache.iotdb.commons.memory.MemoryBlockType;
import org.apache.iotdb.commons.pipe.agent.plugin.builtin.BuiltinPipePlugin;
@@ -141,6 +142,8 @@ public class DataRegionConsensusImpl {
.setThisNode(new TEndPoint(CONF.getInternalAddress(),
CONF.getDataRegionConsensusPort()))
.setStorageDir(CONF.getDataRegionConsensusDir())
.setRecvSnapshotDirs(Arrays.asList(CONF.getLocalDataDirs()))
+ .setDirectoryStrategyType(
+
DirectoryStrategyType.fromClassName(CONF.getMultiDirStrategyClassName()))
.setConsensusGroupType(TConsensusGroupType.DataRegion)
.setIoTConsensusConfig(
IoTConsensusConfig.newBuilder()
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLoader.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLoader.java
index 17136cd24fb..588b0e21956 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLoader.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLoader.java
@@ -146,9 +146,14 @@ public class SnapshotLoader {
try {
deleteAllFilesInDataDirs();
LOGGER.info(StorageEngineMessages.REMOVE_ALL_DATA_FILES_IN_ORIGINAL_DIR);
+ // IoTConsensus may spread the fragments of one snapshot across several
receive folders.
+ // The fileTarget map must be shared across all of them so that a tsfile
and its companion
+ // files (resource, exclusive mods, etc.) are relinked to the same data
dir even when their
+ // fragments were received on different disks.
+ Map<String, String> fileTarget = new HashMap<>();
for (String path : snapshotPaths) {
File snapshotDir = new File(path);
- createLinksFromSnapshotDirToDataDirWithoutLog(snapshotDir);
+ createLinksFromSnapshotDirToDataDirWithoutLog(snapshotDir, fileTarget);
loadCompressionRatio(snapshotDir);
}
return loadSnapshot();
@@ -170,7 +175,7 @@ public class SnapshotLoader {
}
LOGGER.info(StorageEngineMessages.MOVING_SNAPSHOT_FILE_TO_DATA_DIRS);
File snapshotDir = new File(snapshotPath);
- createLinksFromSnapshotDirToDataDirWithoutLog(snapshotDir);
+ createLinksFromSnapshotDirToDataDirWithoutLog(snapshotDir, new
HashMap<>());
loadCompressionRatio(snapshotDir);
return loadSnapshot();
} catch (IOException | DiskSpaceInsufficientException e) {
@@ -294,7 +299,8 @@ public class SnapshotLoader {
}
}
- private void createLinksFromSnapshotDirToDataDirWithoutLog(File sourceDir)
+ private void createLinksFromSnapshotDirToDataDirWithoutLog(
+ File sourceDir, Map<String, String> fileTarget)
throws IOException, DiskSpaceInsufficientException {
if (!sourceDir.exists()) {
throw new IOException(
@@ -340,7 +346,7 @@ public class SnapshotLoader {
+ dataRegionId
+ File.separator
+ timePartitionFolder.getName();
- createLinksFromSnapshotToSourceDir(targetSuffix, files, folderManager);
+ createLinksFromSnapshotToSourceDir(targetSuffix, files, folderManager,
fileTarget);
}
}
@@ -359,7 +365,7 @@ public class SnapshotLoader {
+ dataRegionId
+ File.separator
+ timePartitionFolder.getName();
- createLinksFromSnapshotToSourceDir(targetSuffix, files, folderManager);
+ createLinksFromSnapshotToSourceDir(targetSuffix, files, folderManager,
fileTarget);
}
}
}
@@ -406,8 +412,11 @@ public class SnapshotLoader {
}
private void createLinksFromSnapshotToSourceDir(
- String targetSuffix, File[] files, FolderManager folderManager) throws
IOException {
- Map<String, String> fileTarget = new HashMap<>();
+ String targetSuffix,
+ File[] files,
+ FolderManager folderManager,
+ Map<String, String> fileTarget)
+ throws IOException {
for (File file : files) {
checkTsFileResourceExists(file);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManager.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManager.java
index 200be139664..561da825e9c 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManager.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManager.java
@@ -22,9 +22,6 @@ import org.apache.iotdb.commons.conf.CommonDescriptor;
import org.apache.iotdb.commons.conf.IoTDBConstant;
import org.apache.iotdb.commons.disk.FolderManager;
import org.apache.iotdb.commons.disk.strategy.DirectoryStrategyType;
-import org.apache.iotdb.commons.disk.strategy.MaxDiskUsableSpaceFirstStrategy;
-import
org.apache.iotdb.commons.disk.strategy.MinFolderOccupiedSpaceFirstStrategy;
-import org.apache.iotdb.commons.disk.strategy.RandomOnDiskUsableSpaceStrategy;
import org.apache.iotdb.commons.exception.DiskSpaceInsufficientException;
import org.apache.iotdb.db.conf.IoTDBConfig;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
@@ -98,19 +95,8 @@ public class TierManager {
}
public synchronized void initFolders() {
- try {
- String strategyName =
Class.forName(config.getMultiDirStrategyClassName()).getSimpleName();
- if
(strategyName.equals(MaxDiskUsableSpaceFirstStrategy.class.getSimpleName())) {
- directoryStrategyType =
DirectoryStrategyType.MAX_DISK_USABLE_SPACE_FIRST_STRATEGY;
- } else if
(strategyName.equals(MinFolderOccupiedSpaceFirstStrategy.class.getSimpleName()))
{
- directoryStrategyType =
DirectoryStrategyType.MIN_FOLDER_OCCUPIED_SPACE_FIRST_STRATEGY;
- } else if
(strategyName.equals(RandomOnDiskUsableSpaceStrategy.class.getSimpleName())) {
- directoryStrategyType =
DirectoryStrategyType.RANDOM_ON_DISK_USABLE_SPACE_STRATEGY;
- }
- } catch (Exception e) {
- logger.error(
- "Can't find strategy {} for mult-directories.",
config.getMultiDirStrategyClassName(), e);
- }
+ directoryStrategyType =
+
DirectoryStrategyType.fromClassName(config.getMultiDirStrategyClassName());
config.updatePath();
String[][] tierDirs = config.getTierDataDirs();
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/IoTDBSnapshotTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/IoTDBSnapshotTest.java
index d042d2a2ae8..ba7c9310945 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/IoTDBSnapshotTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/IoTDBSnapshotTest.java
@@ -52,7 +52,9 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import static
org.apache.iotdb.consensus.iot.IoTConsensusServerImpl.SNAPSHOT_DIR_NAME;
import static org.apache.tsfile.common.constant.TsFileConstant.PATH_SEPARATOR;
@@ -258,6 +260,51 @@ public class IoTDBSnapshotTest {
loadSnapshotSpreadAcrossReceiveFolders(false);
}
+ /**
+ * When IoTConsensus spreads a tsfile and its exclusive mods across
different receive folders, the
+ * loader must still relink them to the same data dir. Otherwise the mods
file is not found next
+ * to the tsfile and deletion markers are silently ignored.
+ */
+ @Test
+ public void
testLoadSnapshotKeepsTsFileAndModsOnSameDataDirWhenFragmentsAreSpread()
+ throws IOException, WriteProcessException {
+ String[][] originDataDirs =
IoTDBDescriptor.getInstance().getConfig().getTierDataDirs();
+ IoTDBDescriptor.getInstance().getConfig().setTierDataDirs(testDataDirs);
+ TierManager.getInstance().resetFolders();
+ String recvBase0 = "target" + File.separator + "recv-snapshot-mods-0";
+ String recvBase1 = "target" + File.separator + "recv-snapshot-mods-1";
+ File recvFolder0 = new File(recvBase0, SNAPSHOT_DIR_NAME);
+ File recvFolder1 = new File(recvBase1, SNAPSHOT_DIR_NAME);
+ try {
+ Assert.assertTrue(recvFolder0.mkdirs());
+ Assert.assertTrue(recvFolder1.mkdirs());
+
+
writeSnapshotFragmentWithExclusiveModsSpread(recvFolder0.getAbsolutePath(), 0,
recvFolder1);
+
+ DataRegion dataRegion =
+ new SnapshotLoader(
+ Arrays.asList(recvFolder0.getAbsolutePath(),
recvFolder1.getAbsolutePath()),
+ testSgName,
+ "0")
+ .loadSnapshotForStateMachine();
+
+ Assert.assertNotNull(dataRegion);
+ TsFileResource resource =
dataRegion.getTsFileManager().getTsFileList(true).get(0);
+ File tsFile = resource.getTsFile();
+ File modsFile =
+
org.apache.iotdb.db.storageengine.dataregion.modification.ModificationFile
+ .getExclusiveMods(tsFile);
+ Assert.assertTrue(modsFile.exists());
+ Assert.assertEquals(
+ tsFile.getParentFile().getAbsolutePath(),
modsFile.getParentFile().getAbsolutePath());
+ } finally {
+ FileUtils.recursivelyDeleteFolder(recvBase0);
+ FileUtils.recursivelyDeleteFolder(recvBase1);
+
IoTDBDescriptor.getInstance().getConfig().setTierDataDirs(originDataDirs);
+ TierManager.getInstance().resetFolders();
+ }
+ }
+
/**
* The fragments of one snapshot are disjoint across the receive folders, so
the order in which
* the folders are relinked must not change the loaded data. This loads the
same spread snapshot
@@ -343,6 +390,41 @@ public class IoTDBSnapshotTest {
resource.serialize();
}
+ private void writeSnapshotFragmentWithExclusiveModsSpread(
+ String tsFileRecvSnapshotDir, int i, File modsRecvFolder)
+ throws IOException, WriteProcessException {
+ writeSnapshotFragment(tsFileRecvSnapshotDir, i);
+ String tsFileName = String.format("%d-%d-0-0.tsfile", i + 1, i + 1);
+ File tsFile =
+ new File(
+ tsFileRecvSnapshotDir
+ + File.separator
+ + "sequence"
+ + File.separator
+ + testSgName
+ + File.separator
+ + "0"
+ + File.separator
+ + "0"
+ + File.separator
+ + tsFileName);
+ File sourceMods =
+
org.apache.iotdb.db.storageengine.dataregion.modification.ModificationFile.getExclusiveMods(
+ tsFile);
+ Assert.assertTrue(sourceMods.exists() || sourceMods.createNewFile());
+
+ File targetModsDir =
+ new File(
+ modsRecvFolder,
+ "sequence" + File.separator + testSgName + File.separator + "0" +
File.separator + "0");
+ Assert.assertTrue(targetModsDir.exists() || targetModsDir.mkdirs());
+ Files.copy(
+ sourceMods.toPath(),
+ new File(targetModsDir, sourceMods.getName()).toPath(),
+ java.nio.file.StandardCopyOption.REPLACE_EXISTING);
+ Files.delete(sourceMods.toPath());
+ }
+
@Ignore("Need manual execution to specify different disks")
@Test
public void testLoadSnapshotNoHardLink()
@@ -511,12 +593,23 @@ public class IoTDBSnapshotTest {
Method method =
SnapshotLoader.class.getDeclaredMethod(
- "createLinksFromSnapshotToSourceDir", String.class, File[].class,
FolderManager.class);
+ "createLinksFromSnapshotToSourceDir",
+ String.class,
+ File[].class,
+ FolderManager.class,
+ Map.class);
method.setAccessible(true);
SnapshotLoader loader = new SnapshotLoader("dummy", "root.testsg", "0");
- method.invoke(loader, targetSuffix, files, folderManager);
+ // Tracks fileKey -> chosen data dir, so files sharing a fileKey land in
the same dir.
+ Map<String, String> fileTarget = new HashMap<>();
+ method.invoke(loader, targetSuffix, files, folderManager, fileTarget);
+
+ // The shared fileKey must be recorded exactly once, pointing at one of
the data dirs.
+ String fileKey = tsFile.getName().split("\\.")[0];
+ Assert.assertEquals(1, fileTarget.size());
+
Assert.assertTrue(Arrays.asList(dataDirs).contains(fileTarget.get(fileKey)));
// verify: only ONE dir contains all three files
int hitDirCount = 0;
diff --git
a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/UtilMessages.java
b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/UtilMessages.java
index b13ad217f7a..395754dfb77 100644
---
a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/UtilMessages.java
+++
b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/UtilMessages.java
@@ -119,6 +119,8 @@ public final class UtilMessages {
"Disk space is insufficient, change system mode to read-only.";
public static final String CANNOT_CALCULATE_OCCUPIED_SPACE =
"Cannot calculate occupied space of folder {}";
+ public static final String UNRECOGNIZED_MULTI_DIR_STRATEGY =
+ "Unrecognized multi-dir strategy '{}', falling back to {}.";
// ======================== NodeUrlUtils ========================
diff --git
a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/UtilMessages.java
b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/UtilMessages.java
index f4215f6e449..1a1b97e7f95 100644
---
a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/UtilMessages.java
+++
b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/UtilMessages.java
@@ -117,6 +117,8 @@ public final class UtilMessages {
"磁盘空间不足,系统切换为只读模式。";
public static final String CANNOT_CALCULATE_OCCUPIED_SPACE =
"无法计算文件夹 {} 的已占用空间";
+ public static final String UNRECOGNIZED_MULTI_DIR_STRATEGY =
+ "无法识别的多目录策略 '{}',回退为 {}。";
// ======================== NodeUrlUtils ========================
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/disk/strategy/DirectoryStrategyType.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/disk/strategy/DirectoryStrategyType.java
index 2d081dd87f5..853edf899cf 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/disk/strategy/DirectoryStrategyType.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/disk/strategy/DirectoryStrategyType.java
@@ -18,9 +18,39 @@
*/
package org.apache.iotdb.commons.disk.strategy;
+import org.apache.iotdb.commons.i18n.UtilMessages;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
public enum DirectoryStrategyType {
SEQUENCE_STRATEGY,
MAX_DISK_USABLE_SPACE_FIRST_STRATEGY,
MIN_FOLDER_OCCUPIED_SPACE_FIRST_STRATEGY,
- RANDOM_ON_DISK_USABLE_SPACE_STRATEGY,
+ RANDOM_ON_DISK_USABLE_SPACE_STRATEGY;
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(DirectoryStrategyType.class);
+
+ /**
+ * Resolves the strategy type from a multi-dir strategy class name as
configured by {@code
+ * dn_multi_dir_strategy}. Accepts either a simple class name (e.g. {@code
SequenceStrategy}) or a
+ * fully-qualified one. Returns {@link #SEQUENCE_STRATEGY} for a null or
unrecognized value, which
+ * matches the configured default.
+ */
+ public static DirectoryStrategyType fromClassName(String className) {
+ if (className != null) {
+ String simpleName = className.substring(className.lastIndexOf('.') + 1);
+ if
(simpleName.equals(MaxDiskUsableSpaceFirstStrategy.class.getSimpleName())) {
+ return MAX_DISK_USABLE_SPACE_FIRST_STRATEGY;
+ } else if
(simpleName.equals(MinFolderOccupiedSpaceFirstStrategy.class.getSimpleName())) {
+ return MIN_FOLDER_OCCUPIED_SPACE_FIRST_STRATEGY;
+ } else if
(simpleName.equals(RandomOnDiskUsableSpaceStrategy.class.getSimpleName())) {
+ return RANDOM_ON_DISK_USABLE_SPACE_STRATEGY;
+ } else if (simpleName.equals(SequenceStrategy.class.getSimpleName())) {
+ return SEQUENCE_STRATEGY;
+ }
+ LOGGER.warn(UtilMessages.UNRECOGNIZED_MULTI_DIR_STRATEGY, className,
SEQUENCE_STRATEGY);
+ }
+ return SEQUENCE_STRATEGY;
+ }
}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/disk/strategy/MinFolderOccupiedSpaceFirstStrategy.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/disk/strategy/MinFolderOccupiedSpaceFirstStrategy.java
index 285b8b1d5b0..6a893d47aa1 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/disk/strategy/MinFolderOccupiedSpaceFirstStrategy.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/disk/strategy/MinFolderOccupiedSpaceFirstStrategy.java
@@ -25,6 +25,7 @@ import org.apache.iotdb.commons.utils.JVMCommonUtils;
import org.apache.iotdb.commons.utils.TestOnly;
import java.io.IOException;
+import java.io.UncheckedIOException;
/**
* Selects the folder with the least occupied space.
@@ -118,7 +119,7 @@ public class MinFolderOccupiedSpaceFirstStrategy extends
DirectoryStrategy {
}
try {
cachedOccupiedSpace[i] = JVMCommonUtils.getOccupiedSpace(folder);
- } catch (IOException e) {
+ } catch (IOException | UncheckedIOException e) {
LOGGER.error(UtilMessages.CANNOT_CALCULATE_OCCUPIED_SPACE, folder, e);
cachedOccupiedSpace[i] = Long.MAX_VALUE;
}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/JVMCommonUtils.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/JVMCommonUtils.java
index ccbe525d3d5..f041c46c9a3 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/JVMCommonUtils.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/JVMCommonUtils.java
@@ -111,8 +111,17 @@ public class JVMCommonUtils {
public static long getOccupiedSpace(String folderPath) throws IOException {
Path folder = Paths.get(folderPath);
+ if (!Files.exists(folder)) {
+ return 0;
+ }
try (Stream<Path> s = Files.walk(folder)) {
- return s.filter(p -> p.toFile().isFile()).mapToLong(p ->
p.toFile().length()).sum();
+ return s.filter(p -> p.toFile().isFile())
+ .mapToLong(
+ p -> {
+ File file = p.toFile();
+ return file.exists() ? file.length() : 0L;
+ })
+ .sum();
}
}
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/disk/strategy/DirectoryStrategyTypeTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/disk/strategy/DirectoryStrategyTypeTest.java
new file mode 100644
index 00000000000..90e56cf8f0b
--- /dev/null
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/disk/strategy/DirectoryStrategyTypeTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.disk.strategy;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DirectoryStrategyTypeTest {
+
+ @Test
+ public void fromSimpleClassName() {
+ Assert.assertEquals(
+ DirectoryStrategyType.SEQUENCE_STRATEGY,
+ DirectoryStrategyType.fromClassName("SequenceStrategy"));
+ Assert.assertEquals(
+ DirectoryStrategyType.MAX_DISK_USABLE_SPACE_FIRST_STRATEGY,
+
DirectoryStrategyType.fromClassName("MaxDiskUsableSpaceFirstStrategy"));
+ Assert.assertEquals(
+ DirectoryStrategyType.MIN_FOLDER_OCCUPIED_SPACE_FIRST_STRATEGY,
+
DirectoryStrategyType.fromClassName("MinFolderOccupiedSpaceFirstStrategy"));
+ Assert.assertEquals(
+ DirectoryStrategyType.RANDOM_ON_DISK_USABLE_SPACE_STRATEGY,
+
DirectoryStrategyType.fromClassName("RandomOnDiskUsableSpaceStrategy"));
+ }
+
+ @Test
+ public void fromFullyQualifiedClassName() {
+ Assert.assertEquals(
+ DirectoryStrategyType.SEQUENCE_STRATEGY,
+ DirectoryStrategyType.fromClassName(SequenceStrategy.class.getName()));
+ Assert.assertEquals(
+ DirectoryStrategyType.MIN_FOLDER_OCCUPIED_SPACE_FIRST_STRATEGY,
+
DirectoryStrategyType.fromClassName(MinFolderOccupiedSpaceFirstStrategy.class.getName()));
+ }
+
+ @Test
+ public void nullOrUnknownFallsBackToSequence() {
+ // The configured default (dn_multi_dir_strategy=SequenceStrategy) and any
unrecognized value
+ // must resolve to SEQUENCE_STRATEGY.
+ Assert.assertEquals(
+ DirectoryStrategyType.SEQUENCE_STRATEGY,
DirectoryStrategyType.fromClassName(null));
+ Assert.assertEquals(
+ DirectoryStrategyType.SEQUENCE_STRATEGY,
+ DirectoryStrategyType.fromClassName("NoSuchStrategy"));
+ }
+}
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/JVMCommonUtilsTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/JVMCommonUtilsTest.java
index 4a247cfb765..d35d3a52ea2 100644
---
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/JVMCommonUtilsTest.java
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/JVMCommonUtilsTest.java
@@ -20,10 +20,19 @@
package org.apache.iotdb.commons.utils;
import org.junit.Assert;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
public class JVMCommonUtilsTest {
+ @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
+
@Test
public void getJdkVersionTest() {
try {
@@ -39,4 +48,22 @@ public class JVMCommonUtilsTest {
Assert.fail();
}
}
+
+ @Test
+ public void getOccupiedSpaceMissingFolderReturnsZero() throws IOException {
+ File missing = new File(tempFolder.getRoot(), "does-not-exist");
+ Assert.assertFalse(missing.exists());
+ // A non-existent folder must be treated as empty rather than throwing
NoSuchFileException.
+ Assert.assertEquals(0L,
JVMCommonUtils.getOccupiedSpace(missing.getAbsolutePath()));
+ }
+
+ @Test
+ public void getOccupiedSpaceSumsFileSizes() throws IOException {
+ File dir = tempFolder.newFolder("data");
+ byte[] payload = "hello-iotdb".getBytes(StandardCharsets.UTF_8);
+ Files.write(new File(dir, "a.txt").toPath(), payload);
+ Files.write(new File(dir, "b.txt").toPath(), payload);
+ Assert.assertEquals(
+ 2L * payload.length,
JVMCommonUtils.getOccupiedSpace(dir.getAbsolutePath()));
+ }
}