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


##########
be/src/exec/sink/writer/vtablet_writer.cpp:
##########
@@ -136,6 +137,17 @@ Status IndexChannel::init(RuntimeState* state, const 
std::vector<TTabletWithPart
         if (tablet_locations == nullptr) {
             return Status::InternalError("unknown tablet, tablet_id={}", 
tablet.tablet_id);
         }
+        int selected_replicas_num = 
cast_set<int>(tablet_locations->node_ids.size());
+        int load_required_replicas_num = 
_load_required_replicas_num(tablet.tablet_id);
+        if (selected_replicas_num < load_required_replicas_num) {

Review Comment:
   [P1] Preserve the bootstrap tablet for empty automatic tables
   
   With `enable_memtable_on_sink_node=false`, the first load into an empty 
`AUTO PARTITION BY LIST (...) ()` table now fails even when every backend is 
healthy. `OlapTableSink.createDummyPartition()` creates partition/tablet 0, and 
`createDummyLocation()` intentionally gives it exactly one backend. The initial 
sink still assigns it the default allocation's required quorum of two, and 
`VOlapTablePartitionParam::init()` retains it in `_partitions` (the fake flag 
only excludes its keys from lookup). Thus a normal three-replica table reaches 
this check with selected=1, required=2 and returns `tablet 0 in partition 0 has 
1 selected replicas, fewer than required 2` before real partitions can be 
created.
   
   Handle the synthetic tablet separately in initialization and failure 
accounting while retaining quorum enforcement for real tablets. Add an 
end-to-end first-load case using an empty automatic table and the legacy 
writer; the new direct createPartition RPC tests do not exercise this bootstrap.



##########
fe/fe-core/src/main/java/org/apache/doris/catalog/Tablet.java:
##########
@@ -240,12 +240,19 @@ protected Multimap<Long, Long> 
getNormalReplicaBackendPathMapImpl(String beEndpo
     }
 
     // return map of (BE id -> path hash) of normal replicas
-    // for load plan.
     public Multimap<Long, Long> getNormalReplicaBackendPathMap() throws 
UserException {
         TabletSlidingWindowAccessStats.recordTablet(getId());
         return getNormalReplicaBackendPathMapImpl(null, (rep, be) -> 
rep.getBackendId());
     }
 
+    // return map of (BE id -> path hash) of replicas eligible for a new load 
plan
+    public Multimap<Long, Long> getLoadReplicaBackendPathMap() throws 
UserException {
+        Multimap<Long, Long> pathMap = getNormalReplicaBackendPathMap();
+        SystemInfoService infoService = Env.getCurrentSystemInfo();
+        pathMap.entries().removeIf(entry -> 
!infoService.checkBackendLoadAvailable(entry.getKey()));

Review Comment:
   [P1] Account for filtered replicas in the V2 writer as well
   
   The new filter also feeds `VTabletWriterV2`, but only legacy `IndexChannel` 
received selected-replica failure accounting. With 
`enable_memtable_on_sink_node=true`, replication=3, required=1, one-bucket 
partitions A on BEs{1,2,3} and B on{1,4,5}, disabling loads on BEs2/3 leaves A 
only BE1. If BE1 has a receiver-side rowset/delete-bitmap/commit failure during 
CLOSE_LOAD, `IndexStream::close()` reports A in `failed_tablets` with overall 
OK/EOS. Stream close still succeeds, and V2's `_create_commit_info()` tolerates 
that one failure against configured3-required1=2. B succeeds through4/5, while 
A produces no commit info; FE `checkCommitStatus()` consequently checks B only 
and the load can succeed with every A row lost. The existing 
`LoadStreamWriter.close.cancelled` point on BE1 provides a concrete late-close 
trigger.
   
   This is the separate V2 implementation, beyond the legacy-only instance in 
thread 4367374261. Apply selected-destination accounting to V2 too, or require 
successful participation of every touched partition, and add a V2 
mixed-partition late-close failure regression.



##########
regression-test/suites/fault_injection_p0/test_disable_load_selected_replica_failure.groovy:
##########
@@ -0,0 +1,96 @@
+// 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.
+
+import org.apache.doris.regression.suite.ClusterOptions
+import org.apache.doris.regression.util.NodeType
+
+suite("test_disable_load_selected_replica_failure", "docker") {
+    def options = new ClusterOptions()
+    options.cloudMode = false
+    options.feNum = 1
+    options.beNum = 5
+    options.feConfigs += ["disable_tablet_scheduler=true"]
+    options.enableDebugPoints()
+
+    docker(options) {
+        def backendIPs = [:]
+        def backendPorts = [:]
+        getBackendIpHttpPort(backendIPs, backendPorts)
+        def backendIds = backendIPs.keySet().sort { it.toLong() }
+        def sharedBackend = backendIds[0]
+        def disabledBackends = backendIds[1..2]
+        def tags = ["shared", "left", "left", "right", "right"]
+        backendIds.eachWithIndex { backendId, i ->
+            sql """ALTER SYSTEM MODIFY BACKEND "${backendId}" SET 
("tag.location" = "${tags[i]}")"""
+        }
+
+        def originalMemtableOnSink = sql("select 
@@enable_memtable_on_sink_node")[0][0]
+        sql "set enable_memtable_on_sink_node = false"
+        sql "drop table if exists disable_load_selected_replica_failure"
+        // Deterministic placement: A={1,2,3}, B={1,4,5}; both require one 
successful replica.
+        sql """
+            create table disable_load_selected_replica_failure (k int, v int)
+            duplicate key(k)
+            partition by range(k) (
+                partition p_a values less than ("10"),
+                partition p_b values less than ("30")
+                    ("replication_allocation" = "tag.location.shared: 1, 
tag.location.right: 2")
+            )
+            distributed by hash(k) buckets 1
+            properties (
+                "replication_allocation" = "tag.location.shared: 1, 
tag.location.left: 2",
+                "min_load_replica_num" = "1"
+            )
+        """
+
+        def point = "LoadChannel.add_batch.failed"
+        def debugPoint = GetDebugPoint()
+        try {
+            disabledBackends.each { backendId ->
+                sql """ALTER SYSTEM MODIFY BACKEND "${backendId}" SET 
("disable_load" = "true")"""
+            }
+            debugPoint.enableDebugPoint(backendIPs[sharedBackend],
+                    backendPorts[sharedBackend] as int, NodeType.BE, point)
+
+            // B still succeeds through BE4/5 while BE1's actual writer RPC 
fails.
+            sql "insert into disable_load_selected_replica_failure values (21, 
100)"

Review Comment:
   [P2] Seed the baseline before enabling the shared-backend fault
   
   This unqualified INSERT plans both A and B: absent an explicit PARTITION 
clause, the sink expands its partition IDs to the whole table. The legacy 
writer therefore opens A's tablet on BE1 and sends EOS to that node even though 
this row belongs only to B. With the fault already enabled, BE1's add-batch 
response cancels its channel; close handling calls `mark_as_failed(..., -1)`, 
which also marks untouched A failed. A has selected=required=1, so its new 
failure allowance is zero and this unwrapped setup statement errors before the 
following `test { ... }` or either atomicity assertion is reached.
   
   Insert the baseline before enabling the fault, or explicitly use 
`PARTITION(p_b)` for this seed so only the intended mixed-partition statement 
exercises A's failing destination.



-- 
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