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 02e675a74d3 Preserve write-path causes and return delete failure
details (#18933)
02e675a74d3 is described below
commit 02e675a74d39380fb8f078b572fa0313a133a7ec
Author: Caideyipi <[email protected]>
AuthorDate: Fri Oct 23 09:55:32 2026 +0800
Preserve write-path causes and return delete failure details (#18933)
* Preserve causes in write-path diagnostics
* Return delete failure details to clients
* Avoid stack traces for expected write-path errors
---
.../dataregion/DataExecutionVisitor.java | 9 +-
.../db/storageengine/dataregion/DataRegion.java | 3 +-
.../timeindex/FileTimeIndexCacheRecorder.java | 6 +-
.../dataregion/wal/buffer/WALBuffer.java | 3 +-
.../dataregion/wal/recover/WALNodeRecoverTask.java | 3 +-
.../dataregion/DataExecutionVisitorTest.java | 185 +++++++++++++++++++++
6 files changed, 200 insertions(+), 9 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitor.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitor.java
index d723b8dc888..4f8eca8f275 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitor.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitor.java
@@ -345,9 +345,12 @@ public class DataExecutionVisitor implements
PlanVisitor<TSStatus, DataRegion> {
return StatusUtils.OK;
} catch (WriteProcessRejectException e) {
return RpcUtils.getStatus(e.getErrorCode(), e.getMessage());
- } catch (IOException | IllegalPathException e) {
+ } catch (IOException e) {
LOGGER.error(DataNodeMiscMessages.ERROR_EXECUTING_PLAN_NODE, node, e);
- return new TSStatus(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode());
+ return RpcUtils.getStatus(TSStatusCode.WRITE_PROCESS_ERROR,
e.getMessage());
+ } catch (IllegalPathException e) {
+ LOGGER.error(DataNodeMiscMessages.ERROR_EXECUTING_PLAN_NODE_CAUSED,
node, e.getMessage());
+ return RpcUtils.getStatus(TSStatusCode.WRITE_PROCESS_ERROR,
e.getMessage());
} finally {
dataRegion.writeUnlock();
}
@@ -363,7 +366,7 @@ public class DataExecutionVisitor implements
PlanVisitor<TSStatus, DataRegion> {
return RpcUtils.getStatus(e.getErrorCode(), e.getMessage());
} catch (final IOException e) {
LOGGER.error(DataNodeMiscMessages.ERROR_EXECUTING_PLAN_NODE, node, e);
- return new TSStatus(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode());
+ return RpcUtils.getStatus(TSStatusCode.WRITE_PROCESS_ERROR,
e.getMessage());
}
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
index 4ee3a2a6827..52000fe97fc 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
@@ -1854,7 +1854,8 @@ public class DataRegion implements IDataRegionForQuery {
logger.error(
StorageEngineMessages
.STORAGE_LOG_REMOTE_REQUEST_CONFIG_NODE_FAILED_THAT_JUDGMENT_IF_TABLE_25FE3602,
- e.getMessage());
+ e.getMessage(),
+ e);
CommonMetadataUtils.throwTableNotExistsException(getDatabaseName(), tableName);
return null; // unreachable, throwTableNotExistsException
always throws
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/tsfile/timeindex/FileTimeIndexCacheRecorder.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/tsfile/timeindex/FileTimeIndexCacheRecorder.java
index 40159a6924b..8aacb519c9d 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/tsfile/timeindex/FileTimeIndexCacheRecorder.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/tsfile/timeindex/FileTimeIndexCacheRecorder.java
@@ -104,7 +104,7 @@ public class FileTimeIndexCacheRecorder {
writer.write(buffer);
} catch (IOException e) {
LOGGER.warn(
-
StorageEngineMessages.ERROR_RECORD_FILE_TIME_INDEX_CACHE, e.getMessage());
+
StorageEngineMessages.ERROR_RECORD_FILE_TIME_INDEX_CACHE, e.getMessage(), e);
}
});
if (!result) {
@@ -158,7 +158,7 @@ public class FileTimeIndexCacheRecorder {
}
} catch (IOException e) {
LOGGER.warn(
-
StorageEngineMessages.ERROR_COMPACT_FILE_TIME_INDEX_CACHE, e.getMessage());
+
StorageEngineMessages.ERROR_COMPACT_FILE_TIME_INDEX_CACHE, e.getMessage(), e);
}
});
if (!result) {
@@ -208,7 +208,7 @@ public class FileTimeIndexCacheRecorder {
writer.close();
deleteFileOrDirectory(writer.getLogFile(), true);
} catch (IOException e) {
- LOGGER.warn(StorageEngineMessages.ERROR_CLOSE_FILE_TIME_INDEX_CACHE,
e.getMessage());
+ LOGGER.warn(StorageEngineMessages.ERROR_CLOSE_FILE_TIME_INDEX_CACHE,
e.getMessage(), e);
}
}
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBuffer.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBuffer.java
index ca975b397fc..eba379f1cc5 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBuffer.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBuffer.java
@@ -930,7 +930,8 @@ public class WALBuffer extends AbstractWALBuffer {
.STORAGE_LOG_FAIL_TO_READ_MEMTABLE_IDS_FROM_THE_WAL_FILE_OF_WAL_NODE_54B0056E,
id,
identifier,
- e.getMessage());
+ e.getMessage(),
+ e);
} catch (IOException e) {
logger.warn(
StorageEngineMessages
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/WALNodeRecoverTask.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/WALNodeRecoverTask.java
index 7efce703656..73d429e3ccb 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/WALNodeRecoverTask.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/WALNodeRecoverTask.java
@@ -332,7 +332,8 @@ public class WALNodeRecoverTask implements Runnable {
StorageEngineMessages
.STORAGE_LOG_FAIL_TO_READ_MEMTABLE_IDS_FROM_THE_WAL_FILE_OF_WAL_NODE_5325B5AB,
walFile.getAbsoluteFile(),
- e.getMessage());
+ logDirectory.getName(),
+ e);
} catch (IOException e) {
logger.warn(
StorageEngineMessages
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitorTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitorTest.java
new file mode 100644
index 00000000000..8d754bb3d10
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/consensus/statemachine/dataregion/DataExecutionVisitorTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.db.consensus.statemachine.dataregion;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.exception.IoTDBRuntimeException;
+import org.apache.iotdb.commons.exception.SemanticException;
+import org.apache.iotdb.commons.path.MeasurementPath;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
+import org.apache.iotdb.db.exception.runtime.TableLostRuntimeException;
+import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.DeleteDataNode;
+import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowNode;
+import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowsNode;
+import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalDeleteDataNode;
+import org.apache.iotdb.db.storageengine.dataregion.DataRegion;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
+import org.apache.tsfile.enums.TSDataType;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import static org.mockito.ArgumentMatchers.any;
+
+public class DataExecutionVisitorTest {
+
+ private final Logger logger = (Logger)
LoggerFactory.getLogger(DataExecutionVisitor.class);
+ private ListAppender<ILoggingEvent> appender;
+
+ @Before
+ public void setUp() {
+ appender = new ListAppender<>();
+ appender.start();
+ logger.addAppender(appender);
+ }
+
+ @After
+ public void tearDown() {
+ logger.detachAppender(appender);
+ appender.stop();
+ }
+
+ @Test
+ public void testInsertRowsSemanticFailureDoesNotLogStackTrace() throws
Exception {
+ assertInsertRowsFailureWithoutStackTrace(new SemanticException("injected
write failure"));
+ }
+
+ @Test
+ public void testInsertRowsTableLostFailureDoesNotLogStackTrace() throws
Exception {
+ assertInsertRowsFailureWithoutStackTrace(
+ new TableLostRuntimeException("test_db", "test_table"));
+ }
+
+ private void assertInsertRowsFailureWithoutStackTrace(final
IoTDBRuntimeException failure)
+ throws Exception {
+ final DataRegion dataRegion = Mockito.mock(DataRegion.class);
+
Mockito.doThrow(failure).when(dataRegion).insert(any(InsertRowsNode.class));
+
+ final InsertRowNode insertRowNode =
+ new InsertRowNode(
+ new PlanNodeId("write-log-test"),
+ new PartialPath("root.sg.device"),
+ false,
+ new String[] {"s1"},
+ new TSDataType[] {TSDataType.INT64},
+ 1L,
+ new Object[] {1L},
+ false);
+ final InsertRowsNode insertRowsNode =
+ new InsertRowsNode(
+ new PlanNodeId("write-log-test"),
+ Collections.singletonList(0),
+ Collections.singletonList(insertRowNode));
+
+ final TSStatus status = new
DataExecutionVisitor().visitInsertRows(insertRowsNode, dataRegion);
+
+ Assert.assertEquals(failure.getErrorCode(), status.getCode());
+ Assert.assertEquals(failure.getMessage(), status.getMessage());
+ Assert.assertEquals(1, appender.list.size());
+
Assert.assertTrue(appender.list.get(0).getFormattedMessage().contains(failure.getMessage()));
+ Assert.assertNull(appender.list.get(0).getThrowableProxy());
+ }
+
+ @Test
+ public void testTreeDeleteFailureReturnsMessage() throws Exception {
+ final DataRegion dataRegion = Mockito.mock(DataRegion.class);
+ Mockito.when(dataRegion.getDatabaseName()).thenReturn("root.sg");
+ final DeleteDataNode node = createDeleteDataNode("root.sg.device.s1");
+ final IOException failure = new IOException("injected tree delete
failure");
+
Mockito.doThrow(failure).when(dataRegion).deleteByDevice(any(MeasurementPath.class),
any());
+
+ final TSStatus status = new DataExecutionVisitor().visitDeleteData(node,
dataRegion);
+
+ Assert.assertEquals(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode(),
status.getCode());
+ Assert.assertEquals(failure.getMessage(), status.getMessage());
+ Assert.assertEquals(1, appender.list.size());
+ Assert.assertNotNull(appender.list.get(0).getThrowableProxy());
+ Assert.assertEquals(
+ failure.getClass().getName(),
appender.list.get(0).getThrowableProxy().getClassName());
+ Mockito.verify(dataRegion).writeUnlock();
+ }
+
+ @Test
+ public void testTreeDeleteIllegalPathDoesNotLogStackTrace() throws Exception
{
+ final DataRegion dataRegion = Mockito.mock(DataRegion.class);
+ Mockito.when(dataRegion.getDatabaseName()).thenReturn("root..sg");
+ final DeleteDataNode node = createDeleteDataNode("root.sg.device.s1");
+ final IllegalPathException failure = new
IllegalPathException("root..sg.**");
+
+ final TSStatus status = new DataExecutionVisitor().visitDeleteData(node,
dataRegion);
+
+ Assert.assertEquals(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode(),
status.getCode());
+ Assert.assertEquals(failure.getMessage(), status.getMessage());
+ Assert.assertEquals(1, appender.list.size());
+
Assert.assertTrue(appender.list.get(0).getFormattedMessage().contains(failure.getMessage()));
+ Assert.assertNull(appender.list.get(0).getThrowableProxy());
+ Mockito.verify(dataRegion).writeUnlock();
+ Mockito.verify(dataRegion,
Mockito.never()).deleteByDevice(any(MeasurementPath.class), any());
+ }
+
+ @Test
+ public void testDirectTreeDeleteFailureReturnsMessage() throws Exception {
+ final DataRegion dataRegion = Mockito.mock(DataRegion.class);
+ Mockito.when(dataRegion.getDatabaseName()).thenReturn("root.sg");
+ final DeleteDataNode node = createDeleteDataNode("root.sg.**");
+ final IOException failure = new IOException("injected direct delete
failure");
+
Mockito.doThrow(failure).when(dataRegion).deleteDataDirectly(any(MeasurementPath.class),
any());
+
+ final TSStatus status = new DataExecutionVisitor().visitDeleteData(node,
dataRegion);
+
+ Assert.assertEquals(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode(),
status.getCode());
+ Assert.assertEquals(failure.getMessage(), status.getMessage());
+ Mockito.verify(dataRegion).writeUnlock();
+ }
+
+ @Test
+ public void testTableDeleteFailureReturnsMessage() throws Exception {
+ final DataRegion dataRegion = Mockito.mock(DataRegion.class);
+ final RelationalDeleteDataNode node =
+ new RelationalDeleteDataNode(
+ new PlanNodeId("delete-error-test"), Collections.emptyList(),
"test_db");
+ final IOException failure = new IOException("injected table delete
failure");
+ Mockito.doThrow(failure).when(dataRegion).deleteByTable(node);
+
+ final TSStatus status = new DataExecutionVisitor().visitDeleteData(node,
dataRegion);
+
+ Assert.assertEquals(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode(),
status.getCode());
+ Assert.assertEquals(failure.getMessage(), status.getMessage());
+ }
+
+ private DeleteDataNode createDeleteDataNode(final String path) throws
IllegalPathException {
+ return new DeleteDataNode(
+ new PlanNodeId("delete-error-test"),
+ Collections.singletonList(new MeasurementPath(path)),
+ Long.MIN_VALUE,
+ Long.MAX_VALUE);
+ }
+}