xiangfu0 commented on code in PR #19537:
URL: https://github.com/apache/pinot/pull/19537#discussion_r4221916889


##########
pinot-query-planner/src/main/java/org/apache/pinot/query/routing/WorkerManager.java:
##########
@@ -1169,9 +1185,17 @@ private void 
setSegmentsForReplicatedLeafFragment(DispatchablePlanMetadata metad
       }
     }
 
-    // TODO: Support unavailable segments and optional segments for replicated 
leaf stage
     metadata.setReplicatedSegments(segmentsMap);
     filterReplicatedLeafStageSegments(context, metadata);
+
+    Map<String, RoutingTable> routingTableMap =

Review Comment:
   **Second full routing computation per replicated-leaf query.** This 
`getRoutingTable` call runs complete instance selection + pruning solely to 
read `getUnavailableSegments()`, on top of the `getSegments()` call above — 
every replicated-table query pays it even when nothing is unavailable. It also 
snapshots routing at a different time than `getSegments()`, so the unavailable 
list can disagree with the segment list under concurrent state changes. A 
routing-manager API that exposes only unavailable segments (or reusing a single 
routing acquisition for both) would avoid the duplicated work.



##########
pinot-query-planner/src/main/java/org/apache/pinot/query/routing/WorkerManager.java:
##########
@@ -2075,4 +2108,35 @@ private static Map<String, List<String>> 
getSegmentsMap(@Nullable List<String> o
       return Map.of(TableType.REALTIME.name(), realtimeSegments);
     }
   }
+
+  private void 
assignWorkersForNonPartitionedLeafSegmentsWhenNoServersHaveSegments(DispatchablePlanMetadata
 metadata,
+      String tableName, Map<String, RoutingTable> routingTableMap, 
DispatchablePlanContext context,
+      @Nullable TimeBoundaryInfo timeBoundaryInfo) {
+    Set<String> serversRoutingToTable = 
_routingManager.getServingInstances(tableName);
+    if (CollectionUtils.isEmpty(serversRoutingToTable)) {
+      LOGGER.error("[RequestId: {}] No routable server for empty routing on 
table: {}", context.getRequestId(),
+          tableName);
+      throw new IllegalStateException("No routable server for empty routing on 
table: " + tableName);
+    }
+
+    Map<String, ServerInstance> serverInstanceMap = 
_routingManager.getRoutableServerInstanceMap();
+    ServerInstance serverInstance = 
serverInstanceMap.get(serversRoutingToTable.iterator().next());
+    Map<String, List<String>> emptySegmentsMap = new HashMap<>();
+    for (String tableType : routingTableMap.keySet()) {
+      emptySegmentsMap.put(tableType, List.of());
+    }
+    if (emptySegmentsMap.isEmpty()) {

Review Comment:
   **Dead branch + immutable collections handed to an extension point.** 
`routingTableMap.isEmpty()` already throws `BROKER_RESOURCE_MISSING` before 
this method is reachable, and the hybrid OFFLINE removal only happens when 
`size() > 1`, so the loop above always inserts at least one key — this whole 
`if (emptySegmentsMap.isEmpty())` block (and the `timeBoundaryInfo` parameter 
that only it reads) can never execute. Separately, `updateContextForLeafStage` 
then runs `filterLeafStageSegments`, whose contract says the handed-over 
assignment "is nevertheless kept safe to edit in place"; `Map.of(0, ...)` / 
`List.of()` would throw `UnsupportedOperationException` on a subclass that 
edits in place.



##########
pinot-query-planner/src/test/java/org/apache/pinot/query/routing/WorkerManagerTest.java:
##########
@@ -3115,4 +3115,133 @@ public boolean isTableDisabled(String 
tableNameWithType) {
       return false;
     }
   }
+
+  @Test
+  public void 
testNonPartitionedLeafPropagatesOptionalSegmentsToWorkerMetadata() {
+    Schema tableWithOptionalSegmentsSchema = 
getSchemaBuilder("tableWithOptionalSegments").build();
+
+    // Create server instance
+    ServerInstance server = getServerInstance("localhost", 1);
+    Map<String, ServerInstance> serverInstanceMap = new HashMap<>();
+    serverInstanceMap.put(server.getInstanceId(), server);
+
+    // Create a routing table with both optional and normal segments
+    RoutingTable routingTable =
+        new RoutingTable(Map.of(server, new SegmentsToQuery(List.of("seg"), 
List.of("optSeg"))),
+            List.of("unavailableSeg"), 0);
+
+    // Create mock routing manager
+    CapturingRoutingManager routingManager = new 
CapturingRoutingManager(serverInstanceMap,
+        Map.of("tableWithOptionalSegments_OFFLINE", routingTable));
+
+    // Create mock table cache
+    Map<String, String> tableNameMap = new HashMap<>();
+    tableNameMap.put("tableWithOptionalSegments_OFFLINE", 
"tableWithOptionalSegments_OFFLINE");
+    tableNameMap.put("tableWithOptionalSegments", "tableWithOptionalSegments");
+
+    TableCache tableCache = mock(TableCache.class);
+    when(tableCache.getTableNameMap()).thenReturn(tableNameMap);
+    when(tableCache.getActualTableName(anyString())).thenAnswer(inv -> 
tableNameMap.get(inv.getArgument(0)));
+    
when(tableCache.getSchema(anyString())).thenReturn(tableWithOptionalSegmentsSchema);
+    
when(tableCache.getTableConfig("tableWithOptionalSegments_OFFLINE")).thenReturn(mock(TableConfig.class));
+
+    WorkerManager workerManager = new WorkerManager("Broker_localhost", 
"localhost", 3, routingManager);
+    QueryEnvironment queryEnvironment = new 
QueryEnvironment(CommonConstants.DEFAULT_DATABASE, tableCache,
+        workerManager);
+
+    String query = "SELECT * FROM tableWithOptionalSegments LIMIT 10";
+
+    try (QueryEnvironment.CompiledQuery compiledQuery = 
queryEnvironment.compile(query)) {
+      DispatchableSubPlan dispatchableSubPlan = 
compiledQuery.planQuery(0).getQueryPlan();
+      assertNotNull(dispatchableSubPlan);
+      assertEquals(List.of("unavailableSeg"),
+          
dispatchableSubPlan.getTableToUnavailableSegmentsMap().get("tableWithOptionalSegments"));
+      // This stage should handle basic data reads
+      DispatchablePlanFragment dataReadPlanFragment = 
dispatchableSubPlan.getQueryStages().getLast();
+      List<WorkerMetadata> workerMetadataList = 
dataReadPlanFragment.getWorkerMetadataList();
+      
assertNotNull(workerMetadataList.getFirst().getOptionalTableSegmentsMap());
+      
assertEquals(workerMetadataList.getFirst().getOptionalTableSegmentsMap().size(),
 1);
+      
assertTrue(workerMetadataList.getFirst().getOptionalTableSegmentsMap().containsKey("OFFLINE"));
+      assertEquals(List.of("optSeg"), 
workerMetadataList.getFirst().getOptionalTableSegmentsMap().get("OFFLINE"));
+      assertNotNull(workerMetadataList.getFirst().getTableSegmentsMap());
+      assertEquals(workerMetadataList.getFirst().getTableSegmentsMap().size(), 
1);
+      
assertTrue(workerMetadataList.getFirst().getTableSegmentsMap().containsKey("OFFLINE"));
+      assertEquals(List.of("seg"), 
workerMetadataList.getFirst().getTableSegmentsMap().get("OFFLINE"));
+    }
+  }
+
+  @Test
+  public void 
testWorkerAssignmentForNonPartitionedLeadSegmentsWhenNoServersHaveSegments() {
+    Schema tableWithNoServersHavingSegmentsSchema = 
getSchemaBuilder("tableWithNoServersHavingSegments").build();
+    Schema anotherTableSchema = getSchemaBuilder("anotherTable").build();
+
+    // Create server instance to server the table have no segments
+    ServerInstance server = getServerInstance("localhost", 1);
+    Map<String, ServerInstance> serverInstanceMap = new HashMap<>();
+    serverInstanceMap.put(server.getInstanceId(), server);
+
+    // Create server instance to server the other normal table
+    ServerInstance server2 = getServerInstance("localhost", 2);
+    serverInstanceMap.put(server2.getInstanceId(), server);

Review Comment:
   Copy-paste bug: this registers `server` (port 1) under `server2`'s instance 
id, so both ids resolve to the same `ServerInstance` and the test cannot tell 
which server the fallback actually picked — it would keep passing even if the 
implementation chose the wrong instance id.
   
   ```suggestion
       serverInstanceMap.put(server2.getInstanceId(), server2);
   ```



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