Skip to content

Commit b31934e

Browse files
CaideyipirobertMileaNi
authored andcommitted
Pipe: Retry history LoadTsFile while waiting for schema (#18031)
* Fix pipe history LoadTsFile schema retry * Address load tsfile schema retry review * Fix load tsfile tests with real temp files
1 parent cbb33a0 commit b31934e

9 files changed

Lines changed: 237 additions & 14 deletions

File tree

integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/basic/IoTDBPipeReceiverAutoCreateDisabledIT.java

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.sql.ResultSetMetaData;
3737
import java.sql.SQLException;
3838
import java.sql.Statement;
39+
import java.util.Arrays;
3940
import java.util.HashSet;
4041
import java.util.Set;
4142

@@ -61,14 +62,16 @@ protected void setupConfig() {
6162
.getConfig()
6263
.getCommonConfig()
6364
.setDataReplicationFactor(1)
64-
.setSchemaReplicationFactor(1);
65+
.setSchemaReplicationFactor(1)
66+
.setPipeAutoSplitFullEnabled(true);
6567
receiverEnv
6668
.getConfig()
6769
.getCommonConfig()
6870
.setAutoCreateSchemaEnabled(false)
6971
.setDatanodeMemoryProportion("3:3:1:1:1:0")
7072
.setDataReplicationFactor(1)
71-
.setSchemaReplicationFactor(1);
73+
.setSchemaReplicationFactor(1)
74+
.setPipeAutoSplitFullEnabled(true);
7275
}
7376

7477
@Test
@@ -122,6 +125,63 @@ public void testReceiverAutoCreateSchemaDisabledWithSpecialTimeSeries() throws E
122125
}
123126
}
124127

128+
@Test
129+
public void testAutoSplitHistoryTsFileWithDeletionWhenReceiverAutoCreateSchemaDisabled()
130+
throws Exception {
131+
TestUtils.executeNonQueries(
132+
senderEnv,
133+
Arrays.asList(
134+
"create database root.sg",
135+
"create timeseries root.sg.non_aligned.s1 with datatype=INT32",
136+
"create timeseries root.sg.non_aligned.s2 with datatype=DOUBLE",
137+
"create aligned timeseries root.sg.aligned(s1 INT32, s2 DOUBLE)",
138+
"create timeseries root.sg.deleted_measurement.s1 with datatype=INT32",
139+
"create timeseries root.sg.deleted_measurement.s2 with datatype=DOUBLE",
140+
"insert into root.sg.non_aligned(time, s1, s2) values(1, 1, 1.0), (2, 2, 2.0), (3, 3, 3.0)",
141+
"insert into root.sg.aligned(time, s1, s2) values(1, 10, 10.0), (2, 20, 20.0), (3, 30, 30.0)",
142+
"insert into root.sg.deleted_measurement(time, s1, s2) values(1, 100, 100.0), (2, 200, 200.0)",
143+
"flush",
144+
"delete from root.sg.non_aligned.s1 where time > 2",
145+
"delete from root.sg.aligned.* where time > 2",
146+
"delete timeseries root.sg.deleted_measurement.s1",
147+
"flush"));
148+
149+
awaitUntilFlush(senderEnv);
150+
151+
TestUtils.executeNonQuery(
152+
senderEnv,
153+
String.format(
154+
"create pipe test with source ('inclusion'='all', 'source.history.enable'='true', 'source.realtime.mode'='batch') "
155+
+ "with sink ('sink'='iotdb-thrift-sink', 'sink.node-urls'='%s')",
156+
receiverEnv.getDataNodeWrapper(0).getIpAndPortString()));
157+
158+
TestUtils.assertDataEventuallyOnEnv(
159+
receiverEnv,
160+
"select * from root.sg.non_aligned",
161+
"Time,root.sg.non_aligned.s1,root.sg.non_aligned.s2,",
162+
new HashSet<>(Arrays.asList("1,1,1.0,", "2,2,2.0,", "3,null,3.0,")));
163+
TestUtils.assertDataEventuallyOnEnv(
164+
receiverEnv,
165+
"select * from root.sg.aligned",
166+
"Time,root.sg.aligned.s1,root.sg.aligned.s2,",
167+
new HashSet<>(Arrays.asList("1,10,10.0,", "2,20,20.0,")));
168+
TestUtils.assertDataEventuallyOnEnv(
169+
receiverEnv,
170+
"select * from root.sg.deleted_measurement",
171+
"Time,root.sg.deleted_measurement.s2,",
172+
new HashSet<>(Arrays.asList("1,100.0,", "2,200.0,")));
173+
TestUtils.assertDataEventuallyOnEnv(
174+
receiverEnv,
175+
"count timeseries root.sg.deleted_measurement.*",
176+
"count(timeseries),",
177+
new HashSet<>(Arrays.asList("1,")));
178+
TestUtils.assertDataEventuallyOnEnv(
179+
receiverEnv,
180+
"show devices root.sg.aligned",
181+
"Device,IsAligned,Template,TTL(ms),",
182+
new HashSet<>(Arrays.asList("root.sg.aligned,true,null,INF,")));
183+
}
184+
125185
private QueryResult queryForResult(final Statement statement, final String sql)
126186
throws SQLException {
127187
try (final ResultSet resultSet = statement.executeQuery(sql)) {

iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,12 @@ public final class DataNodeQueryMessages {
314314
"Empty file detected, will skip loading this file: {}";
315315
public static final String AUTO_CREATE_OR_VERIFY_SCHEMA_ERROR =
316316
"Auto create or verify schema error.";
317+
public static final String LOAD_TSFILE_DEVICE_SCHEMA_MISSING_AUTO_CREATE_DISABLED =
318+
"Device %s does not exist in IoTDB and can not be created. Please check whether auto-create-schema is enabled.";
319+
public static final String LOAD_TSFILE_MEASUREMENT_SCHEMA_MISSING_AUTO_CREATE_DISABLED =
320+
"Measurement %s does not exist in IoTDB and can not be created. Please check whether auto-create-schema is enabled.";
321+
public static final String PIPE_GENERATED_LOAD_TSFILE_WAITING_FOR_SCHEMA_METADATA =
322+
"Pipe generated LoadTsFile is waiting for schema metadata to be transferred. Detail: %s";
317323
public static final String FAILED_TO_FIND_TAG_COLUMN_MAPPING_FOR_TABLE =
318324
"Failed to find tag column mapping for table {}";
319325
public static final String AUTO_CREATE_DATABASE_FAILED_BECAUSE =

iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,12 @@ public final class DataNodeQueryMessages {
312312
"检测到空文件,将跳过加载此文件:{}";
313313
public static final String AUTO_CREATE_OR_VERIFY_SCHEMA_ERROR =
314314
"自动创建或验证 schema 出错。";
315+
public static final String LOAD_TSFILE_DEVICE_SCHEMA_MISSING_AUTO_CREATE_DISABLED =
316+
"设备 %s 在 IoTDB 中不存在且无法被创建。请检查是否启用了 auto-create-schema。";
317+
public static final String LOAD_TSFILE_MEASUREMENT_SCHEMA_MISSING_AUTO_CREATE_DISABLED =
318+
"时间序列 %s 在 IoTDB 中不存在且无法被创建。请检查是否启用了 auto-create-schema。";
319+
public static final String PIPE_GENERATED_LOAD_TSFILE_WAITING_FOR_SCHEMA_METADATA =
320+
"Pipe 生成的 LoadTsFile 正在等待 schema 元数据传输完成。详情:%s";
315321
public static final String FAILED_TO_FIND_TAG_COLUMN_MAPPING_FOR_TABLE =
316322
"未找到表 {} 的标签列映射";
317323
public static final String AUTO_CREATE_DATABASE_FAILED_BECAUSE =
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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.db.exception.load;
21+
22+
public class LoadAnalyzeMissingSchemaException extends LoadAnalyzeException {
23+
24+
public LoadAnalyzeMissingSchemaException(final String message) {
25+
super(message);
26+
}
27+
}

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/visitor/PipeStatementTSStatusVisitor.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,7 @@ public TSStatus visitNode(final StatementNode node, final TSStatus status) {
7474
public TSStatus visitLoadFile(
7575
final LoadTsFileStatement loadTsFileStatement, final TSStatus status) {
7676
if (status.getCode() == TSStatusCode.SYSTEM_READ_ONLY.getStatusCode()
77-
|| status.getCode() == TSStatusCode.LOAD_FILE_ERROR.getStatusCode()
78-
&& status.getMessage() != null
79-
&& status.getMessage().contains("memory")) {
77+
|| status.getCode() == TSStatusCode.LOAD_TEMPORARY_UNAVAILABLE_EXCEPTION.getStatusCode()) {
8078
return new TSStatus(
8179
TSStatusCode.PIPE_RECEIVER_TEMPORARY_UNAVAILABLE_EXCEPTION.getStatusCode())
8280
.setMessage(status.getMessage());

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzer.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.iotdb.commons.queryengine.utils.TimestampPrecisionUtils;
2929
import org.apache.iotdb.db.conf.IoTDBDescriptor;
3030
import org.apache.iotdb.db.exception.load.LoadAnalyzeException;
31+
import org.apache.iotdb.db.exception.load.LoadAnalyzeMissingSchemaException;
3132
import org.apache.iotdb.db.exception.load.LoadAnalyzeTypeMismatchException;
3233
import org.apache.iotdb.db.exception.load.LoadEmptyFileException;
3334
import org.apache.iotdb.db.i18n.DataNodeQueryMessages;
@@ -222,6 +223,9 @@ public IAnalysis analyzeFileByFile(IAnalysis analysis) {
222223
executeTabletConversionOnException(analysis, e);
223224
return analysis;
224225
} catch (Exception e) {
226+
if (setTemporaryUnavailableStatusIfNecessary(analysis, e)) {
227+
return analysis;
228+
}
225229
final String exceptionMessage =
226230
String.format(
227231
"Auto create or verify schema error when executing statement %s. Detail: %s.",
@@ -346,6 +350,9 @@ private boolean doAnalyzeFileByFile(IAnalysis analysis) {
346350
"The file %s is not a valid tsfile. Please check the input file.",
347351
tsFile.getPath()));
348352
} catch (Exception e) {
353+
if (setTemporaryUnavailableStatusIfNecessary(analysis, e)) {
354+
return false;
355+
}
349356
final String exceptionMessage =
350357
String.format(
351358
"Loading file %s failed. Detail: %s",
@@ -681,8 +688,26 @@ private void setFailAnalysisForAuthException(IAnalysis analysis, AuthException e
681688
analysis.setFailStatus(RpcUtils.getStatus(e.getCode(), e.getMessage()));
682689
}
683690

691+
private void setFailAnalysisForTemporaryUnavailablePipeSchema(
692+
final IAnalysis analysis, final Throwable throwable) {
693+
final String exceptionMessage =
694+
String.format(
695+
DataNodeQueryMessages.PIPE_GENERATED_LOAD_TSFILE_WAITING_FOR_SCHEMA_METADATA,
696+
throwable.getMessage() == null
697+
? throwable.getClass().getName()
698+
: throwable.getMessage());
699+
analysis.setFinishQueryAfterAnalyze(true);
700+
analysis.setFailStatus(
701+
RpcUtils.getStatus(TSStatusCode.LOAD_TEMPORARY_UNAVAILABLE_EXCEPTION, exceptionMessage));
702+
setRealStatement(analysis);
703+
}
704+
684705
private void executeTabletConversionOnException(
685706
final IAnalysis analysis, final LoadAnalyzeException e) {
707+
if (setTemporaryUnavailableStatusIfNecessary(analysis, e)) {
708+
return;
709+
}
710+
686711
if (shouldSkipConversion(e)) {
687712
analysis.setFailStatus(
688713
new TSStatus(TSStatusCode.LOAD_FILE_ERROR.getStatusCode()).setMessage(e.getMessage()));
@@ -764,6 +789,38 @@ private void executeTabletConversionOnException(
764789
setRealStatement(analysis);
765790
}
766791

792+
private boolean setTemporaryUnavailableStatusIfNecessary(
793+
final IAnalysis analysis, final Throwable throwable) {
794+
if (isTemporaryUnavailableDueToPipeSchemaNotReady(throwable)) {
795+
setFailAnalysisForTemporaryUnavailablePipeSchema(analysis, throwable);
796+
return true;
797+
}
798+
if (isGeneratedByPipe && LoadTsFileDataTypeConverter.isMemoryPressureException(throwable)) {
799+
analysis.setFinishQueryAfterAnalyze(true);
800+
analysis.setFailStatus(LoadTsFileDataTypeConverter.getMemoryPressureStatus(throwable));
801+
setRealStatement(analysis);
802+
return true;
803+
}
804+
return false;
805+
}
806+
807+
boolean isTemporaryUnavailableDueToPipeSchemaNotReady(final Throwable throwable) {
808+
if (!isGeneratedByPipe
809+
|| !isVerifySchema
810+
|| IoTDBDescriptor.getInstance().getConfig().isAutoCreateSchemaEnabled()) {
811+
return false;
812+
}
813+
814+
Throwable current = throwable;
815+
while (current != null) {
816+
if (current instanceof LoadAnalyzeMissingSchemaException) {
817+
return true;
818+
}
819+
current = current.getCause();
820+
}
821+
return false;
822+
}
823+
767824
private boolean shouldSkipConversion(LoadAnalyzeException e) {
768825
return (e instanceof LoadAnalyzeTypeMismatchException) && !isConvertOnTypeMismatch;
769826
}

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/TreeSchemaAutoCreatorAndVerifier.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.apache.iotdb.db.auth.AuthorityChecker;
3737
import org.apache.iotdb.db.conf.IoTDBDescriptor;
3838
import org.apache.iotdb.db.exception.load.LoadAnalyzeException;
39+
import org.apache.iotdb.db.exception.load.LoadAnalyzeMissingSchemaException;
3940
import org.apache.iotdb.db.exception.load.LoadAnalyzeTypeMismatchException;
4041
import org.apache.iotdb.db.exception.load.LoadFileException;
4142
import org.apache.iotdb.db.exception.load.LoadRuntimeOutOfMemoryException;
@@ -109,7 +110,7 @@ public void setCurrentTimeIndex(final ITimeIndex timeIndex) {
109110
public void autoCreateAndVerify(
110111
TsFileSequenceReader reader,
111112
Map<IDeviceID, List<TimeseriesMetadata>> device2TimeseriesMetadataList)
112-
throws IOException, AuthException, LoadAnalyzeTypeMismatchException {
113+
throws IOException, AuthException, LoadAnalyzeException {
113114
for (final Map.Entry<IDeviceID, List<TimeseriesMetadata>> entry :
114115
device2TimeseriesMetadataList.entrySet()) {
115116
final IDeviceID device = entry.getKey();
@@ -198,14 +199,14 @@ public void flushAndClearDeviceIsAlignedCacheIfNecessary() throws SemanticExcept
198199
schemaCache.clearDeviceIsAlignedCacheIfNecessary();
199200
}
200201

201-
public void flush() throws AuthException, LoadAnalyzeTypeMismatchException {
202+
public void flush() throws AuthException, LoadAnalyzeException {
202203
doAutoCreateAndVerify();
203204

204205
schemaCache.clearTimeSeries();
205206
}
206207

207208
private void doAutoCreateAndVerify()
208-
throws SemanticException, AuthException, LoadAnalyzeTypeMismatchException {
209+
throws SemanticException, AuthException, LoadAnalyzeException {
209210
if (schemaCache.getDevice2TimeSeries().isEmpty()) {
210211
return;
211212
}
@@ -235,6 +236,11 @@ private void doAutoCreateAndVerify()
235236
} else {
236237
handleException(e, loadTsFileAnalyzer.getStatementString());
237238
}
239+
} catch (LoadAnalyzeMissingSchemaException e) {
240+
if (loadTsFileAnalyzer.isTemporaryUnavailableDueToPipeSchemaNotReady(e)) {
241+
throw e;
242+
}
243+
handleException(e, loadTsFileAnalyzer.getStatementString());
238244
} catch (Exception e) {
239245
if (e.getCause() instanceof LoadAnalyzeTypeMismatchException
240246
&& loadTsFileAnalyzer.isConvertOnTypeMismatch()) {
@@ -449,10 +455,9 @@ private void verifySchema(ISchemaTree schemaTree)
449455
.collect(Collectors.toList()));
450456

451457
if (iotdbDeviceSchemaInfo == null) {
452-
throw new LoadAnalyzeException(
458+
throw new LoadAnalyzeMissingSchemaException(
453459
String.format(
454-
"Device %s does not exist in IoTDB and can not be created. "
455-
+ "Please check weather auto-create-schema is enabled.",
460+
DataNodeQueryMessages.LOAD_TSFILE_DEVICE_SCHEMA_MISSING_AUTO_CREATE_DISABLED,
456461
device));
457462
}
458463

@@ -475,10 +480,9 @@ private void verifySchema(ISchemaTree schemaTree)
475480
final IMeasurementSchema tsFileSchema = tsfileTimeseriesSchemas.get(i);
476481
final IMeasurementSchema iotdbSchema = iotdbTimeseriesSchemas.get(i);
477482
if (iotdbSchema == null) {
478-
throw new LoadAnalyzeException(
483+
throw new LoadAnalyzeMissingSchemaException(
479484
String.format(
480-
"Measurement %s does not exist in IoTDB and can not be created. "
481-
+ "Please check weather auto-create-schema is enabled.",
485+
DataNodeQueryMessages.LOAD_TSFILE_MEASUREMENT_SCHEMA_MISSING_AUTO_CREATE_DISABLED,
482486
device + TsFileConstant.PATH_SEPARATOR + tsfileTimeseriesSchemas.get(i)));
483487
}
484488

iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/receiver/PipeStatementTsStatusVisitorTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@
2424
import org.apache.iotdb.commons.utils.StatusUtils;
2525
import org.apache.iotdb.db.pipe.receiver.protocol.thrift.IoTDBDataNodeReceiver;
2626
import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowsStatement;
27+
import org.apache.iotdb.db.queryengine.plan.statement.crud.LoadTsFileStatement;
2728
import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.BatchActivateTemplateStatement;
2829
import org.apache.iotdb.rpc.TSStatusCode;
2930

3031
import org.junit.Assert;
3132
import org.junit.Test;
3233

34+
import java.io.File;
3335
import java.util.Arrays;
3436
import java.util.Collections;
3537

@@ -64,6 +66,36 @@ StatusUtils.OK, new TSStatus(TSStatusCode.OUT_OF_TTL.getStatusCode()))))
6466
.getCode());
6567
}
6668

69+
@Test
70+
public void testLoadTemporaryUnavailableClassification() throws Exception {
71+
final File tsFile = File.createTempFile("temporary-unavailable", ".tsfile");
72+
tsFile.deleteOnExit();
73+
74+
Assert.assertEquals(
75+
TSStatusCode.PIPE_RECEIVER_TEMPORARY_UNAVAILABLE_EXCEPTION.getStatusCode(),
76+
IoTDBDataNodeReceiver.STATEMENT_STATUS_VISITOR
77+
.process(
78+
LoadTsFileStatement.createUnchecked(tsFile.getAbsolutePath()),
79+
new TSStatus(TSStatusCode.LOAD_TEMPORARY_UNAVAILABLE_EXCEPTION.getStatusCode())
80+
.setMessage("schema is not ready"))
81+
.getCode());
82+
}
83+
84+
@Test
85+
public void testLoadFileErrorWithMemoryMessageIsNotClassifiedByMessage() throws Exception {
86+
final File tsFile = File.createTempFile("memory-error", ".tsfile");
87+
tsFile.deleteOnExit();
88+
89+
Assert.assertEquals(
90+
TSStatusCode.LOAD_FILE_ERROR.getStatusCode(),
91+
IoTDBDataNodeReceiver.STATEMENT_STATUS_VISITOR
92+
.process(
93+
LoadTsFileStatement.createUnchecked(tsFile.getAbsolutePath()),
94+
new TSStatus(TSStatusCode.LOAD_FILE_ERROR.getStatusCode())
95+
.setMessage("memory pressure"))
96+
.getCode());
97+
}
98+
6799
@Test
68100
public void testDatabaseNotExistRuntimeExceptionClassification() {
69101
Assert.assertEquals(

0 commit comments

Comments
 (0)