Skip to content

Commit 42aac29

Browse files
committed
[Refactor] Rename deprecated_tablet_row_nums, unify first-load stats on tablet_stats, precompute reshard signals
Address review feedback on the tablet_row_nums deprecation: - Rename the deprecated proto field tablet_row_nums -> deprecated_tablet_row_nums ([deprecated = true], json_name = "tablet_row_nums" for wire compatibility), matching the lake_types.proto deprecation convention. - Fully consolidate first-load per-tablet stats onto PartitionCommitInfo.tabletStats: the shared-nothing publish path (LeaderImpl) now builds TabletStatPB from TTabletInfo, removing tabletIdToRowCountForPartitionFirstLoad and the Map<Long,Long> tabletRowNum parameter threaded through the Utils publish overloads; StatisticsCollectionTrigger and InsertOverwriteJobRunner read tabletStats[id].numRows. tabletStats is cleared after the leader-side apply to bound FE heap; lake first-load statistics sample from LakeTablet.getFuzzyRowCount() (set during apply) and the shared-nothing path keeps its own tabletStats (OlapTableTxnLogApplier never clears it), so the clear is unconditional and batch-publish-safe (no per-version coupling). - Eliminate the separate locked full-table reshard re-walk: TabletStatMgr piggybacks the split/merge signal computation on its existing row-count scan, and the publish apply computes its partition-local signal and enqueues a candidate carrying the precomputed signal only when it crosses a threshold. TabletReshardJobMgr.computeReshardSignals is removed; the drain triggers from the carried signal. Signals are monotone, so a per-partition crossing is decision-safe; the periodic scan remains the authoritative fallback. Signed-off-by: xiangguangyxg <xiangguangyxg@gmail.com>
1 parent 1207fdc commit 42aac29

24 files changed

Lines changed: 203 additions & 214 deletions

be/test/service/lake_service_test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6058,7 +6058,7 @@ TEST_F(LakeServiceTest, test_publish_returns_tablet_stats) {
60586058
auto it = response.tablet_stats().find(_tablet_id);
60596059
ASSERT_NE(it, response.tablet_stats().end()) << "first-load tablet must report stats via tablet_stats";
60606060
EXPECT_EQ(101, it->second.num_rows()) << "first-load row count must flow through tablet_stats";
6061-
EXPECT_EQ(0, response.tablet_row_nums().count(_tablet_id))
6061+
EXPECT_EQ(0, response.deprecated_tablet_row_nums().count(_tablet_id))
60626062
<< "deprecated tablet_row_nums must no longer be populated";
60636063
}
60646064

fe/fe-core/src/main/java/com/starrocks/alter/LakeRollupJob.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ protected boolean lakePublishVersion() {
776776
if (useAggregatePublish) {
777777
List<VectorIndexBuildInfoPB> vectorIndexBuildInfos = new ArrayList<>();
778778
Utils.sendAggregatePublishVersionRequest(request, 1, computeResource, null, null,
779-
null, vectorIndexBuildInfos);
779+
vectorIndexBuildInfos);
780780
VectorIndexBuildScheduler.onPublishComplete(vectorIndexBuildInfos, /* fromCompaction= */ false);
781781
}
782782
}

fe/fe-core/src/main/java/com/starrocks/alter/LakeTableAlterMetaJobBase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ protected boolean lakePublishVersion() {
338338
if (useAggregatePublish) {
339339
List<VectorIndexBuildInfoPB> vectorIndexBuildInfos = new ArrayList<>();
340340
Utils.aggregatePublishVersion(tablets, Lists.newArrayList(txnInfo), commitVersion - 1, commitVersion,
341-
null, null, computeResource, null, null, vectorIndexBuildInfos);
341+
null, null, computeResource, null, vectorIndexBuildInfos);
342342
VectorIndexBuildScheduler.onPublishComplete(vectorIndexBuildInfos, /* fromCompaction= */ false);
343343
}
344344
}

fe/fe-core/src/main/java/com/starrocks/alter/LakeTableSchemaChangeJob.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ protected boolean lakePublishVersion() {
889889
if (isFileBundling) {
890890
List<VectorIndexBuildInfoPB> vectorIndexBuildInfos = new ArrayList<>();
891891
Utils.sendAggregatePublishVersionRequest(request, 1, computeResource, null, null,
892-
null, vectorIndexBuildInfos);
892+
vectorIndexBuildInfos);
893893
VectorIndexBuildScheduler.onPublishComplete(vectorIndexBuildInfos, /* fromCompaction= */ false);
894894
}
895895
}

fe/fe-core/src/main/java/com/starrocks/alter/reshard/MergeTabletJob.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ private Map<Long, TabletRange> publishVersion(List<Tablet> tablets, long commitV
538538
Map<Long, TabletRange> tabletRange = new HashMap<>();
539539
List<VectorIndexBuildInfoPB> vectorIndexBuildInfos = new ArrayList<>();
540540
Utils.publishVersion(tablets, txnInfo, commitVersion - 1, commitVersion, null, tabletRange,
541-
computeResource, null, null, useAggregatePublish, vectorIndexBuildInfos);
541+
computeResource, null, useAggregatePublish, vectorIndexBuildInfos);
542542
VectorIndexBuildScheduler.onPublishComplete(vectorIndexBuildInfos, /* fromCompaction= */ false);
543543

544544
return tabletRange;

fe/fe-core/src/main/java/com/starrocks/alter/reshard/SplitTabletJob.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ private Map<Long, TabletRange> publishVersion(List<Tablet> tablets, long commitV
578578
Map<Long, TabletRange> tabletRange = new HashMap<>();
579579
List<VectorIndexBuildInfoPB> vectorIndexBuildInfos = new ArrayList<>();
580580
Utils.publishVersion(tablets, txnInfo, commitVersion - 1, commitVersion, null, tabletRange,
581-
computeResource, null, null, useAggregatePublish, vectorIndexBuildInfos);
581+
computeResource, null, useAggregatePublish, vectorIndexBuildInfos);
582582
VectorIndexBuildScheduler.onPublishComplete(vectorIndexBuildInfos, /* fromCompaction= */ false);
583583

584584
return tabletRange;

fe/fe-core/src/main/java/com/starrocks/alter/reshard/TabletReshardJobMgr.java

Lines changed: 74 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,12 @@
1818
import com.google.common.collect.Maps;
1919
import com.google.gson.annotations.SerializedName;
2020
import com.starrocks.catalog.Database;
21-
import com.starrocks.catalog.MaterializedIndex;
22-
import com.starrocks.catalog.MaterializedIndex.IndexExtState;
2321
import com.starrocks.catalog.OlapTable;
24-
import com.starrocks.catalog.Partition;
25-
import com.starrocks.catalog.PhysicalPartition;
2622
import com.starrocks.catalog.Table;
2723
import com.starrocks.catalog.Tablet;
2824
import com.starrocks.common.Config;
2925
import com.starrocks.common.StarRocksException;
3026
import com.starrocks.common.util.FrontendDaemon;
31-
import com.starrocks.common.util.concurrent.lock.LockType;
32-
import com.starrocks.common.util.concurrent.lock.Locker;
3327
import com.starrocks.lake.LakeTablet;
3428
import com.starrocks.metric.MetricRepo;
3529
import com.starrocks.persist.ImageWriter;
@@ -52,7 +46,6 @@
5246
import java.util.ArrayList;
5347
import java.util.List;
5448
import java.util.Map;
55-
import java.util.Set;
5649
import java.util.concurrent.ConcurrentHashMap;
5750

5851
public class TabletReshardJobMgr extends FrontendDaemon implements GsonPostProcessable {
@@ -75,13 +68,23 @@ public class TabletReshardJobMgr extends FrontendDaemon implements GsonPostProce
7568
public record ReshardCandidateKey(long dbId, long tableId) {
7669
}
7770

78-
private final Set<ReshardCandidateKey> reshardCandidates = ConcurrentHashMap.newKeySet();
71+
private final Map<ReshardCandidateKey, ReshardSignals> reshardCandidates = new ConcurrentHashMap<>();
7972

80-
public void markReshardCandidate(long dbId, long tableId) {
73+
// Enqueue a table whose just-published partition already crossed a split/merge threshold,
74+
// carrying the precomputed signal so the drain triggers without re-walking the whole table.
75+
// Concurrent marks for the same table before the next drain coalesce by max/min.
76+
public void markReshardCandidate(long dbId, long tableId,
77+
long maxTabletSize, long minAdjacentTabletPairSize) {
8178
if (!isLeaderAdmissionOpen()) {
8279
return;
8380
}
84-
reshardCandidates.add(new ReshardCandidateKey(dbId, tableId));
81+
ReshardSignals fresh = new ReshardSignals();
82+
fresh.maxTabletSize = maxTabletSize;
83+
fresh.minAdjacentTabletPairSize = minAdjacentTabletPairSize;
84+
reshardCandidates.merge(new ReshardCandidateKey(dbId, tableId), fresh, (old, incoming) -> {
85+
old.merge(incoming);
86+
return old;
87+
});
8588
}
8689

8790
public TabletReshardJobMgr() {
@@ -125,88 +128,73 @@ public void createTabletReshardJob(Database db, OlapTable table, MergeTabletClau
125128
addTabletReshardJob(job);
126129
}
127130

128-
private static class ReshardSignals {
129-
long maxTabletSize = 0L;
130-
long minAdjacentTabletPairSize = Long.MAX_VALUE;
131+
public static class ReshardSignals {
132+
public long maxTabletSize = 0L;
133+
public long minAdjacentTabletPairSize = Long.MAX_VALUE;
134+
135+
void merge(ReshardSignals other) {
136+
maxTabletSize = Math.max(maxTabletSize, other.maxTabletSize);
137+
minAdjacentTabletPairSize = Math.min(minAdjacentTabletPairSize, other.minAdjacentTabletPairSize);
138+
}
139+
}
140+
141+
// Accumulate split/merge signals over one VISIBLE index's tablets into {@code signals}.
142+
// maxTabletSize drives split (never gated); the adjacent-pair min drives merge and only
143+
// contributes when the index is above the parallelism floor and both neighbors are fresh
144+
// (LakeTablet dataSizeUpdateTime >= visibleVersionTime), so auto-merge cannot shrink an
145+
// index below its pre-split parallelism. Pure in-memory field reads, no RPC. Called by the
146+
// periodic TabletStatMgr scan (whole table) and the publish apply (one partition), both of
147+
// which already iterate these tablets, so the signal is precomputed instead of re-walked
148+
// under a fresh lock.
149+
public static void accumulateIndexSignals(List<Tablet> tablets, int parallelismFloor,
150+
long visibleVersionTime, ReshardSignals signals) {
151+
boolean eligibleForMerge = tablets.size() > parallelismFloor;
152+
long prevFreshTabletSize = -1L;
153+
for (Tablet tablet : tablets) {
154+
long dataSize = tablet.getDataSize(true);
155+
signals.maxTabletSize = Math.max(signals.maxTabletSize, dataSize);
156+
if (!(tablet instanceof LakeTablet)
157+
|| ((LakeTablet) tablet).getDataSizeUpdateTime() < visibleVersionTime) {
158+
prevFreshTabletSize = -1L;
159+
continue;
160+
}
161+
if (prevFreshTabletSize >= 0 && eligibleForMerge) {
162+
signals.minAdjacentTabletPairSize = Math.min(signals.minAdjacentTabletPairSize,
163+
prevFreshTabletSize + dataSize);
164+
}
165+
prevFreshTabletSize = dataSize;
166+
}
167+
}
168+
169+
// Parallelism floor for merge eligibility; returns 0 (ungated) when warehouse state is unavailable.
170+
public static int safeComputeParallelismFloor(long tableId) {
171+
try {
172+
return TabletReshardUtils.computeParallelismFloor(tableId);
173+
} catch (RuntimeException e) {
174+
LOG.warn("Parallelism floor unavailable for table {}; auto-merge will not be floor-gated.", tableId, e);
175+
return 0;
176+
}
131177
}
132178

133179
/**
134180
* Shared reshard-trigger entrypoint used by both the publish-driven candidate drain and the
135-
* periodic TabletStatMgr scan. Self-gates on leader/admission, cloud-native range distribution,
136-
* and NORMAL table state; recomputes split/merge signals from in-memory LakeTablet stats (no RPC).
181+
* periodic TabletStatMgr scan, with split/merge signals precomputed by the caller (which is
182+
* already walking the relevant tablets — no second lock or full-table re-walk here). Self-gates
183+
* on leader/admission, cloud-native range distribution, and NORMAL table state; the authoritative
184+
* NORMAL re-check happens in the job factory under its own lock.
137185
*/
138-
public void triggerTabletReshardForTable(Database db, OlapTable table) {
186+
public void triggerTabletReshardForTable(Database db, OlapTable table,
187+
long maxTabletSize, long minAdjacentTabletPairSize) {
139188
if (!isLeaderAdmissionOpen()) {
140189
return;
141190
}
142191
if (!table.isCloudNativeTableOrMaterializedView() || !table.isRangeDistribution()) {
143192
return;
144193
}
145-
// Cheap pre-check; the authoritative NORMAL check happens under the read lock in computeReshardSignals.
146194
if (table.getState() != OlapTable.OlapTableState.NORMAL) {
147195
return;
148196
}
149-
ReshardSignals signals = computeReshardSignals(db, table);
150-
if (signals == null) {
151-
return;
152-
}
153-
triggerTabletReshard(db, table, signals.maxTabletSize, signals.minAdjacentTabletPairSize);
154-
}
155-
156-
// Returns null if the table is no longer reshard-eligible (state changed under the lock).
157-
private ReshardSignals computeReshardSignals(Database db, OlapTable table) {
158-
int parallelismFloor = resolveMergeParallelismFloor(db, table); // resolves warehouse state before the lock
159-
ReshardSignals signals = new ReshardSignals();
160-
Locker locker = new Locker();
161-
locker.lockTableWithIntensiveDbLock(db.getId(), table.getId(), LockType.READ);
162-
try {
163-
if (table.getState() != OlapTable.OlapTableState.NORMAL) {
164-
return null;
165-
}
166-
for (Partition partition : table.getAllPartitions()) {
167-
for (PhysicalPartition physicalPartition : partition.getSubPartitions()) {
168-
long visibleVersionTime = physicalPartition.getVisibleVersionTime();
169-
for (MaterializedIndex index :
170-
physicalPartition.getLatestMaterializedIndices(IndexExtState.VISIBLE)) {
171-
List<Tablet> tablets = index.getTablets();
172-
// Only an index above the parallelism floor contributes the merge signal;
173-
// otherwise auto-merge could shrink it below the pre-split parallelism count.
174-
// Split detection (maxTabletSize) is never gated.
175-
boolean eligibleForMerge = tablets.size() > parallelismFloor;
176-
long prevFreshTabletSize = -1L;
177-
for (Tablet tablet : tablets) {
178-
long dataSize = tablet.getDataSize(true);
179-
signals.maxTabletSize = Math.max(signals.maxTabletSize, dataSize);
180-
if (!(tablet instanceof LakeTablet)
181-
|| ((LakeTablet) tablet).getDataSizeUpdateTime() < visibleVersionTime) {
182-
prevFreshTabletSize = -1L;
183-
continue;
184-
}
185-
if (prevFreshTabletSize >= 0 && eligibleForMerge) {
186-
signals.minAdjacentTabletPairSize = Math.min(signals.minAdjacentTabletPairSize,
187-
prevFreshTabletSize + dataSize);
188-
}
189-
prevFreshTabletSize = dataSize;
190-
}
191-
}
192-
}
193-
}
194-
} finally {
195-
locker.unLockTableWithIntensiveDbLock(db.getId(), table.getId(), LockType.READ);
196-
}
197-
return signals;
198-
}
199-
200-
// Precondition: caller has verified the table is a cloud-native range-distribution table.
201-
private int resolveMergeParallelismFloor(Database db, OlapTable table) {
202-
try {
203-
return TabletReshardUtils.computeParallelismFloor(table.getId());
204-
} catch (RuntimeException e) {
205-
LOG.warn("Parallelism floor unavailable for table {}.{}; "
206-
+ "auto-merge will not be floor-gated for this table.",
207-
db.getFullName(), table.getName(), e);
208-
return 0;
209-
}
197+
triggerTabletReshard(db, table, maxTabletSize, minAdjacentTabletPairSize);
210198
}
211199

212200
// Precondition: caller has verified the table is a cloud-native range-distribution table.
@@ -340,9 +328,12 @@ private void drainReshardCandidates() {
340328
if (reshardCandidates.isEmpty()) {
341329
return;
342330
}
343-
List<ReshardCandidateKey> batch = new ArrayList<>(reshardCandidates);
344-
reshardCandidates.removeAll(batch);
345-
for (ReshardCandidateKey key : batch) {
331+
// Snapshot the keys; remove each atomically so a concurrent re-mark is re-evaluated next tick.
332+
for (ReshardCandidateKey key : new ArrayList<>(reshardCandidates.keySet())) {
333+
ReshardSignals signals = reshardCandidates.remove(key);
334+
if (signals == null) {
335+
continue;
336+
}
346337
// db and table lookups are not atomic; the null guards are conservative — a dropped db/table
347338
// is simply skipped this cycle.
348339
Database db = GlobalStateMgr.getCurrentState().getLocalMetastore().getDb(key.dbId());
@@ -354,7 +345,8 @@ private void drainReshardCandidates() {
354345
if (!(table instanceof OlapTable)) {
355346
continue;
356347
}
357-
triggerTabletReshardForTable(db, (OlapTable) table);
348+
triggerTabletReshardForTable(db, (OlapTable) table,
349+
signals.maxTabletSize, signals.minAdjacentTabletPairSize);
358350
}
359351
}
360352

fe/fe-core/src/main/java/com/starrocks/catalog/TabletStatMgr.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import com.google.common.collect.ImmutableMap;
3838
import com.google.common.collect.Lists;
3939
import com.google.common.collect.Maps;
40+
import com.starrocks.alter.reshard.TabletReshardJobMgr;
4041
import com.starrocks.catalog.MaterializedIndex.IndexExtState;
4142
import com.starrocks.common.Config;
4243
import com.starrocks.common.ErrorReportException;
@@ -136,6 +137,15 @@ protected void runAfterCatalogReady() {
136137
Map<Pair<Long, Long>, Long> indexRowCountMap = Maps.newHashMap();
137138
// NOTE: calculate the row first with read lock, then update the stats with write lock
138139
OlapTable olapTable = (OlapTable) table;
140+
// Periodic reshard fallback: piggyback the split/merge signal computation on this
141+
// existing all-tablet read-locked scan instead of re-locking and re-walking the table
142+
// in TabletReshardJobMgr. Only cloud-native range-distribution tables can reshard.
143+
boolean reshardEligible = olapTable.isCloudNativeTableOrMaterializedView()
144+
&& olapTable.isRangeDistribution();
145+
TabletReshardJobMgr.ReshardSignals reshardSignals =
146+
reshardEligible ? new TabletReshardJobMgr.ReshardSignals() : null;
147+
int reshardParallelismFloor =
148+
reshardEligible ? TabletReshardJobMgr.safeComputeParallelismFloor(table.getId()) : 0;
139149
locker.lockTableWithIntensiveDbLock(db.getId(), table.getId(), LockType.READ);
140150
try {
141151
for (Partition partition : olapTable.getAllPartitions()) {
@@ -149,6 +159,10 @@ protected void runAfterCatalogReady() {
149159
for (Tablet tablet : tablets) {
150160
indexRowCount += tablet.getRowCount(version);
151161
} // end for tablets
162+
if (reshardEligible) {
163+
TabletReshardJobMgr.accumulateIndexSignals(tablets, reshardParallelismFloor,
164+
physicalPartition.getVisibleVersionTime(), reshardSignals);
165+
}
152166
indexRowCountMap.put(Pair.create(physicalPartition.getId(), index.getId()),
153167
indexRowCount);
154168
if (!olapTable.isTempPartition(partition.getId())) {
@@ -183,9 +197,12 @@ protected void runAfterCatalogReady() {
183197
locker.unLockTableWithIntensiveDbLock(db.getId(), table.getId(), LockType.WRITE);
184198
}
185199

186-
// Trigger tablet reshard (publish-driven path is primary; this is the periodic fallback)
187-
GlobalStateMgr.getCurrentState().getTabletReshardJobMgr()
188-
.triggerTabletReshardForTable(db, olapTable);
200+
// Trigger tablet reshard with the signals computed above (publish-driven path is
201+
// primary; this periodic scan is the authoritative fallback).
202+
if (reshardEligible) {
203+
GlobalStateMgr.getCurrentState().getTabletReshardJobMgr().triggerTabletReshardForTable(
204+
db, olapTable, reshardSignals.maxTabletSize, reshardSignals.minAdjacentTabletPairSize);
205+
}
189206
}
190207
}
191208
LOG.info("finished to update index row num of all databases. cost: {} ms",

0 commit comments

Comments
 (0)