This is an automated email from the ASF dual-hosted git repository.
luwei16 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 35fea58aa87 [fix](fe) Keep schema change waiting on conflict txn abort
failure (#65196)
35fea58aa87 is described below
commit 35fea58aa87a3f72d52f5d117427608bbde14bb1
Author: Jamie <[email protected]>
AuthorDate: Mon Aug 17 18:05:19 2026 +0800
[fix](fe) Keep schema change waiting on conflict txn abort failure (#65196)
### What problem does this PR solve?
Issue Number: None
Related PR: None
Problem Summary:
A cloud schema change job in WAITING_TXN uses
`checkFailedPreviousLoadAndAbort()` to abort failed conflict
transactions and make progress. If that conflict transaction is
concurrently cleaned up, already aborted, visible, or otherwise no
longer abortable, `abortTransaction()` can throw `UserException`. Before
this patch, `runWaitingTxnJob()` propagated that exception as
`AlterCancelException`, and `AlterJobV2.run()` cancelled the schema
change job.
This patch makes conflict transaction abort best-effort for schema
change: abort failure keeps the job in WAITING_TXN and lets the next
scheduler round re-query transaction state. The docker regression case
also now reports the actual schema change state and fails immediately on
CANCELLED instead of using opaque `assertEquals(1,2)` fallbacks.
---
.../org/apache/doris/alter/SchemaChangeJobV2.java | 10 ++-
.../org/apache/doris/alter/CloudIndexTest.java | 82 ++++++++++++++++++++++
.../schema_change_p0/test_abort_txn_by_fe.groovy | 8 ++-
3 files changed, 95 insertions(+), 5 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeJobV2.java
b/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeJobV2.java
index 976577f7ec8..de8a7c94fb8 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeJobV2.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeJobV2.java
@@ -903,8 +903,14 @@ public class SchemaChangeJobV2 extends AlterJobV2
implements GsonPostProcessable
if (Config.enable_abort_txn_by_checking_conflict_txn) {
List<TransactionState> failedTxns =
GlobalTransactionMgr.checkFailedTxns(unFinishedTxns);
for (TransactionState txn : failedTxns) {
- Env.getCurrentGlobalTransactionMgr()
- .abortTransaction(txn.getDbId(),
txn.getTransactionId(), "Cancel by schema change");
+ try {
+ Env.getCurrentGlobalTransactionMgr()
+ .abortTransaction(txn.getDbId(),
txn.getTransactionId(), "Cancel by schema change");
+ } catch (UserException e) {
+ LOG.warn("failed to abort previous load txn {}, wait next
round. schema change job: {}",
+ txn.getTransactionId(), jobId, e);
+ return false;
+ }
}
}
return unFinishedTxns.isEmpty();
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/alter/CloudIndexTest.java
b/fe/fe-core/src/test/java/org/apache/doris/alter/CloudIndexTest.java
index 370a9cd90dc..13d6c804667 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/alter/CloudIndexTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/alter/CloudIndexTest.java
@@ -649,4 +649,86 @@ public class CloudIndexTest {
Assert.assertEquals("true",
table.getIndexes().get(0).getProperties().get("support_phrase"));
Assert.assertEquals("true",
table.getIndexes().get(0).getProperties().get("lower_case"));
}
+
+ @Test
+ public void testSchemaChangeWaitsWhenConflictTxnAbortFails() throws
Exception {
+ Assert.assertTrue(Env.getCurrentSystemInfo() instanceof
CloudSystemInfoService);
+
+ SystemInfoService cloudSystemInfo = Env.getCurrentSystemInfo();
+ if (fakeEnv != null) {
+ fakeEnv.close();
+ }
+ fakeEnv = new FakeEnv();
+ if (fakeEditLog != null) {
+ fakeEditLog.close();
+ }
+ fakeEditLog = new FakeEditLog();
+ FakeEnv.setEnv(masterEnv);
+ FakeEnv.setSystemInfo(cloudSystemInfo);
+ schemaChangeHandler = (SchemaChangeHandler) new
Alter().getSchemaChangeHandler();
+
+ Assert.assertTrue(Env.getCurrentInternalCatalog() instanceof
CloudInternalCatalog);
+ Assert.assertTrue(Env.getCurrentSystemInfo() instanceof
CloudSystemInfoService);
+ CatalogTestUtil.createDupTable(db);
+ OlapTable table = (OlapTable)
db.getTableOrDdlException(CatalogTestUtil.testTableId2);
+ DataSortInfo dataSortInfo = new DataSortInfo();
+ dataSortInfo.setSortType(TSortType.LEXICAL);
+ table.setDataSortInfo(dataSortInfo);
+
table.setInvertedIndexFileStorageFormat(TInvertedIndexFileStorageFormat.V2);
+
+ Map<String, String> properties = Maps.newHashMap();
+ properties.put("parser", "english");
+ properties.put("support_phrase", "true");
+ properties.put("lower_case", "true");
+ IndexDefinition indexDefinition = new
IndexDefinition("conflict_txn_abort_index", false,
+ Lists.newArrayList(table.getBaseSchema().get(2).getName()),
+ "INVERTED",
+ properties, "tokenized inverted index with conflict txn
abort");
+ TableNameInfo tableNameInfo = new
TableNameInfo(masterEnv.getInternalCatalog().getName(), db.getName(),
+ table.getName());
+ createIndexOp = new CreateIndexOp(tableNameInfo, indexDefinition,
false);
+ createIndexOp.validate(new ConnectContext());
+ ArrayList<AlterOp> alterOps = new ArrayList<>();
+ alterOps.add(createIndexOp);
+ schemaChangeHandler.process(alterOps, db, table);
+ Map<Long, AlterJobV2> indexChangeJobMap =
schemaChangeHandler.getAlterJobsV2();
+ Assert.assertEquals(1, indexChangeJobMap.size());
+
+ SchemaChangeJobV2 jobV2 = (SchemaChangeJobV2)
indexChangeJobMap.values().stream()
+ .findFirst()
+ .orElse(null);
+ schemaChangeHandler.runAfterCatalogReady();
+ Assert.assertEquals(AlterJobV2.JobState.WAITING_TXN,
jobV2.getJobState());
+
+ Mockito.doAnswer(invocation -> {
+ Cloud.TxnCoordinatorPB coordinator =
Cloud.TxnCoordinatorPB.newBuilder()
+ .setSourceType(Cloud.TxnSourceTypePB.TXN_SOURCE_TYPE_FE)
+ .setIp("offline-fe")
+ .setId(1L)
+ .setStartTime(1L)
+ .build();
+ Cloud.TxnInfoPB txnInfo = Cloud.TxnInfoPB.newBuilder()
+ .setDbId(CatalogTestUtil.testDbId1)
+
.addAllTableIds(Lists.newArrayList(CatalogTestUtil.testTableId2))
+ .setTxnId(1002L)
+ .setLabel("conflict_txn")
+ .setListenerId(0L)
+ .setStatus(Cloud.TxnStatusPB.TXN_STATUS_PREPARED)
+ .setCoordinator(coordinator)
+ .build();
+ return Cloud.CheckTxnConflictResponse.newBuilder()
+ .setStatus(Cloud.MetaServiceResponseStatus.newBuilder()
+ .setCode(MetaServiceCode.OK).setMsg("OK"))
+ .addConflictTxns(txnInfo)
+ .build();
+ }).when(mockProxy).checkTxnConflict(Mockito.any());
+ Mockito.doAnswer(invocation -> Cloud.GetTxnResponse.newBuilder()
+ .setStatus(Cloud.MetaServiceResponseStatus.newBuilder()
+ .setCode(MetaServiceCode.TXN_ID_NOT_FOUND).setMsg("txn
not found"))
+ .build()).when(mockProxy).getTxn(Mockito.any());
+
+ schemaChangeHandler.runAfterCatalogReady();
+ Assert.assertEquals(AlterJobV2.JobState.WAITING_TXN,
jobV2.getJobState());
+ Assert.assertEquals(OlapTableState.SCHEMA_CHANGE, table.getState());
+ }
}
diff --git
a/regression-test/suites/schema_change_p0/test_abort_txn_by_fe.groovy
b/regression-test/suites/schema_change_p0/test_abort_txn_by_fe.groovy
index 91641ae483c..8a989fbe426 100644
--- a/regression-test/suites/schema_change_p0/test_abort_txn_by_fe.groovy
+++ b/regression-test/suites/schema_change_p0/test_abort_txn_by_fe.groovy
@@ -86,10 +86,10 @@ suite('test_abort_txn_by_fe', 'docker') {
}
}
if (max_try_time < 1){
- assertEquals(1,2)
+ assertTrue("schema change job did not leave PENDING before FE
restart, last state: ${result}", false)
}
sleep 10000
- assertEquals(result, "WAITING_TXN");
+ assertEquals("schema change job should wait for the running load txn
before FE restart", "WAITING_TXN", result);
def oldMasterFe = cluster.getMasterFe()
cluster.restartFrontends(oldMasterFe.index)
@@ -115,10 +115,12 @@ suite('test_abort_txn_by_fe', 'docker') {
if (result == "FINISHED") {
sleep(3000)
break
+ } else if (result == "CANCELLED") {
+ assertTrue("schema change job was cancelled after FE restart",
false)
} else {
sleep(100)
if (max_try_time < 1){
- assertEquals(1,2)
+ assertTrue("schema change job did not finish after FE
restart, last state: ${result}", false)
}
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]