You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I had searched in the issues and found no similar issues.
Version
Doris 3.0.8 (also verified against master branch code)
What's Wrong?
After a master FE failover (old master lost leadership due to OOM/GC pause but the process stayed alive and kept serving its MySQL/thrift ports), a non-master FE (observer/follower) with a lagging journal replay keeps forwarding statements to the old master, and clients continuously receive:
ERROR 1105 (HY000): errCode = 2, detailMessage = The statement has been forwarded to master FE(<old master ip>) and failed to execute because Master FE is not ready. You may need to check FE's status
The error persists until the observer's journal replay catches up (or the observer is restarted), because:
The forward target is Env.getMasterHost()/getMasterRpcPort() (MasterOpExecutor → FEOpExecutor), which is backed by the in-memory masterInfo field.
masterInfo is only updated by replaying the OP_MASTER_INFO_CHANGE journal entry written by the new master (EditLog.java → Env.setMaster()), or by loading an image at startup. There is no active master re-discovery on the forward path.
Meanwhile the old master process is still listening on its ports, so the forwarded thrift call succeeds at the transport level — it reaches StmtExecutor on the old master, which (as a proxy-forwarded statement, isProxy=true) detects it cannot find a valid master and throws the error above (StmtExecutor.executeByNereids/executeByLegacy, the isProxy branch) instead of the sender learning "this node is not master, retry elsewhere".
LB health checks (TCP-level) keep routing client connections to the degraded old master as well, compounding the issue.
Additionally, for FORWARD_WITH_SYNC statements (e.g. CREATE USER), the old master's error response still carries a maxJournalId, so the observer blocks in JournalObservable.waitOn() (up to query_timeout * 1.2, default ~18 min) before surfacing anything — the client just hangs.
What You Expected?
After a master failover, the non-master FE should stop forwarding statements to the old (degraded) master quickly — regardless of its journal replay lag. Either:
the forwarded request should be rejected early by the old master with a NOT_MASTER-style response (so the sender can learn the real master and retry), or
the sender (FEOpExecutor) should re-discover the current master on forward failure/NOT_MASTER and retry against the real master,
so that statements executed via the observer succeed (or fail fast with a clear, actionable error) instead of failing with "Master FE is not ready" (or hanging in journal-sync wait) for the whole meta_delay_toleration_second window.
How to Reproduce?
Reproduced deterministically on Doris 3.0.8 with 4 FEs in docker (fe1=master, fe2/fe3=followers, fe4=observer). nsenter-based iptables is used to simulate the degraded states (equivalent to what an OOM/GC-pause produces):
Freeze the observer's journal replay: block fe4 → fe2/fe3 on the bdbje port (9010), so fe4 cannot replay the new master's journals (its masterInfo stays = fe1).
Kill fe1 (docker kill, simulating the OOM death of the old master). fe2 is elected as the new master.
Restart fe1 with its bdbje isolated (block 9010 toward fe2/fe3/fe4). fe1 comes back "alive but degraded": MySQL (9030) and thrift (9020) ports still serve, but it is no longer the master and cannot rejoin/catch up — exactly the state of an OOM-recovered old master that lost the election.
Execute a forward-to-master statement via the observer (fe4):
$ mysql -h <fe4> -P 9030 -u root -e 'ADMIN SET FRONTEND CONFIG ("label_keep_max_second" = "259200")'
ERROR 1105 (HY000) at line 1: errCode = 2, detailMessage = The statement has been forwarded to master FE(172.20.80.2) and failed to execute because Master FE is not ready. You may need to check FE's status
(The IP in the message is the old master's own IP — the error is thrown by the old master itself.)
Evidence from the logs:
Observer (fe4) keeps forwarding to the old (dead/degraded) master after the switch:
[fe4 log] MasterOpExecutor.forward: forward to master FE TNetworkAddress(hostname:172.20.80.2, port:9020)
Old master (fe1) throws the error on the proxy-forwarded statement:
[fe1 log] StmtExecutor.executeByLegacy:1042 execute Exception.
org.apache.doris.common.UserException: errCode = 2, detailMessage = The statement has been forwarded to master FE(172.20.80.2) and failed to execute because Master FE is not ready. You may need to check FE's status
Notes:
With a FORWARD_WITH_SYNC statement (CREATE USER), the client instead hangs in journal-sync wait on the observer for up to ~18 minutes before failing.
The recovery observed in production (rolling restart of the observer) matches this analysis: a restart forces an image load + full journal replay, refreshing masterInfo to the new master.
meta_delay_toleration_second (default 300s) eventually marks the observer not-ready, which changes the failure mode but does not fix the stale-forward window (5 minutes of broken/hanging statements even in the best case).
BE-side protection against stale masters exists (e.g. commit f13bd7a "follower fe invalidate dns and be rpc skip old master" probes other FEs after a NOT_MASTER/transport failure), while the FE forward path still relies solely on journal-replay-updated masterInfo.
Suggested fix directions (for discussion):
In FrontendServiceImpl.forward(): when the receiving FE is not the master, return a NOT_MASTER-style response immediately (with the real master address as a hint, similar to [feature](thrift) Add FE thrift rpc redirect master address #25371) instead of executing the statement deep into StmtExecutor and throwing "Master FE is not ready".
In FEOpExecutor.forward(): on receiving NOT_MASTER (or on forward failure), re-discover the current master (e.g. via Env.getHaProtocol().getLeader() which asks bdbje directly and does not depend on journal replay) and retry once against the real master.
This combination would close the stale-forward window regardless of journal replay lag, and also prevents the journal-wait hang caused by the old master's stale maxJournalId in error responses.
Full reproduction scripts and timelines available if needed.
Search before asking
Version
Doris 3.0.8 (also verified against master branch code)
What's Wrong?
After a master FE failover (old master lost leadership due to OOM/GC pause but the process stayed alive and kept serving its MySQL/thrift ports), a non-master FE (observer/follower) with a lagging journal replay keeps forwarding statements to the old master, and clients continuously receive:
The error persists until the observer's journal replay catches up (or the observer is restarted), because:
Env.getMasterHost()/getMasterRpcPort()(MasterOpExecutor→FEOpExecutor), which is backed by the in-memorymasterInfofield.masterInfois only updated by replaying theOP_MASTER_INFO_CHANGEjournal entry written by the new master (EditLog.java→Env.setMaster()), or by loading an image at startup. There is no active master re-discovery on the forward path.StmtExecutoron the old master, which (as a proxy-forwarded statement,isProxy=true) detects it cannot find a valid master and throws the error above (StmtExecutor.executeByNereids/executeByLegacy, theisProxybranch) instead of the sender learning "this node is not master, retry elsewhere".Additionally, for
FORWARD_WITH_SYNCstatements (e.g.CREATE USER), the old master's error response still carries amaxJournalId, so the observer blocks inJournalObservable.waitOn()(up toquery_timeout * 1.2, default ~18 min) before surfacing anything — the client just hangs.What You Expected?
After a master failover, the non-master FE should stop forwarding statements to the old (degraded) master quickly — regardless of its journal replay lag. Either:
FEOpExecutor) should re-discover the current master on forward failure/NOT_MASTER and retry against the real master,so that statements executed via the observer succeed (or fail fast with a clear, actionable error) instead of failing with "Master FE is not ready" (or hanging in journal-sync wait) for the whole
meta_delay_toleration_secondwindow.How to Reproduce?
Reproduced deterministically on Doris 3.0.8 with 4 FEs in docker (fe1=master, fe2/fe3=followers, fe4=observer).
nsenter-based iptables is used to simulate the degraded states (equivalent to what an OOM/GC-pause produces):masterInfostays = fe1).docker kill, simulating the OOM death of the old master). fe2 is elected as the new master.(The IP in the message is the old master's own IP — the error is thrown by the old master itself.)
Evidence from the logs:
Notes:
FORWARD_WITH_SYNCstatement (CREATE USER), the client instead hangs in journal-sync wait on the observer for up to ~18 minutes before failing.masterInfoto the new master.meta_delay_toleration_second(default 300s) eventually marks the observer not-ready, which changes the failure mode but does not fix the stale-forward window (5 minutes of broken/hanging statements even in the best case).Anything Else?
Related prior work:
master_addressto thrift responses for BE→FE RPCs so the BE can skip an old master, but the FE→FEforward()path never got the equivalent treatment.masterInfo.Suggested fix directions (for discussion):
FrontendServiceImpl.forward(): when the receiving FE is not the master, return a NOT_MASTER-style response immediately (with the real master address as a hint, similar to [feature](thrift) Add FE thrift rpc redirect master address #25371) instead of executing the statement deep intoStmtExecutorand throwing "Master FE is not ready".FEOpExecutor.forward(): on receiving NOT_MASTER (or on forward failure), re-discover the current master (e.g. viaEnv.getHaProtocol().getLeader()which asks bdbje directly and does not depend on journal replay) and retry once against the real master.This combination would close the stale-forward window regardless of journal replay lag, and also prevents the journal-wait hang caused by the old master's stale
maxJournalIdin error responses.Full reproduction scripts and timelines available if needed.
Are you willing to submit PR?