1919
2020import org .apache .doris .catalog .ColocateTableIndex ;
2121import org .apache .doris .catalog .ColocateTableIndex .GroupId ;
22+ import org .apache .doris .catalog .Database ;
2223import org .apache .doris .catalog .Env ;
24+ import org .apache .doris .catalog .MaterializedIndex ;
25+ import org .apache .doris .catalog .OlapTable ;
26+ import org .apache .doris .catalog .Partition ;
27+ import org .apache .doris .catalog .Replica ;
28+ import org .apache .doris .catalog .Table ;
29+ import org .apache .doris .catalog .Tablet ;
30+ import org .apache .doris .cloud .system .CloudSystemInfoService ;
2331import org .apache .doris .common .AnalysisException ;
32+ import org .apache .doris .common .Config ;
2433import org .apache .doris .resource .Tag ;
34+ import org .apache .doris .system .Backend ;
2535
36+ import com .google .common .base .Strings ;
2637import com .google .common .collect .ImmutableList ;
38+ import com .google .common .collect .Lists ;
39+ import com .google .common .collect .Maps ;
40+ import com .google .common .collect .Sets ;
2741
42+ import java .util .ArrayList ;
2843import java .util .List ;
2944import java .util .Map ;
45+ import java .util .Set ;
3046
3147/*
3248 * show proc "/colocation_group";
@@ -61,7 +77,22 @@ public ProcNodeInterface lookup(String groupIdStr) throws AnalysisException {
6177 GroupId groupId = new GroupId (dbId , grpId );
6278 ColocateTableIndex index = Env .getCurrentColocateIndex ();
6379 Map <Tag , List <List <Long >>> beSeqs = index .getBackendsPerBucketSeq (groupId );
64- return new ColocationGroupBackendSeqsProcNode (beSeqs );
80+ Map <String , List <List <Long >>> columns ;
81+ if ((beSeqs == null || beSeqs .isEmpty ()) && Config .isCloudMode ()) {
82+ // In cloud mode, legacy backend sequence metadata may be empty. Derive the
83+ // sequence from current tablets, one column per compute group. This path must
84+ // not resolve cloud backends in a way that auto-starts a compute group.
85+ columns = getCloudBackendSeqsFromTablets (groupId , index );
86+ } else {
87+ // Local mode: one column per resource tag.
88+ columns = Maps .newLinkedHashMap ();
89+ if (beSeqs != null ) {
90+ for (Map .Entry <Tag , List <List <Long >>> entry : beSeqs .entrySet ()) {
91+ columns .put (entry .getKey ().toString (), entry .getValue ());
92+ }
93+ }
94+ }
95+ return new ColocationGroupBackendSeqsProcNode (columns );
6596 }
6697
6798 @ Override
@@ -74,4 +105,129 @@ public ProcResult fetchResult() throws AnalysisException {
74105 result .setRows (infos );
75106 return result ;
76107 }
108+
109+ private Map <String , List <List <Long >>> getCloudBackendSeqsFromTablets (GroupId groupId , ColocateTableIndex index ) {
110+ Map <String , List <List <Long >>> backendsSeq = Maps .newLinkedHashMap ();
111+ List <Long > tableIds = index .getAllTableIds (groupId );
112+ for (Long tableId : tableIds ) {
113+ long dbId = groupId .dbId ;
114+ if (dbId == 0 ) {
115+ Long tableDbId = index .getDbIdByTblIdNullable (groupId , tableId );
116+ if (tableDbId == null ) {
117+ continue ;
118+ }
119+ dbId = tableDbId ;
120+ }
121+ Database db = Env .getCurrentInternalCatalog ().getDbNullable (dbId );
122+ if (db == null ) {
123+ continue ;
124+ }
125+ Table table = db .getTableNullable (tableId );
126+ if (!(table instanceof OlapTable )) {
127+ continue ;
128+ }
129+ backendsSeq = getCloudBackendSeqsFromTable ((OlapTable ) table );
130+ if (!backendsSeq .isEmpty ()) {
131+ return backendsSeq ;
132+ }
133+ }
134+ return backendsSeq ;
135+ }
136+
137+ private Map <String , List <List <Long >>> getCloudBackendSeqsFromTable (OlapTable olapTable ) {
138+ // Snapshot replicas (ordered by bucket) under the table lock only. Resolving the
139+ // per-compute-group placement of colocate cloud replicas calls into
140+ // CloudSystemInfoService / the colocate index, which must run outside the table
141+ // lock to avoid nested lock acquisition.
142+ List <List <Replica >> bucketReplicas = Lists .newArrayList ();
143+ olapTable .readLock ();
144+ try {
145+ Partition firstPartition = null ;
146+ for (Partition partition : olapTable .getAllPartitions ()) {
147+ firstPartition = partition ;
148+ break ;
149+ }
150+ if (firstPartition == null ) {
151+ return Maps .newLinkedHashMap ();
152+ }
153+ MaterializedIndex baseIndex = firstPartition .getBaseIndex ();
154+ for (Tablet tablet : baseIndex .getTablets ()) {
155+ bucketReplicas .add (new ArrayList <>(tablet .getReplicas ()));
156+ }
157+ } finally {
158+ olapTable .readUnlock ();
159+ }
160+
161+ // Resolve each replica's per-compute-group placement outside the table lock. In
162+ // cloud mode a replica is hashed to a different BE in each compute group, so build
163+ // a separate bucket sequence per compute group. Merging across groups (picking an
164+ // arbitrary first BE) would mix BEs from different compute groups into one bucket
165+ // sequence, which is meaningless. For colocate cloud tables placement is computed
166+ // on the fly; otherwise it comes from the cached clusterId -> backendId map (or an
167+ // empty scope key for local-style replicas).
168+ List <List <Map <String , Long >>> tabletReplicaBackends = Lists .newArrayListWithCapacity (bucketReplicas .size ());
169+ Set <String > scopeKeys = Sets .newLinkedHashSet ();
170+ // Shared across all replicas in this proc call so each compute group's backend
171+ // list is fetched only once (colocate placement is resolved per compute group).
172+ Map <String , List <Backend >> computeGroupBackendCache = Maps .newHashMap ();
173+ for (List <Replica > replicas : bucketReplicas ) {
174+ List <Map <String , Long >> replicaBackends = new ArrayList <>();
175+ for (Replica replica : replicas ) {
176+ Map <String , Long > clusterToBackend =
177+ replica .getClusterToBackendForProcDisplay (computeGroupBackendCache );
178+ replicaBackends .add (clusterToBackend );
179+ scopeKeys .addAll (clusterToBackend .keySet ());
180+ }
181+ tabletReplicaBackends .add (replicaBackends );
182+ }
183+
184+ Map <String , List <List <Long >>> seqByScopeKey = Maps .newLinkedHashMap ();
185+ for (String scopeKey : scopeKeys ) {
186+ List <List <Long >> bucketSeq = Lists .newArrayListWithCapacity (tabletReplicaBackends .size ());
187+ boolean hasBackend = false ;
188+ for (List <Map <String , Long >> replicaBackends : tabletReplicaBackends ) {
189+ List <Long > bucketBackends = new ArrayList <>();
190+ for (Map <String , Long > clusterToBackend : replicaBackends ) {
191+ Long backendId = clusterToBackend .get (scopeKey );
192+ if (backendId == null || backendId < 0 ) {
193+ continue ;
194+ }
195+ bucketBackends .add (backendId );
196+ hasBackend = true ;
197+ }
198+ bucketSeq .add (bucketBackends );
199+ }
200+ if (hasBackend ) {
201+ seqByScopeKey .put (scopeKey , bucketSeq );
202+ }
203+ }
204+
205+ // Resolve scope keys to display column names (also outside the table lock): name
206+ // resolution acquires CloudSystemInfoService's lock.
207+ Map <String , List <List <Long >>> backendsSeq = Maps .newLinkedHashMap ();
208+ for (Map .Entry <String , List <List <Long >>> entry : seqByScopeKey .entrySet ()) {
209+ backendsSeq .put (scopeKeyToColumnName (entry .getKey ()), entry .getValue ());
210+ }
211+ return backendsSeq ;
212+ }
213+
214+ // Map a proc-display scope key to its column name. An empty key means there is no
215+ // per-compute-group breakdown (local-style replicas), shown as a single "BackendIds"
216+ // column. Otherwise the key is a cloud compute group id, shown by its compute group
217+ // name (falling back to the raw id when the name cannot be resolved).
218+ private String scopeKeyToColumnName (String scopeKey ) {
219+ if (Strings .isNullOrEmpty (scopeKey )) {
220+ return "BackendIds" ;
221+ }
222+ try {
223+ String name = ((CloudSystemInfoService ) Env .getCurrentSystemInfo ())
224+ .getClusterNameByClusterId (scopeKey );
225+ if (!Strings .isNullOrEmpty (name )) {
226+ return name ;
227+ }
228+ } catch (Exception e ) {
229+ // Fall back to the raw compute group id if name resolution is unavailable.
230+ }
231+ return scopeKey ;
232+ }
77233}
0 commit comments