fix: handle optional segments, unavailable segments, and empty partitions in WorkerManager (#18223) - #19144
Conversation
…ions in WorkerManager (apache#18223)
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Updates WorkerManager routing/assignment logic to better tolerate optional segments, unavailable segments, and partitions with no segments, addressing #18223.
Changes:
- Merge required + optional segments when building server→segment mappings for multi-stage execution.
- Propagate unavailable segments into dispatchable metadata for replicated leaf stages.
- Skip (instead of failing) partitions that have no segments, treating them as pruned/empty.
| // Attach unavailable segments to metadata for replicated leaf stage | ||
| Map<String, RoutingTable> routingTableMap = getRoutingTable(tableName, context.getRequestId()); | ||
| for (Map.Entry<String, RoutingTable> entry : routingTableMap.entrySet()) { | ||
| if (!entry.getValue().getUnavailableSegments().isEmpty()) { | ||
| metadata.addUnavailableSegments(tableName, entry.getValue().getUnavailableSegments()); | ||
| } | ||
| } |
| // Merge required and optional segments for multi-stage engine. | ||
| List<String> segments = serverEntry.getValue().getSegments(); | ||
| List<String> optionalSegments = serverEntry.getValue().getOptionalSegments(); | ||
| if (optionalSegments != null && !optionalSegments.isEmpty()) { | ||
| List<String> combinedSegments = new ArrayList<>(segments.size() + optionalSegments.size()); | ||
| combinedSegments.addAll(segments); | ||
| combinedSegments.addAll(optionalSegments); | ||
| segments = Collections.unmodifiableList(combinedSegments); | ||
| } |
| // Merge required and optional segments for multi-stage engine. | ||
| List<String> segments = serverEntry.getValue().getSegments(); | ||
| List<String> optionalSegments = serverEntry.getValue().getOptionalSegments(); | ||
| if (optionalSegments != null && !optionalSegments.isEmpty()) { | ||
| List<String> combinedSegments = new ArrayList<>(segments.size() + optionalSegments.size()); | ||
| combinedSegments.addAll(segments); | ||
| combinedSegments.addAll(optionalSegments); | ||
| segments = Collections.unmodifiableList(combinedSegments); | ||
| } |
| if (partitionInfo == null) { | ||
| LOGGER.warn("No segment found for table: {}, partition: {}, skipping", tableName, i); | ||
| continue; | ||
| } |
|
Thanks for picking this up. Unfortunately I don't think this can be merged in its current form — I traced each of the four changes through to its runtime behaviour, and the common problem is that these four TODOs are not unimplemented features but load-bearing guards. Removing them without building the mechanism each one is waiting for turns loud, correct failures (and one documented, bounded gap) into either a new hard failure on a routine steady-state condition, or silently wrong results. Line references below are against 1. Merging optional segments into the required list inverts the meaning of the fieldThis affects both A segment is classified as optional precisely when the selected replica is not ONLINE. From
"New" here means younger than The two-list split is the feature.
Required segments that fail to acquire land in
There is no optional-segment channel in the MSE stage metadata at all, so merging the lists at the routing site does not add support for optional segments — it promotes them to required. The consequence chain:
So the net effect of this hunk: any MSE query touching a table that has a segment created in the last 5 minutes which is not yet ONLINE on the selected replica now fails outright. That is a routine condition — it occurs after every realtime segment commit and after every offline push. Before this change the same query succeeded, missing at most a few of the very newest rows. That bounded freshness gap is what the TODO is acknowledging; the diff replaces it with a hard failure. It is also racy rather than deterministic: whether the server happens to have already loaded the segment decides whether the query fails, so in production this surfaces as intermittent, timing- and load-dependent query failures rather than a reproducible bug. Two smaller defects in the same hunks:
2. Skipping empty partitions breaks the partition-to-workerId identity, silentlyThis one is documented directly above the changed code.
enforced by the
The new Concrete wrong-results case — colocated join Separately, the premise that It also does not fix the failure this is most often reported as. A partition whose segments are genuinely unavailable produces a non-null Finally, the change is internally inconsistent with the sibling path: On the replacement comment — "The leaf stage can handle empty partitions by returning an empty response, which is equivalent to a pruned partition" is asserted without demonstration, and it is also beside the point: the diff does not create an empty worker, it removes the worker. The blocker was never the empty response, it is worker-id alignment. 3. Attaching unavailable segments in
|
Closes #18223