github-actions[bot] commented on code in PR #66680:
URL: https://github.com/apache/doris/pull/66680#discussion_r3765732045


##########
be/src/exec/sink/writer/vtablet_writer.cpp:
##########
@@ -492,6 +492,32 @@ bool IndexChannel::_quorum_success(const 
std::unordered_set<int64_t>& unfinished
         }
     }
 
+    const auto& table_sink = _parent->_t_sink.olap_table_sink;
+    if (table_sink.__isset.cross_az_succ_quorum) {
+        std::unordered_set<int64_t> finished_node_ids;

Review Comment:
   The AZ gate needs tablet-level success here. A channel can finish 
successfully while `result.tablet_errors()` has already put this node in 
`_failed_channels[tablet_id]`; nevertheless this set adds the node for every 
tablet. For example, with three completed az1 successes, one completed tablet 
failure in az2, and one slow healthy az2 replica, ordinary quorum is genuinely 
satisfied but `az2:1` is falsely considered satisfied. We then enter the 
bounded post-quorum wait, can drop the healthy peer, and FE rejects a load that 
could have succeeded by continuing the normal wait. Please exclude per-tablet 
failures (and make the parallel v2 path require the tablet in 
`success_tablets()` rather than just a finished stream), with tests for both 
writers.



##########
fe/fe-common/src/main/java/org/apache/doris/common/Config.java:
##########
@@ -541,6 +550,40 @@ public class Config extends ConfigBase {
             + "a load job.")
     public static short min_load_replica_num = -1;
 
+    @ConfField(mutable = true, masterOnly = true, description = "Minimum 
number of successfully written replicas "
+            + "required in each availability zone for a load job.")
+    public static String[] cross_az_succ_quorum = {};
+
+    public static Map<String, Integer> getCrossAzSuccQuorum() {
+        String[] config = cross_az_succ_quorum;
+        if (config == cachedCrossAzSuccQuorumConfig) {
+            return cachedCrossAzSuccQuorum;
+        }
+        synchronized (Config.class) {
+            config = cross_az_succ_quorum;
+            if (config == cachedCrossAzSuccQuorumConfig) {
+                return cachedCrossAzSuccQuorum;
+            }
+            Map<String, Integer> parsedConfig = new HashMap<>();
+            for (String item : config) {
+                String[] parts = item.split(":", -1);
+                try {
+                    int configuredMin = Integer.parseInt(parts.length == 2 ? 
parts[1].trim() : "");
+                    if (parts[0].trim().isEmpty() || configuredMin < 0) {
+                        throw new NumberFormatException();
+                    }
+                    parsedConfig.put(parts[0].trim(), configuredMin);
+                } catch (NumberFormatException e) {

Review Comment:
   Malformed entries should make the config update fail atomically, not be 
dropped here. Today `ADMIN SET ... ("cross_az_succ_quorum" = 
"az1:2,az2:not-a-number")` succeeds, then commit enforces only az1; if every 
item is invalid, the parsed map is empty and the AZ fence is completely 
disabled. The added regression even codifies that fail-open behavior. Please 
validate the entire value in the config callback, reject any 
malformed/duplicate entry, and publish it only after full validation.



##########
gensrc/thrift/DataSinks.thrift:
##########
@@ -319,6 +319,7 @@ struct TOlapTableSink {
     // initial partition list is empty, so auto-partition tables whose first 
partitions arrive at
     // runtime still enter the correct mode from the start.
     25: optional bool enable_adaptive_random_bucket
+    26: optional map<string, i32> cross_az_succ_quorum

Review Comment:
   Making this optional preserves decoding, but not the feature's healthy-load 
behavior during a rolling upgrade. An old coordinator BE ignores the map and 
exits at ordinary quorum after its bounded grace; the PR's own 3-second 
slow-AZ/1-second-grace case then loses the required replica, while the new FE 
still enforces the AZ rule and rejects. Please fence nonempty activation until 
every possible coordinator BE advertises support (or reject/defer it in a mixed 
fleet), and add a new-FE/old-BE slow-AZ test.



##########
fe/fe-core/src/main/java/org/apache/doris/planner/RemoteOlapTableSink.java:
##########
@@ -61,7 +60,7 @@ public TPaloNodesInfo createPaloNodesInfo() {
             if (backend == null) {
                 continue;
             }
-            nodesInfo.addToNodes(new TNodeInfo(backend.getId(), 0, 
backend.getHost(), backend.getBrpcPort()));
+            nodesInfo.addToNodes(createNodeInfo(backend));

Review Comment:
   This cannot supply correct AZ data for remote writes. 
`FeServiceClient.listBackends` builds these objects from `TBackend`, which has 
no tag/location field, so every remote `Backend` keeps the `default` location; 
inherited `setNodesInfo` also attaches the source FE's policy, while 
`commitRemoteTxn` is validated under the target FE's policy. BE therefore 
clamps target `az1`/`az2` requirements to zero and may abandon a slow target-AZ 
replica before target FE rejects. Please carry the target policy and backend 
locations in remote metadata/transaction setup and bind that snapshot to this 
sink; add a two-cluster differing-policy test.



##########
fe/fe-common/src/main/java/org/apache/doris/common/Config.java:
##########
@@ -541,6 +550,40 @@ public class Config extends ConfigBase {
             + "a load job.")
     public static short min_load_replica_num = -1;
 
+    @ConfField(mutable = true, masterOnly = true, description = "Minimum 
number of successfully written replicas "
+            + "required in each availability zone for a load job.")
+    public static String[] cross_az_succ_quorum = {};
+
+    public static Map<String, Integer> getCrossAzSuccQuorum() {
+        String[] config = cross_az_succ_quorum;

Review Comment:
   This cache has no Java publication edge for a dynamic update. 
`ConfigBase.setMutableConfig` reflectively writes the plain 
`cross_az_succ_quorum` field under `ConfigBase.class`, while this 
unsynchronized read (and the slow path's `Config.class` lock) uses neither that 
monitor nor a volatile source reference; the setter also never writes the 
volatile cache fields. A load may therefore keep taking the old identity/map 
after an apparently successful enable. Please validate and publish one 
immutable policy through a volatile/atomic reference (and add a concurrent 
setter/reader test) instead of keying a cache off this unsafely published array.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to