This is an automated email from the ASF dual-hosted git repository.
JackieTien97 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 4473a133d08 Fix table rename on a DataNode that missed the pre-release
(#18800)
4473a133d08 is described below
commit 4473a133d08490e94bfc21e4227f949cf0158dc6
Author: alpass163 <[email protected]>
AuthorDate: Fri Oct 9 09:14:03 2026 +0800
Fix table rename on a DataNode that missed the pre-release (#18800)
---
.../iotdb/confignode/i18n/ProcedureMessages.java | 2 +
.../iotdb/confignode/i18n/ProcedureMessages.java | 11 ++--
.../procedure/impl/schema/SchemaUtils.java | 17 +++++--
.../table/AbstractAlterOrDropTableProcedure.java | 19 +++++++
.../impl/schema/table/RenameTableProcedure.java | 2 +-
.../db/schemaengine/table/DataNodeTableCache.java | 27 +++++++++-
.../schemaengine/table/DataNodeTableCacheTest.java | 59 ++++++++++++++++++++++
7 files changed, 125 insertions(+), 12 deletions(-)
diff --git
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
index e04460375f9..94abb3faab4 100644
---
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
+++
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
@@ -1200,6 +1200,8 @@ public final class ProcedureMessages {
public static final String
LOG_FINISHED_SUBPROCEDURE_PID_ARG_RESUME_PROCESSING_PPID_ARG_93ED990B =
"Finished subprocedure pid={}, resume processing ppid={}";
public static final String LOG_HALT_PID_ARG_ACTIVECOUNT_ARG_411F3EBF = "Halt
pid={}, activeCount={}";
public static final String
LOG_EXCEPTION_HAPPENED_WORKER_ARG_EXECUTE_PROCEDURE_ARG_6E3AD27D = "Exception
happened when worker {} execute procedure {}";
+ public static final String
LOG_FAILED_TO_COMMIT_RELEASE_ARG_FOR_TABLE_ARG_ARG_TO_DATANODE_FAILURE_RESULTS_ARG_55386572
=
+ "Failed to commit-release {} for table {}.{} to DataNode, failure
results: {}";
public static final String LOG_WORKER_STUCK_ARG_ARG_RUN_TIME_ARG_MS_FB612354
= "Worker stuck {}({}), run time {} ms";
public static final String
LOG_PROCEDURE_WORKERS_ARG_RUNNING_ARG_RUNNING_STUCK_1565936D = "Procedure
workers: {} is running, {} is running and stuck";
public static final String
LOG_ADD_PROCEDURE_ARG_AS_ARG_TH_ROLLBACK_STEP_C71B2184 = "Add procedure {} as
the {}th rollback step";
diff --git
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
index 3014e1c0076..d4d5dc2770a 100644
---
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
+++
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
@@ -1137,6 +1137,9 @@ public final class ProcedureMessages {
"暂停 pid={}, activeCount={}";
public static final String
LOG_EXCEPTION_HAPPENED_WORKER_ARG_EXECUTE_PROCEDURE_ARG_6E3AD27D =
"worker {} 执行 procedure {} 时发生异常";
+ public static final String
+
LOG_FAILED_TO_COMMIT_RELEASE_ARG_FOR_TABLE_ARG_ARG_TO_DATANODE_FAILURE_RESULTS_ARG_55386572
=
+ "在 {} procedure 操作中, 对表{}.{} 的正式提交失败,失败结果:{}";
public static final String LOG_WORKER_STUCK_ARG_ARG_RUN_TIME_ARG_MS_FB612354
=
"Worker 卡住 {}({}),运行时间 {} ms";
public static final String
LOG_PROCEDURE_WORKERS_ARG_RUNNING_ARG_RUNNING_STUCK_1565936D =
@@ -1500,11 +1503,9 @@ public final class ProcedureMessages {
public static final String
MESSAGE_THE_PREVIOUS_ATTEMPT_FAILED_WITH_ARG_AND_THIS_STATE_IS_BEING_RETRIED_7A541F27
=
"上一次尝试因“%s”失败,正在重试此状态。";
- public static final String
- MESSAGE_THE_STATE_FAILED_WITH_ARG_AND_ROLLBACK_IS_PENDING_E7B43829 =
- "此状态因“%s”失败,正在等待回滚。";
- public static final String MESSAGE_ROLLING_BACK_AFTER_FAILURE_ARG_474DF456 =
- "正在回滚,失败原因:%s。";
+ public static final String
MESSAGE_THE_STATE_FAILED_WITH_ARG_AND_ROLLBACK_IS_PENDING_E7B43829 =
+ "此状态因“%s”失败,正在等待回滚。";
+ public static final String MESSAGE_ROLLING_BACK_AFTER_FAILURE_ARG_474DF456 =
"正在回滚,失败原因:%s。";
public static final String
MESSAGE_ROLLING_BACK_AFTER_AN_EARLIER_FAILURE_850D0AF5 =
"正在回滚此前发生的失败。";
}
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/SchemaUtils.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/SchemaUtils.java
index 69a3cec4400..ea91664834b 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/SchemaUtils.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/SchemaUtils.java
@@ -288,11 +288,9 @@ public class SchemaUtils {
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
- public static Map<Integer, TSStatus> commitReleaseTable(
- final String database,
- final String tableName,
- final ConfigManager configManager,
- final @Nullable String oldName) {
+ /** Build the COMMIT_UPDATE_TABLE request used to commit-release a table
change to DataNodes. */
+ public static TUpdateTableReq buildCommitUpdateTableReq(
+ final String database, final String tableName, final @Nullable String
oldName) {
final TUpdateTableReq req = new TUpdateTableReq();
req.setType(TsTableInternalRPCType.COMMIT_UPDATE_TABLE.getOperationType());
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
@@ -304,6 +302,15 @@ public class SchemaUtils {
}
req.setTableInfo(outputStream.toByteArray());
req.setOldName(oldName);
+ return req;
+ }
+
+ public static Map<Integer, TSStatus> commitReleaseTable(
+ final String database,
+ final String tableName,
+ final ConfigManager configManager,
+ final @Nullable String oldName) {
+ final TUpdateTableReq req = buildCommitUpdateTableReq(database, tableName,
oldName);
final DataNodeAsyncRequestContext<TUpdateTableReq, TSStatus> clientHandler
=
new DataNodeAsyncRequestContext<>(
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/AbstractAlterOrDropTableProcedure.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/AbstractAlterOrDropTableProcedure.java
index 76dfce38808..b4aab9949c2 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/AbstractAlterOrDropTableProcedure.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/AbstractAlterOrDropTableProcedure.java
@@ -136,6 +136,25 @@ public abstract class AbstractAlterOrDropTableProcedure<T>
}
}
+ protected void commitReleaseWithHA(
+ final ConfigNodeProcedureEnv env, final @Nullable String oldName) {
+ final TUpdateTableReq req =
+ SchemaUtils.buildCommitUpdateTableReq(database, table.getTableName(),
oldName);
+ final boolean proceeded =
+ new
ClusterCachePropagator(SchemaUtils.filterFencedDataNode(env.getConfigManager()))
+ .propagate(targets -> SchemaUtils.broadcastTableUpdate(req,
targets));
+
+ if (!proceeded) {
+ LOGGER.warn(
+ ProcedureMessages
+
.LOG_FAILED_TO_COMMIT_RELEASE_ARG_FOR_TABLE_ARG_ARG_TO_DATANODE_FAILURE_RESULTS_ARG_55386572,
+ getActionMessage(),
+ database,
+ table.getTableName(),
+ ProcedureMessages.FAILED_TO_PROVE_DN_IS_FENCED);
+ }
+ }
+
@Override
protected boolean isRollbackSupported(final T state) {
return true;
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/RenameTableProcedure.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/RenameTableProcedure.java
index ba5c379275b..e40b041d0f6 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/RenameTableProcedure.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/RenameTableProcedure.java
@@ -85,7 +85,7 @@ public class RenameTableProcedure extends
AbstractAlterOrDropTableProcedure<Rena
ProcedureMessages.COMMIT_RELEASE_INFO_OF_TABLE_WHEN_RENAMING_TABLE,
database,
tableName);
- commitRelease(env, tableName);
+ commitReleaseWithHA(env, tableName);
return Flow.NO_MORE_STATE;
default:
setFailure(
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
index 98ca71e0eac..525bf511f4e 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
@@ -329,7 +329,7 @@ public class DataNodeTableCache implements ITableCache {
database,
tableName);
if (Objects.nonNull(oldName)) {
- removeTableFromSpecialStatusMap(database, oldName);
+ repairMissedRenamePreUpdate(database, tableName, oldName);
}
return;
}
@@ -370,6 +370,31 @@ public class DataNodeTableCache implements ITableCache {
}
}
+ /**
+ * Repairs the cache after this DataNode missed the pre-update of a rename
procedure. The
+ * ConfigNode has already renamed the table, so the new name can only be
re-fetched, and the old
+ * name must not stay in the cache as a usable table.
+ */
+ private void repairMissedRenamePreUpdate(
+ final String database, final String newName, final String oldName) {
+ removeTableFromSpecialStatusMap(database, oldName);
+ // drop the old table
+ databaseTableMap.computeIfPresent(
+ database,
+ (databaseName, tables) -> {
+ tables.remove(oldName);
+ return tables;
+ });
+ // A placeholder makes the next lookup of the new name fetch its
definition from the ConfigNode,
+ // which holds the committed rename.
+ if (Objects.isNull(getTableFromCache(database, newName))) {
+ specialStatusMap
+ .computeIfAbsent(database, k -> new ConcurrentHashMap<>())
+ .put(newName, new Pair<>(new NonCommittableTsTable(newName), 0L));
+ }
+ instanceVersion.incrementAndGet();
+ }
+
private void commitDeleteTable(String database, final String tableName) {
if (databaseTableMap.containsKey(database)) {
databaseTableMap.get(database).remove(tableName);
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCacheTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCacheTest.java
index d8d1eddafe4..bec226b21ab 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCacheTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCacheTest.java
@@ -21,14 +21,18 @@ package org.apache.iotdb.db.schemaengine.table;
import
org.apache.iotdb.commons.exception.MetadataLeaseFencedException.LeaseFencedRetryPolicy;
import org.apache.iotdb.commons.exception.SemanticException;
+import org.apache.iotdb.commons.schema.table.NonCommittableTsTable;
import org.apache.iotdb.commons.schema.table.PreDeleteTsTable;
import org.apache.iotdb.commons.schema.table.TsTable;
+import org.apache.iotdb.commons.schema.table.TsTableInternalRPCUtil;
import org.apache.iotdb.commons.schema.table.column.FieldColumnSchema;
+import org.apache.iotdb.commons.utils.PathUtils;
import org.apache.iotdb.rpc.TSStatusCode;
import org.apache.tsfile.enums.TSDataType;
import org.apache.tsfile.file.metadata.enums.CompressionType;
import org.apache.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.tsfile.utils.Pair;
import org.junit.Assert;
import org.junit.Test;
@@ -37,7 +41,9 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Semaphore;
public class DataNodeTableCacheTest {
@@ -120,6 +126,33 @@ public class DataNodeTableCacheTest {
}
}
+ @Test
+ public void commitRenameRepairsCacheWhenPreReleaseWasMissed() throws
Exception {
+ final ITableCache cache = DataNodeTableCache.getInstance();
+ final String oldName = "table1";
+ final String newName = "table2";
+ cache.invalid(TABLE_CACHE_TEST_DATABASE);
+ try {
+ // The activation snapshot of a DataNode that started during the
pre-release: the old name is
+ // a using table, and it is also marked as NonCommittable.
+ final Map<String, List<TsTable>> usingTables = new HashMap<>();
+ usingTables.put(TABLE_CACHE_TEST_DATABASE,
Collections.singletonList(createTable(oldName)));
+ final Map<String, List<TsTable>> specialTables = new HashMap<>();
+ specialTables.put(
+ TABLE_CACHE_TEST_DATABASE, Collections.singletonList(new
NonCommittableTsTable(oldName)));
+ cache.init(
+ TsTableInternalRPCUtil.serializeTableInitializationInfo(usingTables,
specialTables));
+
+ // The commit is the only sign of the rename, and the HA broadcast may
send it more than once.
+ cache.commitUpdateTable(TABLE_CACHE_TEST_DATABASE, newName, oldName);
+ assertRenameRepaired(cache, oldName, newName);
+ cache.commitUpdateTable(TABLE_CACHE_TEST_DATABASE, newName, oldName);
+ assertRenameRepaired(cache, oldName, newName);
+ } finally {
+ cache.invalid(TABLE_CACHE_TEST_DATABASE);
+ }
+ }
+
@Test
public void fetchingPreDeleteTableReportsDedicatedStatusCode() throws
Exception {
final ITableCache cache = DataNodeTableCache.getInstance();
@@ -166,6 +199,32 @@ public class DataNodeTableCacheTest {
cache, fetchedTables, DATABASE, tableName,
LeaseFencedRetryPolicy.RETRY_UNTIL_SUCCESS);
}
+ /** The old name must be gone, and the new name left as a placeholder to be
re-fetched. */
+ private static void assertRenameRepaired(
+ final ITableCache cache, final String oldName, final String newName)
throws Exception {
+ Assert.assertNull(cache.getTable(TABLE_CACHE_TEST_DATABASE, oldName,
false));
+ Assert.assertTrue(
+ getSpecialStatusTable(cache, TABLE_CACHE_TEST_DATABASE, newName)
+ instanceof NonCommittableTsTable);
+ }
+
+ /** A placeholder for a table to be re-fetched only shows up in the private
specialStatusMap. */
+ @SuppressWarnings("unchecked")
+ private static TsTable getSpecialStatusTable(
+ final ITableCache cache, final String database, final String tableName)
throws Exception {
+ final Field field =
DataNodeTableCache.class.getDeclaredField("specialStatusMap");
+ field.setAccessible(true);
+ final Map<String, Map<String, Pair<TsTable, Long>>> specialStatusMap =
+ (Map<String, Map<String, Pair<TsTable, Long>>>) field.get(cache);
+ final Map<String, Pair<TsTable, Long>> tableMap =
+ specialStatusMap.get(PathUtils.unQualifyDatabaseName(database));
+ if (Objects.isNull(tableMap)) {
+ return null;
+ }
+ final Pair<TsTable, Long> tablePair = tableMap.get(tableName);
+ return Objects.isNull(tablePair) ? null : tablePair.getLeft();
+ }
+
private Semaphore getFetchTableSemaphore(final ITableCache cache) throws
Exception {
final Field field =
DataNodeTableCache.class.getDeclaredField("fetchTableSemaphore");
field.setAccessible(true);