Skip to content

Commit b0a27a4

Browse files
hongzhi-gaoJackieTien97
authored andcommitted
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
1 parent 5fc7f26 commit b0a27a4

19 files changed

Lines changed: 471 additions & 35 deletions

File tree

‎integration-test/src/main/java/org/apache/iotdb/it/env/cluster/config/MppDataNodeConfig.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,10 @@ public DataNodeConfig setQueryCostStatWindow(int queryCostStatWindow) {
143143
setProperty("query_cost_stat_window", String.valueOf(queryCostStatWindow));
144144
return this;
145145
}
146+
147+
@Override
148+
public DataNodeConfig setDnDataDirs(String dnDataDirs) {
149+
setProperty("dn_data_dirs", dnDataDirs);
150+
return this;
151+
}
146152
}

‎integration-test/src/main/java/org/apache/iotdb/it/env/cluster/node/DataNodeWrapper.java‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
import static org.apache.iotdb.it.env.cluster.ClusterConstant.DEFAULT_DATA_NODE_PROPERTIES;
4545
import static org.apache.iotdb.it.env.cluster.ClusterConstant.DN_CONNECTION_TIMEOUT_MS;
4646
import static org.apache.iotdb.it.env.cluster.ClusterConstant.DN_CONSENSUS_DIR;
47-
import static org.apache.iotdb.it.env.cluster.ClusterConstant.DN_DATA_DIRS;
4847
import static org.apache.iotdb.it.env.cluster.ClusterConstant.DN_DATA_REGION_CONSENSUS_PORT;
4948
import static org.apache.iotdb.it.env.cluster.ClusterConstant.DN_JOIN_CLUSTER_RETRY_INTERVAL_MS;
5049
import static org.apache.iotdb.it.env.cluster.ClusterConstant.DN_METRIC_INTERNAL_REPORTER_TYPE;
@@ -125,7 +124,6 @@ public DataNodeWrapper(
125124

126125
immutableNodeProperties.setProperty(IoTDBConstant.DN_SEED_CONFIG_NODE, seedConfigNode);
127126
immutableNodeProperties.setProperty(DN_SYSTEM_DIR, MppBaseConfig.NULL_VALUE);
128-
immutableNodeProperties.setProperty(DN_DATA_DIRS, MppBaseConfig.NULL_VALUE);
129127
immutableNodeProperties.setProperty(DN_CONSENSUS_DIR, MppBaseConfig.NULL_VALUE);
130128
immutableNodeProperties.setProperty(DN_WAL_DIRS, MppBaseConfig.NULL_VALUE);
131129
immutableNodeProperties.setProperty(DN_TRACING_DIR, MppBaseConfig.NULL_VALUE);

‎integration-test/src/main/java/org/apache/iotdb/it/env/remote/config/RemoteDataNodeConfig.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,9 @@ public DataNodeConfig setDataNodeMemoryProportion(String dataNodeMemoryProportio
9898
public DataNodeConfig setQueryCostStatWindow(int queryCostStatWindow) {
9999
return this;
100100
}
101+
102+
@Override
103+
public DataNodeConfig setDnDataDirs(String dnDataDirs) {
104+
return this;
105+
}
101106
}

‎integration-test/src/main/java/org/apache/iotdb/itbase/env/DataNodeConfig.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,6 @@ DataNodeConfig setLoadTsFileAnalyzeSchemaMemorySizeInBytes(
5353
DataNodeConfig setDataNodeMemoryProportion(String dataNodeMemoryProportion);
5454

5555
DataNodeConfig setQueryCostStatWindow(int queryCostStatWindow);
56+
57+
DataNodeConfig setDnDataDirs(String dnDataDirs);
5658
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iotdb.confignode.it.regionmigration.pass.daily.iotv1;
21+
22+
import org.apache.iotdb.consensus.ConsensusFactory;
23+
import org.apache.iotdb.isession.SessionConfig;
24+
import org.apache.iotdb.it.env.EnvFactory;
25+
import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
26+
import org.apache.iotdb.it.framework.IoTDBTestRunner;
27+
import org.apache.iotdb.itbase.category.ClusterIT;
28+
import org.apache.iotdb.itbase.env.BaseEnv;
29+
30+
import org.apache.tsfile.utils.Pair;
31+
import org.awaitility.Awaitility;
32+
import org.junit.After;
33+
import org.junit.Assert;
34+
import org.junit.Before;
35+
import org.junit.Test;
36+
import org.junit.experimental.categories.Category;
37+
import org.junit.runner.RunWith;
38+
39+
import java.sql.Connection;
40+
import java.sql.ResultSet;
41+
import java.sql.Statement;
42+
import java.util.HashSet;
43+
import java.util.Map;
44+
import java.util.Set;
45+
import java.util.concurrent.TimeUnit;
46+
47+
import static org.apache.iotdb.confignode.it.regionmigration.IoTDBRegionOperationReliabilityITFramework.getAllDataNodes;
48+
import static org.apache.iotdb.confignode.it.regionmigration.IoTDBRegionOperationReliabilityITFramework.getDataRegionMapWithLeader;
49+
50+
@RunWith(IoTDBTestRunner.class)
51+
@Category({ClusterIT.class})
52+
public class IoTDBRegionMigrateWithDeletionMultiDataDirIT {
53+
54+
private static final String MULTI_DATA_DIRS =
55+
"data/datanode/data/disk0,data/datanode/data/disk1,data/datanode/data/disk2";
56+
57+
@Before
58+
public void setUp() throws Exception {
59+
EnvFactory.getEnv()
60+
.getConfig()
61+
.getCommonConfig()
62+
.setDataReplicationFactor(2)
63+
.setDataRegionConsensusProtocolClass(ConsensusFactory.IOT_CONSENSUS);
64+
EnvFactory.getEnv().getConfig().getDataNodeConfig().setDnDataDirs(MULTI_DATA_DIRS);
65+
EnvFactory.getEnv().initClusterEnvironment(1, 3);
66+
}
67+
68+
@After
69+
public void tearDown() throws Exception {
70+
EnvFactory.getEnv().cleanClusterEnvironment();
71+
}
72+
73+
@Test
74+
public void testRegionMigratePreservesDeletionWithMultiDataDirs() throws Exception {
75+
try (Connection connection = EnvFactory.getEnv().getTableConnection();
76+
Statement statement = connection.createStatement()) {
77+
statement.execute("CREATE DATABASE test");
78+
statement.execute("USE test");
79+
statement.execute("CREATE TABLE t1 (s1 INT64 FIELD)");
80+
statement.execute("INSERT INTO t1 (time, s1) VALUES (100, 100), (200, 200), (300, 300)");
81+
statement.execute("FLUSH");
82+
statement.execute("DELETE FROM t1 WHERE time <= 200");
83+
statement.execute("FLUSH");
84+
85+
Map<Integer, Pair<Integer, Set<Integer>>> dataRegionMapWithLeader =
86+
getDataRegionMapWithLeader(statement);
87+
int dataRegionIdForTest =
88+
dataRegionMapWithLeader.keySet().stream().max(Integer::compareTo).orElseThrow();
89+
assertDeletionVisibleOnAllReplicas(statement, dataRegionIdForTest, 1);
90+
91+
Pair<Integer, Set<Integer>> leaderAndNodes = dataRegionMapWithLeader.get(dataRegionIdForTest);
92+
Set<Integer> allDataNodes = getAllDataNodes(statement);
93+
int leaderId = leaderAndNodes.getLeft();
94+
int followerId =
95+
leaderAndNodes.getRight().stream().filter(id -> id != leaderId).findFirst().orElseThrow();
96+
int destDataNodeId =
97+
allDataNodes.stream()
98+
.filter(id -> id != leaderId && id != followerId)
99+
.findFirst()
100+
.orElseThrow();
101+
102+
statement.execute(
103+
String.format(
104+
"migrate region %d from %d to %d", dataRegionIdForTest, leaderId, destDataNodeId));
105+
106+
final int finalDestDataNodeId = destDataNodeId;
107+
Awaitility.await()
108+
.atMost(10, TimeUnit.MINUTES)
109+
.pollDelay(1, TimeUnit.SECONDS)
110+
.pollInterval(2, TimeUnit.SECONDS)
111+
.untilAsserted(
112+
() -> {
113+
try (ResultSet showRegions = statement.executeQuery("SHOW REGIONS")) {
114+
boolean migrated = false;
115+
while (showRegions.next()) {
116+
if (showRegions.getInt("RegionId") == dataRegionIdForTest
117+
&& showRegions.getInt("DataNodeId") == finalDestDataNodeId) {
118+
migrated = true;
119+
break;
120+
}
121+
}
122+
Assert.assertTrue(migrated);
123+
}
124+
});
125+
126+
assertDeletionVisibleOnAllReplicas(statement, dataRegionIdForTest, 1);
127+
}
128+
}
129+
130+
private void assertDeletionVisibleOnAllReplicas(
131+
Statement statement, int dataRegionId, int expectedCount) throws Exception {
132+
Set<Integer> replicaDataNodeIds = getReplicaDataNodeIds(statement, dataRegionId);
133+
for (int dataNodeId : replicaDataNodeIds) {
134+
DataNodeWrapper dataNodeWrapper =
135+
EnvFactory.getEnv().dataNodeIdToWrapper(dataNodeId).orElseThrow();
136+
Awaitility.await()
137+
.atMost(2, TimeUnit.MINUTES)
138+
.pollDelay(500, TimeUnit.MILLISECONDS)
139+
.pollInterval(1, TimeUnit.SECONDS)
140+
.untilAsserted(() -> assertDeletionVisibleOnReplica(dataNodeWrapper, expectedCount));
141+
}
142+
}
143+
144+
private void assertDeletionVisibleOnReplica(DataNodeWrapper dataNodeWrapper, int expectedCount)
145+
throws Exception {
146+
try (Connection connection =
147+
EnvFactory.getEnv()
148+
.getConnection(
149+
dataNodeWrapper,
150+
SessionConfig.DEFAULT_USER,
151+
SessionConfig.DEFAULT_PASSWORD,
152+
BaseEnv.TABLE_SQL_DIALECT);
153+
Statement dataNodeStatement = connection.createStatement()) {
154+
dataNodeStatement.execute("USE test");
155+
try (ResultSet countResultSet = dataNodeStatement.executeQuery("SELECT COUNT(s1) FROM t1")) {
156+
Assert.assertTrue(countResultSet.next());
157+
Assert.assertEquals(expectedCount, countResultSet.getLong(1));
158+
}
159+
try (ResultSet deletedRangeResultSet =
160+
dataNodeStatement.executeQuery("SELECT s1 FROM t1 WHERE time <= 200")) {
161+
Assert.assertFalse(deletedRangeResultSet.next());
162+
}
163+
}
164+
}
165+
166+
private Set<Integer> getReplicaDataNodeIds(Statement statement, int dataRegionId)
167+
throws Exception {
168+
Set<Integer> replicaDataNodeIds = new HashSet<>();
169+
try (ResultSet showRegions = statement.executeQuery("SHOW REGIONS")) {
170+
while (showRegions.next()) {
171+
if ("DataRegion".equals(showRegions.getString("Type"))
172+
&& showRegions.getInt("RegionId") == dataRegionId) {
173+
replicaDataNodeIds.add(showRegions.getInt("DataNodeId"));
174+
}
175+
}
176+
}
177+
Assert.assertFalse(replicaDataNodeIds.isEmpty());
178+
return replicaDataNodeIds;
179+
}
180+
}

‎iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/config/ConsensusConfig.java‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.apache.iotdb.common.rpc.thrift.TConsensusGroupType;
2323
import org.apache.iotdb.common.rpc.thrift.TEndPoint;
24+
import org.apache.iotdb.commons.disk.strategy.DirectoryStrategyType;
2425

2526
import java.util.List;
2627
import java.util.Optional;
@@ -35,6 +36,7 @@ public class ConsensusConfig {
3536
private final RatisConfig ratisConfig;
3637
private final IoTConsensusConfig iotConsensusConfig;
3738
private final IoTConsensusV2Config iotConsensusV2Config;
39+
private final DirectoryStrategyType directoryStrategyType;
3840

3941
private ConsensusConfig(
4042
TEndPoint thisNode,
@@ -44,7 +46,8 @@ private ConsensusConfig(
4446
TConsensusGroupType consensusGroupType,
4547
RatisConfig ratisConfig,
4648
IoTConsensusConfig iotConsensusConfig,
47-
IoTConsensusV2Config iotConsensusV2Config) {
49+
IoTConsensusV2Config iotConsensusV2Config,
50+
DirectoryStrategyType directoryStrategyType) {
4851
this.thisNodeEndPoint = thisNode;
4952
this.thisNodeId = thisNodeId;
5053
this.storageDir = storageDir;
@@ -53,6 +56,7 @@ private ConsensusConfig(
5356
this.ratisConfig = ratisConfig;
5457
this.iotConsensusConfig = iotConsensusConfig;
5558
this.iotConsensusV2Config = iotConsensusV2Config;
59+
this.directoryStrategyType = directoryStrategyType;
5660
}
5761

5862
public TEndPoint getThisNodeEndPoint() {
@@ -87,6 +91,10 @@ public IoTConsensusV2Config getIoTConsensusV2Config() {
8791
return iotConsensusV2Config;
8892
}
8993

94+
public DirectoryStrategyType getDirectoryStrategyType() {
95+
return directoryStrategyType;
96+
}
97+
9098
public static ConsensusConfig.Builder newBuilder() {
9199
return new ConsensusConfig.Builder();
92100
}
@@ -101,6 +109,8 @@ public static class Builder {
101109
private RatisConfig ratisConfig;
102110
private IoTConsensusConfig iotConsensusConfig;
103111
private IoTConsensusV2Config iotConsensusV2Config;
112+
private DirectoryStrategyType directoryStrategyType =
113+
DirectoryStrategyType.MIN_FOLDER_OCCUPIED_SPACE_FIRST_STRATEGY;
104114

105115
public ConsensusConfig build() {
106116
return new ConsensusConfig(
@@ -113,7 +123,8 @@ public ConsensusConfig build() {
113123
Optional.ofNullable(iotConsensusConfig)
114124
.orElseGet(() -> IoTConsensusConfig.newBuilder().build()),
115125
Optional.ofNullable(iotConsensusV2Config)
116-
.orElseGet(() -> IoTConsensusV2Config.newBuilder().build()));
126+
.orElseGet(() -> IoTConsensusV2Config.newBuilder().build()),
127+
directoryStrategyType);
117128
}
118129

119130
public Builder setThisNode(TEndPoint thisNode) {
@@ -155,5 +166,10 @@ public Builder setIoTConsensusV2Config(IoTConsensusV2Config iotConsensusV2Config
155166
this.iotConsensusV2Config = iotConsensusV2Config;
156167
return this;
157168
}
169+
170+
public Builder setDirectoryStrategyType(DirectoryStrategyType directoryStrategyType) {
171+
this.directoryStrategyType = directoryStrategyType;
172+
return this;
173+
}
158174
}
159175
}

‎iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensus.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.apache.iotdb.commons.concurrent.ThreadName;
2727
import org.apache.iotdb.commons.concurrent.threadpool.ScheduledExecutorUtil;
2828
import org.apache.iotdb.commons.consensus.ConsensusGroupId;
29+
import org.apache.iotdb.commons.disk.strategy.DirectoryStrategyType;
2930
import org.apache.iotdb.commons.exception.DiskSpaceInsufficientException;
3031
import org.apache.iotdb.commons.exception.StartupException;
3132
import org.apache.iotdb.commons.request.IConsensusRequest;
@@ -96,6 +97,7 @@ public class IoTConsensus implements IConsensus {
9697
private final int thisNodeId;
9798
private final File storageDir;
9899
private final List<String> recvSnapshotDirs;
100+
private final DirectoryStrategyType recvFolderStrategyType;
99101
private final IStateMachine.Registry registry;
100102
private final Map<ConsensusGroupId, IoTConsensusServerImpl> stateMachineMap =
101103
new ConcurrentHashMap<>();
@@ -113,6 +115,7 @@ public IoTConsensus(ConsensusConfig config, Registry registry) {
113115
this.thisNodeId = config.getThisNodeId();
114116
this.storageDir = new File(config.getStorageDir());
115117
this.recvSnapshotDirs = config.getRecvSnapshotDirs();
118+
this.recvFolderStrategyType = config.getDirectoryStrategyType();
116119
this.config = config.getIotConsensusConfig();
117120
this.registry = registry;
118121
this.service = new IoTConsensusRPCService(thisNode, config.getIotConsensusConfig());
@@ -181,6 +184,7 @@ private void initAndRecover() throws IOException {
181184
new IoTConsensusServerImpl(
182185
path.toString(),
183186
recvSnapshotDirs,
187+
recvFolderStrategyType,
184188
new Peer(consensusGroupId, thisNodeId, thisNode),
185189
new TreeSet<>(),
186190
registry.apply(consensusGroupId),
@@ -295,6 +299,7 @@ public void createLocalPeer(ConsensusGroupId groupId, List<Peer> peers)
295299
new IoTConsensusServerImpl(
296300
path,
297301
recvSnapshotDirs,
302+
recvFolderStrategyType,
298303
new Peer(groupId, thisNodeId, thisNode),
299304
new TreeSet<>(peers),
300305
registry.apply(groupId),

‎iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensusServerImpl.java‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ public class IoTConsensusServerImpl {
137137
public IoTConsensusServerImpl(
138138
String storageDir,
139139
List<String> recvSnapshotDirs,
140+
DirectoryStrategyType recvFolderStrategyType,
140141
Peer thisNode,
141142
TreeSet<Peer> configuration,
142143
IStateMachine stateMachine,
@@ -156,9 +157,7 @@ public IoTConsensusServerImpl(
156157
snapshotDirs.add(storageDir);
157158
}
158159

159-
this.recvFolderManager =
160-
new FolderManager(
161-
snapshotDirs, DirectoryStrategyType.MIN_FOLDER_OCCUPIED_SPACE_FIRST_STRATEGY);
160+
this.recvFolderManager = new FolderManager(snapshotDirs, recvFolderStrategyType);
162161
this.thisNode = thisNode;
163162
this.stateMachine = stateMachine;
164163
this.cacheQueueMap = new ConcurrentHashMap<>();

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/DataRegionConsensusImpl.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.apache.iotdb.commons.conf.CommonDescriptor;
2626
import org.apache.iotdb.commons.consensus.ConsensusGroupId;
2727
import org.apache.iotdb.commons.consensus.DataRegionId;
28+
import org.apache.iotdb.commons.disk.strategy.DirectoryStrategyType;
2829
import org.apache.iotdb.commons.memory.IMemoryBlock;
2930
import org.apache.iotdb.commons.memory.MemoryBlockType;
3031
import org.apache.iotdb.commons.pipe.agent.plugin.builtin.BuiltinPipePlugin;
@@ -141,6 +142,8 @@ private static ConsensusConfig buildConsensusConfig() {
141142
.setThisNode(new TEndPoint(CONF.getInternalAddress(), CONF.getDataRegionConsensusPort()))
142143
.setStorageDir(CONF.getDataRegionConsensusDir())
143144
.setRecvSnapshotDirs(Arrays.asList(CONF.getLocalDataDirs()))
145+
.setDirectoryStrategyType(
146+
DirectoryStrategyType.fromClassName(CONF.getMultiDirStrategyClassName()))
144147
.setConsensusGroupType(TConsensusGroupType.DataRegion)
145148
.setIoTConsensusConfig(
146149
IoTConsensusConfig.newBuilder()

0 commit comments

Comments
 (0)