This is an automated email from the ASF dual-hosted git repository.

deardeng 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 ae61e64702e [refactor](fe) Extract forward handling and bound the 
group commit BE cache (#65607)
ae61e64702e is described below

commit ae61e64702e051306418cd86a0375b291f2f5308
Author: deardeng <[email protected]>
AuthorDate: Wed Jul 15 16:19:51 2026 +0800

    [refactor](fe) Extract forward handling and bound the group commit BE cache 
(#65607)
    
    Problem Summary:
    
    Pure refactor and cleanup in the FE forward / group commit path. No SQL,
    protocol
      or scheduling semantics change.
    
    1. **`FrontendServiceImpl.forward()` is hard to follow and leaks on the
    error path.**
    The method inlined requester validation, four shortcut responses,
    context and
    processor construction, proxy-query registration and execution in one
    body. It is
    now split into intent-revealing helpers (validate / shortcut / context /
    processor
         / execute).
    
    `ConnectContext.remove()` and the proxy-query clear callback used to run
    only on
    the success path — if `proxyExecute()` threw, the thread-local context
    and the
    `proxyQueryIdToConnCtx` entry were both left behind. They now run in a
    `finally`
         block.
    
    The two group commit shortcuts answer without writing a journal, so they
    set
    `maxJournalId` to `0` explicitly instead of relying on the default value
    of that
    required field. This does not change the wire format — Thrift always
    writes
         required primitives — it just makes the intent readable.
    
    2. **`MasterOpExecutor` waited on a journal id that is never produced.**
    `getGroupCommitLoadBeId()` and `updateLoadData()` called
    `waitOnReplaying()`, but
    the master answers both without writing a journal, so the result carries
    no journal
    id and the call degenerates into `waitOn(0)`. It was a no-op that logged
    one INFO
         line per request on a hot path. Removed.
    
    3. **`GroupCommitManager` leaked one entry per group-commit table,
    forever.**
    `tableToBeMap` (table -> BE id) and `tableToPressureMap` (table ->
    pressure counter)
    were plain maps with no removal, expiry or drop-table cleanup path, so a
    workload
    that group-commit-loads many short-lived tables grew both maps without
    bound. Both
    are now bounded caches with the same limits. Keys and BE selection logic
    are
         unchanged.
    
    Reading the pressure counter no longer does `containsKey()` followed by
    `get()`,
         which could NPE if the entry was evicted between the two calls.
    
      4. **`FrontendHbResponse.toString()` printed the wrong field.**
    It printed `processUUID` under the `festartTime` label and never printed
         `feStartTime`.
---
 .../org/apache/doris/load/GroupCommitManager.java  |  58 ++++----
 .../java/org/apache/doris/qe/MasterOpExecutor.java |   4 +-
 .../apache/doris/service/FrontendServiceImpl.java  | 153 +++++++++++++--------
 .../apache/doris/system/FrontendHbResponse.java    |   3 +-
 4 files changed, 136 insertions(+), 82 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/load/GroupCommitManager.java 
b/fe/fe-core/src/main/java/org/apache/doris/load/GroupCommitManager.java
index 534d38a09ee..73cb7f2c4f4 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/load/GroupCommitManager.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/load/GroupCommitManager.java
@@ -40,6 +40,8 @@ import org.apache.doris.thrift.TNetworkAddress;
 import org.apache.doris.thrift.TStatusCode;
 
 import com.google.common.base.Strings;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.jetbrains.annotations.Nullable;
@@ -48,10 +50,9 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 public class GroupCommitManager {
@@ -60,10 +61,19 @@ public class GroupCommitManager {
 
     private Set<Long> blockedTableIds = new HashSet<>();
 
-    // Encoded <Cluster and Table id> to BE id map. Only for group commit.
-    private final Map<String, Long> tableToBeMap = new ConcurrentHashMap<>();
+    // Encoded <Cluster and Table id> to BE id cache. Only for group commit.
+    // Bounded so that dropped tables cannot keep their entries alive forever.
+    private final Cache<String, Long> tableToBeMap = CacheBuilder.newBuilder()
+            .maximumSize(10000)
+            .expireAfterAccess(1, TimeUnit.HOURS)
+            .build();
     // Table id to pressure map. Only for group commit.
-    private final Map<Long, SlidingWindowCounter> tableToPressureMap = new 
ConcurrentHashMap<>();
+    // Bounded like tableToBeMap above: the two hold one entry per 
group-commit table and are only
+    // meaningful together, so a dropped table must not keep either of them 
alive.
+    private final Cache<Long, SlidingWindowCounter> tableToPressureMap = 
CacheBuilder.newBuilder()
+            .maximumSize(10000)
+            .expireAfterAccess(1, TimeUnit.HOURS)
+            .build();
 
     public boolean isBlock(long tableId) {
         return blockedTableIds.contains(tableId);
@@ -277,7 +287,7 @@ public class GroupCommitManager {
             throws DdlException, LoadException {
         if (LOG.isDebugEnabled()) {
             LOG.debug("cloud group commit select be info, tableToBeMap {}, 
tablePressureMap {}",
-                    tableToBeMap.toString(), tableToPressureMap.toString());
+                    tableToBeMap.asMap().toString(), 
tableToPressureMap.asMap().toString());
         }
         if (Strings.isNullOrEmpty(cluster)) {
             ErrorReport.reportDdlException(ErrorCode.ERR_NO_CLUSTER_ERROR);
@@ -309,8 +319,8 @@ public class GroupCommitManager {
 
     private long selectBackendForLocalGroupCommitInternal(long tableId) throws 
LoadException {
         if (LOG.isDebugEnabled()) {
-            LOG.debug("group commit select be info, tableToBeMap {}, 
tablePressureMap {}", tableToBeMap.toString(),
-                    tableToPressureMap.toString());
+            LOG.debug("group commit select be info, tableToBeMap {}, 
tablePressureMap {}",
+                    tableToBeMap.asMap().toString(), 
tableToPressureMap.asMap().toString());
         }
         Long cachedBackendId = getCachedBackend(null, tableId);
         if (cachedBackendId != null) {
@@ -345,26 +355,25 @@ public class GroupCommitManager {
     @Nullable
     private Long getCachedBackend(String cluster, long tableId) {
         OlapTable table = (OlapTable) 
Env.getCurrentEnv().getInternalCatalog().getTableByTableId(tableId);
-        if (tableToBeMap.containsKey(encode(cluster, tableId))) {
-            if (tableToPressureMap.get(tableId) == null) {
+        String cacheKey = encode(cluster, tableId);
+        // There are multiple threads getting cached backends for the same 
table.
+        // Maybe one thread removes the tableId from the tableToBeMap.
+        // Another thread gets the same tableId but can not find this tableId.
+        // So another thread needs to get the random backend.
+        Long backendId = tableToBeMap.getIfPresent(cacheKey);
+        if (backendId != null) {
+            SlidingWindowCounter pressure = 
tableToPressureMap.getIfPresent(tableId);
+            if (pressure == null) {
                 return null;
-            } else if (tableToPressureMap.get(tableId).get() < 
table.getGroupCommitDataBytes()) {
-                // There are multiple threads getting cached backends for the 
same table.
-                // Maybe one thread removes the tableId from the tableToBeMap.
-                // Another thread gets the same tableId but can not find this 
tableId.
-                // So another thread needs to get the random backend.
-                Long backendId = tableToBeMap.get(encode(cluster, tableId));
-                if (backendId == null) {
-                    return null;
-                }
+            } else if (pressure.get() < table.getGroupCommitDataBytes()) {
                 Backend backend = 
Env.getCurrentSystemInfo().getBackend(backendId);
                 if (isBackendAvailable(backend, cluster)) {
                     return backend.getId();
                 } else {
-                    tableToBeMap.remove(encode(cluster, tableId));
+                    tableToBeMap.invalidate(cacheKey);
                 }
             } else {
-                tableToBeMap.remove(encode(cluster, tableId));
+                tableToBeMap.invalidate(cacheKey);
             }
         }
         return null;
@@ -426,11 +435,12 @@ public class GroupCommitManager {
     }
 
     private void updateLoadDataInternal(long tableId, long receiveData) {
-        if (tableToPressureMap.containsKey(tableId)) {
-            tableToPressureMap.get(tableId).add(receiveData);
+        SlidingWindowCounter pressure = 
tableToPressureMap.getIfPresent(tableId);
+        if (pressure != null) {
+            pressure.add(receiveData);
             if (LOG.isDebugEnabled()) {
                 LOG.debug("Update load data for table {}, receiveData {}, 
tablePressureMap {}", tableId, receiveData,
-                        tableToPressureMap.toString());
+                        tableToPressureMap.asMap().toString());
             }
         } else if (LOG.isDebugEnabled()) {
             LOG.debug("can not find table id {}", tableId);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/MasterOpExecutor.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/MasterOpExecutor.java
index 067db673049..892dd3cfd73 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/MasterOpExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/MasterOpExecutor.java
@@ -75,15 +75,15 @@ public class MasterOpExecutor extends FEOpExecutor {
         waitOnReplaying();
     }
 
+    // The master handles the group commit shortcuts without writing a 
journal, so the result carries
+    // no journal id. Waiting on journal 0 is a no-op that only logs one line 
per request.
     public long getGroupCommitLoadBeId(long tableId, String cluster) throws 
Exception {
         result = forward(buildGetGroupCommitLoadBeIdParmas(tableId, cluster));
-        waitOnReplaying();
         return result.groupCommitLoadBeId;
     }
 
     public void updateLoadData(long tableId, long receiveData) throws 
Exception {
         result = forward(buildUpdateLoadDataParams(tableId, receiveData));
-        waitOnReplaying();
     }
 
     private TMasterOpRequest buildSyncJournalParams() {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java 
b/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
index afd0ed46b2a..ef405f907a1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
@@ -1152,61 +1152,97 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
 
     @Override
     public TMasterOpResult forward(TMasterOpRequest params) throws TException {
+        validateForwardRequester(params);
+        TMasterOpResult shortcut = handleForwardShortcut(params);
+        if (shortcut != null) {
+            return shortcut;
+        }
+        logForwardRequest(params);
+        ConnectContext context = createForwardContext(params);
+        ConnectProcessor processor = createForwardProcessor(context);
+        Runnable clearCallback = registerProxyQuery(params, context);
+        try {
+            return executeForward(params, context, processor);
+        } finally {
+            ConnectContext.remove();
+            clearCallback.run();
+        }
+    }
+
+    private void validateForwardRequester(TMasterOpRequest params) throws 
TException {
         Frontend fe = 
Env.getCurrentEnv().checkFeExist(params.getClientNodeHost(), 
params.getClientNodePort());
-        if (fe == null) {
-            LOG.warn("reject request from invalid host. client: {}", 
params.getClientNodeHost());
-            throw new TException("request from invalid host was rejected.");
+        if (fe != null) {
+            return;
         }
+        LOG.warn("reject request from invalid host. client: {}", 
params.getClientNodeHost());
+        throw new TException("request from invalid host was rejected.");
+    }
+
+    private TMasterOpResult handleForwardShortcut(TMasterOpRequest params) 
throws TException {
         if (params.isSyncJournalOnly()) {
-            final TMasterOpResult result = new TMasterOpResult();
-            result.setMaxJournalId(Env.getCurrentEnv().getMaxJournalId());
-            // just make the protocol happy
-            result.setPacket("".getBytes());
-            return result;
+            return createForwardResultWithJournalSync();
         }
         if (params.getGroupCommitInfo() != null && 
params.getGroupCommitInfo().isGetGroupCommitLoadBeId()) {
-            final TGroupCommitInfo info = params.getGroupCommitInfo();
-            final TMasterOpResult result = new TMasterOpResult();
-            try {
-                
result.setGroupCommitLoadBeId(Env.getCurrentEnv().getGroupCommitManager()
-                        
.selectBackendForGroupCommitInternal(info.groupCommitLoadTableId, 
info.cluster));
-            } catch (LoadException | DdlException e) {
-                throw new TException(e.getMessage());
-            }
-            // just make the protocol happy
-            result.setPacket("".getBytes());
-            return result;
+            return handleGroupCommitLoadBeId(params.getGroupCommitInfo());
         }
         if (params.getGroupCommitInfo() != null && 
params.getGroupCommitInfo().isUpdateLoadData()) {
-            final TGroupCommitInfo info = params.getGroupCommitInfo();
-            final TMasterOpResult result = new TMasterOpResult();
             Env.getCurrentEnv().getGroupCommitManager()
-                    .updateLoadData(info.tableId, info.receiveData);
-            // just make the protocol happy
-            result.setPacket("".getBytes());
-            return result;
+                    .updateLoadData(params.getGroupCommitInfo().tableId, 
params.getGroupCommitInfo().receiveData);
+            return createForwardResultWithoutJournalSync();
         }
-        if (params.isSetCancelQeury() && params.isCancelQeury()) {
-            if (!params.isSetQueryId()) {
-                throw new TException("a query id is needed to cancel a query");
-            }
-            TUniqueId queryId = params.getQueryId();
-            ConnectContext ctx = proxyQueryIdToConnCtx.get(queryId);
-            if (ctx != null) {
-                ctx.cancelQuery(new Status(TStatusCode.CANCELLED, "cancel 
query by forward request."));
-            }
-            final TMasterOpResult result = new TMasterOpResult();
-            result.setStatusCode(0);
-            result.setMaxJournalId(Env.getCurrentEnv().getMaxJournalId());
-            // just make the protocol happy
-            result.setPacket("".getBytes());
-            return result;
+        if (!params.isSetCancelQeury() || !params.isCancelQeury()) {
+            return null;
+        }
+        return handleForwardCancel(params);
+    }
+
+    private TMasterOpResult createForwardResultWithJournalSync() {
+        TMasterOpResult result = new TMasterOpResult();
+        result.setMaxJournalId(Env.getCurrentEnv().getMaxJournalId());
+        result.setPacket("".getBytes());
+        return result;
+    }
+
+    private TMasterOpResult createForwardResultWithoutJournalSync() {
+        TMasterOpResult result = new TMasterOpResult();
+        // Group commit shortcuts update master memory without producing a 
journal id. Say so explicitly
+        // instead of relying on the default value of the required 
maxJournalId field.
+        result.setMaxJournalId(0L);
+        result.setPacket("".getBytes());
+        return result;
+    }
+
+    private TMasterOpResult handleGroupCommitLoadBeId(TGroupCommitInfo info) 
throws TException {
+        TMasterOpResult result = createForwardResultWithoutJournalSync();
+        try {
+            
result.setGroupCommitLoadBeId(Env.getCurrentEnv().getGroupCommitManager()
+                    
.selectBackendForGroupCommitInternal(info.groupCommitLoadTableId, 
info.cluster));
+        } catch (LoadException | DdlException e) {
+            throw new TException(e.getMessage());
         }
+        return result;
+    }
+
+    private TMasterOpResult handleForwardCancel(TMasterOpRequest params) 
throws TException {
+        if (!params.isSetQueryId()) {
+            throw new TException("a query id is needed to cancel a query");
+        }
+        ConnectContext context = 
proxyQueryIdToConnCtx.get(params.getQueryId());
+        if (context != null) {
+            context.cancelQuery(new Status(TStatusCode.CANCELLED, "cancel 
query by forward request."));
+        }
+        TMasterOpResult result = createForwardResultWithJournalSync();
+        result.setStatusCode(0);
+        return result;
+    }
 
-        // add this log so that we can track this stmt
+    private void logForwardRequest(TMasterOpRequest params) {
         if (LOG.isDebugEnabled()) {
             LOG.debug("receive forwarded stmt {} from FE: {}", 
params.getStmtId(), params.getClientNodeHost());
         }
+    }
+
+    private ConnectContext createForwardContext(TMasterOpRequest params) {
         ConnectContext context = new ConnectContext(null, true, 
params.getSessionId());
         // Set current connected FE to the client address, so that we can know 
where
         // this request come from.
@@ -1214,28 +1250,35 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
         if (Config.isCloudMode() && 
!Strings.isNullOrEmpty(params.getCloudCluster())) {
             context.setCloudCluster(params.getCloudCluster());
         }
+        return context;
+    }
 
-        ConnectProcessor processor = null;
+    private ConnectProcessor createForwardProcessor(ConnectContext context) 
throws TException {
         if (context.getConnectType().equals(ConnectType.MYSQL)) {
-            processor = new MysqlConnectProcessor(context);
-        } else if 
(context.getConnectType().equals(ConnectType.ARROW_FLIGHT_SQL)) {
-            processor = new FlightSqlConnectProcessor(context);
-        } else {
-            throw new TException("unknown ConnectType: " + 
context.getConnectType());
+            return new MysqlConnectProcessor(context);
+        }
+        if (context.getConnectType().equals(ConnectType.ARROW_FLIGHT_SQL)) {
+            return new FlightSqlConnectProcessor(context);
         }
-        Runnable clearCallback = () -> {};
-        if (params.isSetQueryId()) {
-            proxyQueryIdToConnCtx.put(params.getQueryId(), context);
-            clearCallback = () -> 
proxyQueryIdToConnCtx.remove(params.getQueryId());
+        throw new TException("unknown ConnectType: " + 
context.getConnectType());
+    }
+
+    private Runnable registerProxyQuery(TMasterOpRequest params, 
ConnectContext context) {
+        if (!params.isSetQueryId()) {
+            return () -> {};
         }
+        proxyQueryIdToConnCtx.put(params.getQueryId(), context);
+        return () -> proxyQueryIdToConnCtx.remove(params.getQueryId());
+    }
+
+    private TMasterOpResult executeForward(TMasterOpRequest params, 
ConnectContext context,
+            ConnectProcessor processor) throws TException {
         TMasterOpResult result = processor.proxyExecute(params);
         if 
(QueryState.MysqlStateType.ERR.name().equalsIgnoreCase(result.getStatus())) {
             context.getState().setError(result.getStatus());
-        } else {
-            context.getState().setOk();
+            return result;
         }
-        ConnectContext.remove();
-        clearCallback.run();
+        context.getState().setOk();
         return result;
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/system/FrontendHbResponse.java 
b/fe/fe-core/src/main/java/org/apache/doris/system/FrontendHbResponse.java
index 0bbc72008dd..be8eecfc193 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/system/FrontendHbResponse.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/system/FrontendHbResponse.java
@@ -121,7 +121,8 @@ public class FrontendHbResponse extends HeartbeatResponse 
implements Writable {
         sb.append(", rpcPort: ").append(rpcPort);
         sb.append(", arrowFlightSqlPort: ").append(arrowFlightSqlPort);
         sb.append(", replayedJournalId: ").append(replayedJournalId);
-        sb.append(", festartTime: ").append(processUUID);
+        sb.append(", feStartTime: ").append(feStartTime);
+        sb.append(", processUUID: ").append(processUUID);
         return sb.toString();
     }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to