Skip to content

Commit ee5b2e8

Browse files
xiangguangyxgclaude
andcommitted
[Refactor] Unify position-fallback tabletId-to-bucketSeq fill into one facade helper
The position-fallback tabletId->bucketSeq fill policy was implemented twice: OlapScanNode.fillTabletId2BucketSeq (early-return form) and inline in PlanFragmentBuilder.visitPhysicalOlapScan (nested if/else form). Two independent producers of the same policy can silently drift. Collapse both into a single static helper RangeColocateScanDispatch.fillTabletId2BucketSeq that writes into a caller-supplied target map, and route both call sites through it. RangeColocateScanDispatch already owns computeBucketSeq and the sibling range-colocate scan helpers, so it is the natural home. This is a behavior-preserving extraction: the helper reproduces both sites exactly for dispatch absent, dispatch present + aligned, and dispatch present + transiently unaligned. Add focused unit tests for the three cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: xiangguangyxg <xiangguangyxg@gmail.com>
1 parent 202255e commit ee5b2e8

4 files changed

Lines changed: 115 additions & 51 deletions

File tree

fe/fe-core/src/main/java/com/starrocks/planner/OlapScanNode.java

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -885,40 +885,15 @@ private void computeTabletInfo() throws StarRocksException {
885885
scanTabletIds.addAll(allTabletIds);
886886
}
887887

888-
fillTabletId2BucketSeq(dispatch, selectedIndex, allTabletIds);
888+
RangeColocateScanDispatch.fillTabletId2BucketSeq(
889+
dispatch, selectedIndex, allTabletIds, tabletId2BucketSeq);
889890
totalTabletsNum += selectedIndex.getTablets().size();
890891
selectedTabletsNum += tablets.size();
891892
addScanRangeLocations(partition, physicalPartition, selectedIndex, tablets, List.of(), localBeId);
892893
}
893894
}
894895
}
895896

896-
/**
897-
* Populates {@link #tabletId2BucketSeq} for one {@link MaterializedIndex}.
898-
* Range-colocate scans use the bucket sequence supplied by the dispatch
899-
* facade when alignment holds; everything else (HASH, range non-colocate,
900-
* or transiently unaligned range colocate) falls back to position-based
901-
* bucketSeq. The dispatch-time alignment guard fires later in
902-
* {@link #getBucketNums()} for the colocate-dispatch path.
903-
*/
904-
private void fillTabletId2BucketSeq(@Nullable RangeColocateScanDispatch dispatch,
905-
MaterializedIndex selectedIndex,
906-
List<Long> allTabletIds) {
907-
if (dispatch != null) {
908-
Map<Long, Integer> rangeColocateMap = dispatch.computeBucketSeq(selectedIndex);
909-
if (rangeColocateMap != null) {
910-
tabletId2BucketSeq.putAll(rangeColocateMap);
911-
return;
912-
}
913-
// Range-colocate but transiently unaligned: fall back to position-based bucketSeq so a
914-
// non-colocate scan of this table still works. getBucketNums() fails closed on the
915-
// colocate-dispatch path by validating this built assignment against the aligned mapping.
916-
}
917-
for (int i = 0; i < allTabletIds.size(); i++) {
918-
tabletId2BucketSeq.put(allTabletIds.get(i), i);
919-
}
920-
}
921-
922897
public void checkIfScanRangeNumSafe(long scanRangeSize) {
923898
long totalPartitionNum = 0;
924899
long totalTabletsNum = 0;

fe/fe-core/src/main/java/com/starrocks/planner/RangeColocateScanDispatch.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,4 +334,40 @@ public Map<Long, Integer> computeBucketSeq(MaterializedIndex selectedIndex) {
334334
}
335335
return output;
336336
}
337+
338+
/**
339+
* Fills {@code target} with the tablet-id → bucket-sequence assignment for
340+
* {@code selectedIndex}, applying the position-fallback policy shared by every
341+
* scan-time bucketSeq producer ({@link OlapScanNode} and
342+
* {@link com.starrocks.sql.plan.PlanFragmentBuilder}):
343+
*
344+
* <ul>
345+
* <li>range colocate and aligned — {@code dispatch != null} and
346+
* {@link #computeBucketSeq} returns a mapping — uses that mapping;</li>
347+
* <li>everything else (HASH, range non-colocate, or a transiently unaligned
348+
* range colocate group where {@code computeBucketSeq} returns
349+
* {@code null}) falls back to position-based bucketSeq.</li>
350+
* </ul>
351+
*
352+
* <p>Entries are added to {@code target} without clearing it, so a caller can
353+
* accumulate the whole-scan assignment across sub-partitions. The dispatch-time
354+
* alignment guard in {@link OlapScanNode#getBucketNums()} fails closed on the
355+
* colocate-dispatch path when the built assignment is a stale position fallback,
356+
* so no unaligned observation needs to be recorded here.
357+
*/
358+
public static void fillTabletId2BucketSeq(@Nullable RangeColocateScanDispatch dispatch,
359+
MaterializedIndex selectedIndex,
360+
List<Long> allTabletIds,
361+
Map<Long, Integer> target) {
362+
if (dispatch != null) {
363+
Map<Long, Integer> rangeColocateMap = dispatch.computeBucketSeq(selectedIndex);
364+
if (rangeColocateMap != null) {
365+
target.putAll(rangeColocateMap);
366+
return;
367+
}
368+
}
369+
for (int i = 0; i < allTabletIds.size(); i++) {
370+
target.put(allTabletIds.get(i), i);
371+
}
372+
}
337373
}

fe/fe-core/src/main/java/com/starrocks/sql/plan/PlanFragmentBuilder.java

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,30 +1016,8 @@ public PlanFragment visitPhysicalOlapScan(OptExpression optExpr, ExecPlan contex
10161016
final MaterializedIndex selectedIndex = physicalPartition.getLatestIndex(selectedIndexMetaId);
10171017
totalTabletsNum += selectedIndex.getTablets().size();
10181018
List<Long> allTabletIds = selectedIndex.getTabletIdsInOrder();
1019-
// Range-colocate scans use the dispatch facade when alignment
1020-
// holds; if alignment is transiently broken the same scan still
1021-
// needs a bucketSeq for non-colocate consumers, so fall back to
1022-
// position-based and let the dispatch-time alignment guard in
1023-
// OlapScanNode.getBucketNums() handle the colocate-dispatch path.
1024-
if (dispatch != null) {
1025-
Map<Long, Integer> rangeColocateMap = dispatch.computeBucketSeq(selectedIndex);
1026-
if (rangeColocateMap != null) {
1027-
tabletId2BucketSeq.putAll(rangeColocateMap);
1028-
} else {
1029-
// Range-colocate but transiently unaligned: fall back to position-based
1030-
// bucketSeq so a non-colocate scan still works. getBucketNums() fails closed
1031-
// on the colocate-dispatch path by validating this built assignment against
1032-
// the aligned mapping, so no unaligned observation needs to be recorded here.
1033-
for (int i = 0; i < allTabletIds.size(); i++) {
1034-
tabletId2BucketSeq.put(allTabletIds.get(i), i);
1035-
}
1036-
}
1037-
} else {
1038-
// HASH or range non-colocate: position-based bucketSeq.
1039-
for (int i = 0; i < allTabletIds.size(); i++) {
1040-
tabletId2BucketSeq.put(allTabletIds.get(i), i);
1041-
}
1042-
}
1019+
RangeColocateScanDispatch.fillTabletId2BucketSeq(
1020+
dispatch, selectedIndex, allTabletIds, tabletId2BucketSeq);
10431021
List<Tablet> tablets =
10441022
selectTabletIds.stream().map(selectedIndex::getTablet).collect(Collectors.toList());
10451023
scanNode.addScanRangeLocations(partition, physicalPartition, selectedIndex, tablets, partitionRange,

fe/fe-core/src/test/java/com/starrocks/planner/RangeColocateScanDispatchTest.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.junit.jupiter.api.Test;
4141

4242
import java.util.Arrays;
43+
import java.util.HashMap;
4344
import java.util.List;
4445
import java.util.Map;
4546
import java.util.Objects;
@@ -328,4 +329,78 @@ public void testRequireAlignedOnUnalignedGroupThrows() throws Exception {
328329
Assertions.assertTrue(exception.getMessage().contains("unaligned state"),
329330
"actual: " + exception.getMessage());
330331
}
332+
333+
// ---- fillTabletId2BucketSeq facade (shared by OlapScanNode + PlanFragmentBuilder) ----
334+
335+
@Test
336+
public void testFillTabletId2BucketSeqNullDispatchUsesPositionBased() {
337+
// dispatch == null (HASH or range non-colocate): position-based fill, keyed by scan order.
338+
MaterializedIndex index = new MaterializedIndex(50L);
339+
List<Long> allTabletIds = Arrays.asList(101L, 202L, 303L);
340+
Map<Long, Integer> target = new HashMap<>();
341+
342+
RangeColocateScanDispatch.fillTabletId2BucketSeq(null, index, allTabletIds, target);
343+
344+
Assertions.assertEquals(Map.of(101L, 0, 202L, 1, 303L, 2), target);
345+
}
346+
347+
@Test
348+
public void testFillTabletId2BucketSeqAlignedUsesDispatchMap() throws Exception {
349+
starRocksAssert.withTable(
350+
"create table t_facade_fill_aligned (k1 int, k2 int)\n"
351+
+ "order by(k1, k2)\n"
352+
+ "properties('replication_num' = '1', 'colocate_with' = 'rg_facade_fill_aligned:k1');");
353+
354+
OlapTable table = (OlapTable) GlobalStateMgr.getCurrentState().getLocalMetastore()
355+
.getTable(db.getFullName(), "t_facade_fill_aligned");
356+
MaterializedIndex index = baseIndexOf(table);
357+
358+
RangeColocateScanDispatch dispatch =
359+
Objects.requireNonNull(RangeColocateScanDispatch.forTable(table));
360+
Map<Long, Integer> aligned = dispatch.computeBucketSeq(index);
361+
Assertions.assertNotNull(aligned, "precondition: group must be aligned");
362+
363+
// Aligned range colocate: the facade uses the dispatch mapping verbatim.
364+
Map<Long, Integer> target = new HashMap<>();
365+
RangeColocateScanDispatch.fillTabletId2BucketSeq(
366+
dispatch, index, index.getTabletIdsInOrder(), target);
367+
368+
Assertions.assertEquals(aligned, target);
369+
}
370+
371+
@Test
372+
public void testFillTabletId2BucketSeqUnalignedFallsBackToPositionBased() throws Exception {
373+
starRocksAssert.withTable(
374+
"create table t_facade_fill_unaligned (k1 int, k2 int)\n"
375+
+ "order by(k1, k2)\n"
376+
+ "properties('replication_num' = '1', 'colocate_with' = 'rg_facade_fill_unaligned:k1');");
377+
378+
OlapTable table = (OlapTable) GlobalStateMgr.getCurrentState().getLocalMetastore()
379+
.getTable(db.getFullName(), "t_facade_fill_unaligned");
380+
ColocateTableIndex colocateTableIndex = GlobalStateMgr.getCurrentState().getColocateTableIndex();
381+
long groupId = colocateTableIndex.getGroup(table.getId()).grpId;
382+
383+
// Inject 3 ranges without re-aligning the single base tablet that still covers
384+
// Range.all() -> computeBucketSeq returns null (transiently unaligned, spanning-tablet state).
385+
colocateTableIndex.getColocateRangeMgr().setColocateRanges(groupId, Arrays.asList(
386+
new ColocateRange(Range.lt(makeTuple(100)), 9001L),
387+
new ColocateRange(Range.gelt(makeTuple(100), makeTuple(200)), 9002L),
388+
new ColocateRange(Range.ge(makeTuple(200)), 9003L)));
389+
390+
RangeColocateScanDispatch dispatch =
391+
Objects.requireNonNull(RangeColocateScanDispatch.forTable(table));
392+
MaterializedIndex index = baseIndexOf(table);
393+
Assertions.assertNull(dispatch.computeBucketSeq(index), "precondition: group must be unaligned");
394+
395+
// Unaligned range colocate: the facade falls back to position-based bucketSeq.
396+
List<Long> allTabletIds = index.getTabletIdsInOrder();
397+
Map<Long, Integer> target = new HashMap<>();
398+
RangeColocateScanDispatch.fillTabletId2BucketSeq(dispatch, index, allTabletIds, target);
399+
400+
Map<Long, Integer> expected = new HashMap<>();
401+
for (int i = 0; i < allTabletIds.size(); i++) {
402+
expected.put(allTabletIds.get(i), i);
403+
}
404+
Assertions.assertEquals(expected, target);
405+
}
331406
}

0 commit comments

Comments
 (0)