This is an automated email from the ASF dual-hosted git repository.
sollhui 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 a1853f20e79 [fix](mow) Retry delete bitmap calculation failures for
INSERT (#68342)
a1853f20e79 is described below
commit a1853f20e7960ba1ddd15de786d83f345adb2fad
Author: hui lai <[email protected]>
AuthorDate: Wed Sep 23 14:26:15 2026 +0800
[fix](mow) Retry delete bitmap calculation failures for INSERT (#68342)
### What problem does this PR solve?
Cloud MOW INSERT stopped retrying delete bitmap lock errors and
calculation timeouts after the insert executor switched to the
attachment-aware commit overload. That overload performs one attempt
because BE-driven loads already retry at their caller. Ordinary INSERT
is affected even when its attachment is null.
Add an explicit FE-owned retry entry and reuse the existing cloud retry
loop. Nereids INSERT now uses this entry, preserving the transaction
attachment and table stream updates on every attempt. Keep BE-driven
commit entry points single-attempt, the existing attempt limit and
signature lifecycle, and shared-nothing publish-timeout behavior.
---
.../transaction/CloudGlobalTransactionMgr.java | 11 +-
.../plans/commands/insert/OlapInsertExecutor.java | 2 +-
.../transaction/GlobalTransactionMgrIface.java | 10 ++
.../CloudTransactionCommitRetryTest.java | 162 +++++++++++++++++++++
.../commands/insert/OlapInsertExecutorTest.java | 39 ++++-
5 files changed, 216 insertions(+), 8 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java
b/fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java
index 53fad5a02ed..8268c71925f 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java
@@ -1636,6 +1636,15 @@ public class CloudGlobalTransactionMgr implements
GlobalTransactionMgrIface {
public boolean commitAndPublishTransaction(DatabaseIf db, List<Table>
tableList, long transactionId,
List<TabletCommitInfo>
tabletCommitInfos, long timeoutMillis)
throws UserException {
+ return commitAndPublishTransactionWithRetry(db, tableList,
transactionId, tabletCommitInfos, timeoutMillis,
+ null, Collections.emptyList());
+ }
+
+ @Override
+ public boolean commitAndPublishTransactionWithRetry(DatabaseIf db,
List<Table> tableList, long transactionId,
+ List<TabletCommitInfo> tabletCommitInfos, long timeoutMillis,
+ TxnCommitAttachment txnCommitAttachment,
List<TableStreamUpdateInfo> streamUpdateInfos)
+ throws UserException {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
int retryTimes = 0;
@@ -1644,7 +1653,7 @@ public class CloudGlobalTransactionMgr implements
GlobalTransactionMgrIface {
while (true) {
try {
res = commitAndPublishTransaction(db, tableList,
transactionId, tabletCommitInfos, timeoutMillis,
- null);
+ txnCommitAttachment, streamUpdateInfos);
break;
} catch (UserException e) {
LOG.warn("failed to commit txn,
txnId={},retryTimes={},exception={}",
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/OlapInsertExecutor.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/OlapInsertExecutor.java
index 7dc65ebce29..29b80a91aad 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/OlapInsertExecutor.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/OlapInsertExecutor.java
@@ -233,7 +233,7 @@ public class OlapInsertExecutor extends
AbstractInsertExecutor {
} catch (Exception abortTxnException) {
LOG.warn("errors when abort txn. {}",
ctx.getQueryIdentifier(), abortTxnException);
}
- } else if
(Env.getCurrentGlobalTransactionMgr().commitAndPublishTransaction(
+ } else if
(Env.getCurrentGlobalTransactionMgr().commitAndPublishTransactionWithRetry(
database, Lists.newArrayList((Table) table),
txnId,
TabletCommitInfo.fromThrift(coordinator.getCommitInfos()),
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/transaction/GlobalTransactionMgrIface.java
b/fe/fe-core/src/main/java/org/apache/doris/transaction/GlobalTransactionMgrIface.java
index e241ff37982..1e9a388e141 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/transaction/GlobalTransactionMgrIface.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/transaction/GlobalTransactionMgrIface.java
@@ -124,6 +124,16 @@ public interface GlobalTransactionMgrIface extends
Writable {
txnCommitAttachment);
}
+ // FE-driven inserts own the cloud delete bitmap retries. BE-driven loads
retry at the caller
+ // and must continue to use the single-attempt overloads that accept an
attachment.
+ default boolean commitAndPublishTransactionWithRetry(DatabaseIf db,
List<Table> tableList, long transactionId,
+ List<TabletCommitInfo> tabletCommitInfos, long timeoutMillis,
+ TxnCommitAttachment txnCommitAttachment,
List<TableStreamUpdateInfo> streamUpdateInfos)
+ throws UserException {
+ return commitAndPublishTransaction(db, tableList, transactionId,
tabletCommitInfos, timeoutMillis,
+ txnCommitAttachment, streamUpdateInfos);
+ }
+
public void commitTransaction2PC(Database db, List<Table> tableList, long
transactionId, long timeoutMillis)
throws UserException;
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/cloud/transaction/CloudTransactionCommitRetryTest.java
b/fe/fe-core/src/test/java/org/apache/doris/cloud/transaction/CloudTransactionCommitRetryTest.java
new file mode 100644
index 00000000000..38aa9bda213
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/cloud/transaction/CloudTransactionCommitRetryTest.java
@@ -0,0 +1,162 @@
+// 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.doris.cloud.transaction;
+
+import org.apache.doris.catalog.Database;
+import org.apache.doris.catalog.Table;
+import org.apache.doris.catalog.stream.TableStreamUpdateInfo;
+import org.apache.doris.common.Config;
+import org.apache.doris.common.InternalErrorCode;
+import org.apache.doris.common.UserException;
+import org.apache.doris.common.jmockit.Deencapsulation;
+import
org.apache.doris.job.extensions.insert.streaming.StreamingTaskTxnCommitAttachment;
+import org.apache.doris.transaction.GlobalTransactionMgrIface;
+import org.apache.doris.transaction.TabletCommitInfo;
+import org.apache.doris.transaction.TxnCommitAttachment;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+class CloudTransactionCommitRetryTest {
+ private static final long TXN_ID = 123L;
+ private static final long TIMEOUT_MS = 1000L;
+ private final Database db = new Database(1L, "retry_test");
+ private final List<Table> tables = Collections.emptyList();
+ private final List<TabletCommitInfo> commitInfos = Collections.emptyList();
+ private final List<TableStreamUpdateInfo> streamUpdates =
Collections.singletonList(
+ Mockito.mock(TableStreamUpdateInfo.class));
+ private final TxnCommitAttachment attachment = new
StreamingTaskTxnCommitAttachment();
+ private CloudGlobalTransactionMgr manager;
+ private Map<Long, Long> signatures;
+ private int previousRetryTimes;
+
+ @BeforeEach
+ void setUp() {
+ previousRetryTimes = Config.mow_calculate_delete_bitmap_retry_times;
+ Config.mow_calculate_delete_bitmap_retry_times = 3;
+ manager = Mockito.spy(new CloudGlobalTransactionMgr());
+ signatures = Deencapsulation.getField(manager, "txnLastSignatureMap");
+ }
+
+ @AfterEach
+ void tearDown() {
+ Config.mow_calculate_delete_bitmap_retry_times = previousRetryTimes;
+ }
+
+ @Test
+ void testRetryPreservesAttachmentStreamUpdatesAndSignature() throws
Exception {
+ AtomicInteger attempts = new AtomicInteger();
+ Mockito.doAnswer(invocation -> {
+ Assertions.assertSame(attachment, invocation.getArgument(5));
+ Assertions.assertSame(streamUpdates, invocation.getArgument(6));
+ if (attempts.incrementAndGet() == 1) {
+ signatures.put(TXN_ID, 456L);
+ throw retryableError();
+ }
+ Assertions.assertEquals(Long.valueOf(456L),
signatures.get(TXN_ID));
+ return true;
+ }).when(manager).commitAndPublishTransaction(db, tables, TXN_ID,
commitInfos, TIMEOUT_MS,
+ attachment, streamUpdates);
+
+ Assertions.assertTrue(manager.commitAndPublishTransactionWithRetry(db,
tables, TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment, streamUpdates));
+ Assertions.assertEquals(2, attempts.get());
+ Assertions.assertFalse(signatures.containsKey(TXN_ID));
+ }
+
+ @Test
+ void testRetryExhaustionPreservesAttemptLimitAndCleansSignature() throws
Exception {
+ UserException failure = retryableError();
+ signatures.put(TXN_ID, 456L);
+ Mockito.doThrow(failure).when(manager).commitAndPublishTransaction(db,
tables, TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment, streamUpdates);
+
+ Assertions.assertSame(failure,
Assertions.assertThrows(UserException.class,
+ () -> manager.commitAndPublishTransactionWithRetry(db, tables,
TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment, streamUpdates)));
+ Mockito.verify(manager,
Mockito.times(3)).commitAndPublishTransaction(db, tables, TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment, streamUpdates);
+ Assertions.assertFalse(signatures.containsKey(TXN_ID));
+ }
+
+ @Test
+ void testNonRetryableErrorIsPropagatedImmediately() throws Exception {
+ UserException failure = new UserException("injected non-retryable
error");
+ signatures.put(TXN_ID, 456L);
+ Mockito.doThrow(failure).when(manager).commitAndPublishTransaction(db,
tables, TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment, streamUpdates);
+
+ Assertions.assertSame(failure,
Assertions.assertThrows(UserException.class,
+ () -> manager.commitAndPublishTransactionWithRetry(db, tables,
TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment, streamUpdates)));
+ Mockito.verify(manager).commitAndPublishTransaction(db, tables, TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment, streamUpdates);
+ Assertions.assertFalse(signatures.containsKey(TXN_ID));
+ }
+
+ @Test
+ void testFiveArgumentEntryStillRetriesWithoutAttachment() throws Exception
{
+
Mockito.doThrow(retryableError()).doReturn(true).when(manager).commitAndPublishTransaction(
+ db, tables, TXN_ID, commitInfos, TIMEOUT_MS, null,
Collections.emptyList());
+
+ Assertions.assertTrue(manager.commitAndPublishTransaction(db, tables,
TXN_ID, commitInfos, TIMEOUT_MS));
+ Mockito.verify(manager,
Mockito.times(2)).commitAndPublishTransaction(db, tables, TXN_ID,
+ commitInfos, TIMEOUT_MS, null, Collections.emptyList());
+ }
+
+ @Test
+ void testBeCommitEntryLeavesRetryAndSignatureToCaller() throws Exception {
+ UserException failure = retryableError();
+ signatures.put(TXN_ID, 456L);
+ Mockito.doThrow(failure).when(manager).commitAndPublishTransaction(db,
tables, TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment, Collections.emptyList());
+
+ Assertions.assertSame(failure,
Assertions.assertThrows(UserException.class,
+ () -> manager.commitAndPublishTransaction(db, tables, TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment)));
+ Mockito.verify(manager).commitAndPublishTransaction(db, tables, TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment, Collections.emptyList());
+ Assertions.assertEquals(Long.valueOf(456L), signatures.get(TXN_ID));
+ }
+
+ @Test
+ void testSharedNothingDefaultPreservesPublishTimeoutResult() throws
Exception {
+ GlobalTransactionMgrIface localManager =
Mockito.mock(GlobalTransactionMgrIface.class,
+ Mockito.CALLS_REAL_METHODS);
+ Mockito.when(localManager.commitAndPublishTransaction(db, tables,
TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment)).thenReturn(false);
+
+
Assertions.assertFalse(localManager.commitAndPublishTransactionWithRetry(db,
tables, TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment, streamUpdates));
+ Mockito.verify(localManager).commitAndPublishTransaction(db, tables,
TXN_ID,
+ commitInfos, TIMEOUT_MS, attachment);
+ }
+
+ private UserException retryableError() {
+ return new UserException(InternalErrorCode.DELETE_BITMAP_LOCK_ERR,
+ "Failed to calculate delete bitmap. Timeout.");
+ }
+}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/insert/OlapInsertExecutorTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/insert/OlapInsertExecutorTest.java
index d45cc155fea..d2d792541dc 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/insert/OlapInsertExecutorTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/insert/OlapInsertExecutorTest.java
@@ -86,9 +86,9 @@ class OlapInsertExecutorTest {
prepareFactoryMocks(envFactoryMock, envMock, coordinator, txnMgr,
txnState, currentEnv);
ctx.setEnv(currentEnv);
- Mockito.when(txnMgr.commitAndPublishTransaction(
+ Mockito.when(txnMgr.commitAndPublishTransactionWithRetry(
Mockito.any(), Mockito.anyList(), Mockito.anyLong(),
Mockito.anyList(), Mockito.anyLong(),
- Mockito.isNull())).thenReturn(false);
+ Mockito.isNull(), Mockito.anyList())).thenReturn(false);
OlapInsertExecutor executor = createExecutor(ctx);
executor.txnId = 10001L;
@@ -131,9 +131,9 @@ class OlapInsertExecutorTest {
prepareFactoryMocks(envFactoryMock, envMock, coordinator, txnMgr,
txnState, currentEnv);
ctx.setEnv(currentEnv);
- Mockito.when(txnMgr.commitAndPublishTransaction(
+ Mockito.when(txnMgr.commitAndPublishTransactionWithRetry(
Mockito.any(), Mockito.anyList(), Mockito.anyLong(),
Mockito.anyList(), Mockito.anyLong(),
- Mockito.isNull())).thenReturn(false);
+ Mockito.isNull(), Mockito.anyList())).thenReturn(false);
OlapInsertExecutor executor = createExecutor(ctx);
executor.txnId = 10002L;
@@ -155,6 +155,33 @@ class OlapInsertExecutorTest {
}
}
+ @Test
+ void testOrdinaryInsertUsesRetryEntry() throws Exception {
+ ConnectContext ctx = createExecutorContext();
+ Coordinator coordinator = createCoordinator();
+ GlobalTransactionMgrIface txnMgr =
Mockito.mock(GlobalTransactionMgrIface.class);
+ TransactionState txnState = Mockito.mock(TransactionState.class);
+ Env currentEnv = createCurrentEnv(Mockito.mock(LoadManager.class));
+
+ try (MockedStatic<EnvFactory> envFactoryMock =
Mockito.mockStatic(EnvFactory.class);
+ MockedStatic<Env> envMock = Mockito.mockStatic(Env.class)) {
+ prepareFactoryMocks(envFactoryMock, envMock, coordinator, txnMgr,
txnState, currentEnv);
+ ctx.setEnv(currentEnv);
+ Mockito.when(txnMgr.commitAndPublishTransactionWithRetry(
+ Mockito.any(), Mockito.anyList(), Mockito.anyLong(),
Mockito.anyList(), Mockito.anyLong(),
+ Mockito.isNull(), Mockito.anyList())).thenReturn(true);
+
+ OlapInsertExecutor executor = createExecutor(ctx);
+ executor.txnId = 10006L;
+ executor.onComplete();
+
+ Mockito.verify(txnMgr).commitAndPublishTransactionWithRetry(
+ Mockito.eq(executor.getDatabase()), Mockito.anyList(),
Mockito.eq(10006L),
+ Mockito.anyList(), Mockito.anyLong(), Mockito.isNull(),
Mockito.anyList());
+ Assertions.assertEquals(TransactionStatus.VISIBLE,
executor.txnStatus);
+ }
+ }
+
@Test
void testOnFailAbortsUncommittedTransaction() throws Exception {
ConnectContext ctx = createExecutorContext();
@@ -226,7 +253,7 @@ class OlapInsertExecutorTest {
MockedStatic<Env> envMock = Mockito.mockStatic(Env.class)) {
prepareFactoryMocks(envFactoryMock, envMock, coordinator, txnMgr,
txnState, currentEnv);
ctx.setEnv(currentEnv);
- Mockito.when(txnMgr.commitAndPublishTransaction(
+ Mockito.when(txnMgr.commitAndPublishTransactionWithRetry(
Mockito.any(), Mockito.anyList(), Mockito.anyLong(),
Mockito.anyList(), Mockito.anyLong(),
Mockito.isNull(),
Mockito.eq(streamUpdateInfos))).thenReturn(true);
@@ -236,7 +263,7 @@ class OlapInsertExecutorTest {
executor.executeSingleInsert(stmtExecutor);
Mockito.verify(coordinator, Mockito.never()).exec();
- Mockito.verify(txnMgr).commitAndPublishTransaction(
+ Mockito.verify(txnMgr).commitAndPublishTransactionWithRetry(
Mockito.eq(executor.getDatabase()), Mockito.anyList(),
Mockito.eq(10005L),
Mockito.argThat(List::isEmpty), Mockito.anyLong(),
Mockito.isNull(),
Mockito.eq(streamUpdateInfos));
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]