Skip to content

Commit ed9dcd0

Browse files
Youngwbclaude
andcommitted
[Enhancement] Count per-MV refresh metrics at job granularity
Migrate the per-MV refresh metrics (mv_refresh_jobs / mv_refresh_total_success_jobs / mv_refresh_total_failed_jobs / mv_refresh_duration) from task-run granularity to job granularity so they match information_schema.materialized_view_refresh_jobs: - generateNextTaskRunIfNeeded() now returns whether it enqueued a successor batch; the refresh job is counted once, on its terminal task run (the run that enqueues no successor). MERGED runs are excluded. - A successful batch that cannot enqueue its pending next batch (e.g. task-run queue full) leaves the refresh incomplete; the run fails through the normal failure path, so it is recorded as a failed job consistently in the metric, task history and materialized_view_refresh_jobs. - mv_refresh_duration is the per-job wall-clock duration (ms), computed by reusing ShowMaterializedViewStatus.fromTaskRuns over the job's task runs (archived ones included) so it matches the system table. - Docs (en/zh/ja) and unit tests updated. mv_refresh_total_empty_jobs is left unchanged; its deprecation is deferred to a later PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Youngwb <yangwenbo_mailbox@163.com>
1 parent c7e3815 commit ed9dcd0

10 files changed

Lines changed: 346 additions & 32 deletions

File tree

docs/en/administration/management/monitoring/metrics-materialized_view.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,17 @@ scrape_configs:
3737
### mv_refresh_jobs
3838

3939
- Type: Counter
40-
- Description: Total number of refresh jobs of the materialized view.
40+
- Description: Total number of refresh jobs triggered for the materialized view. A refresh job corresponds to one user-initiated or scheduled refresh; a single job may execute multiple task runs internally. Each job is counted once when it reaches a terminal state. MERGED task runs (sub-tasks merged into a later batch) are not counted.
4141

4242
### mv_refresh_total_success_jobs
4343

4444
- Type: Counter
45-
- Description: Number of successful refresh jobs of the materialized view.
45+
- Description: Number of refresh jobs that completed successfully. Counted once per job.
4646

4747
### mv_refresh_total_failed_jobs
4848

4949
- Type: Counter
50-
- Description: Number of failed refresh jobs of the materialized view.
50+
- Description: Number of refresh jobs that failed. Counted once per job.
5151

5252
### mv_refresh_total_empty_jobs
5353

@@ -112,4 +112,4 @@ scrape_configs:
112112
### mv_refresh_duration
113113

114114
- Type: Histogram
115-
- Description: Duration of the successful materialized view refresh jobs.
115+
- Description: Wall-clock duration of refresh jobs, in milliseconds. For multi-batch jobs, measured from the first task run start to the final task run completion.

docs/ja/administration/management/monitoring/metrics-materialized_view.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,17 @@ scrape_configs:
3737
### mv_refresh_jobs
3838

3939
- Type: Counter
40-
- Description: マテリアライズドビューのリフレッシュジョブの総数
40+
- Description: マテリアライズドビューに対してトリガーされたリフレッシュジョブの総数。1つのリフレッシュジョブはユーザー起動またはスケジュールによる1回のリフレッシュに対応し、内部で複数の Task Run が実行される場合がある。各ジョブは終了状態に達した時点で1回カウントされる。MERGED 状態の Task Run(後のバッチにマージされたサブタスク)はカウントされない
4141

4242
### mv_refresh_total_success_jobs
4343

4444
- Type: Counter
45-
- Description: マテリアライズドビューの成功したリフレッシュジョブの数
45+
- Description: 正常完了したリフレッシュジョブの数。ジョブ成功時に1回カウントされる
4646

4747
### mv_refresh_total_failed_jobs
4848

4949
- Type: Counter
50-
- Description: マテリアライズドビューの失敗したリフレッシュジョブの数
50+
- Description: 失敗したリフレッシュジョブの数。ジョブ失敗時に1回カウントされる
5151

5252
### mv_refresh_total_empty_jobs
5353

@@ -112,4 +112,4 @@ scrape_configs:
112112
### mv_refresh_duration
113113

114114
- Type: Histogram
115-
- Description: 成功したマテリアライズドビューのリフレッシュジョブの期間
115+
- Description: リフレッシュジョブの実時間(ミリ秒)。マルチバッチジョブの場合、最初の Task Run 開始から最後の Task Run 完了までを計測する

docs/zh/administration/management/monitoring/metrics-materialized_view.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,17 @@ scrape_configs:
3737
### mv_refresh_jobs
3838

3939
- 类型:Counter
40-
- 描述:物化视图刷新作业的总数
40+
- 描述:物化视图触发的刷新作业总数。一个刷新作业对应一次用户发起或调度的刷新;单个作业内部可能执行多个 Task Run。每个作业在达到终止状态时计数一次。MERGED 状态的 Task Run(被合并到后续批次的子任务)不计入
4141

4242
### mv_refresh_total_success_jobs
4343

4444
- 类型:Counter
45-
- 描述:执行成功的物化视图刷新作业的数量。
45+
- 描述:执行成功的物化视图刷新作业的数量。每个作业成功时计数一次。
4646

4747
### mv_refresh_total_failed_jobs
4848

4949
- 类型:Counter
50-
- 描述:执行失败的物化视图刷新作业的数量。
50+
- 描述:执行失败的物化视图刷新作业的数量。每个作业失败时计数一次。
5151

5252
### mv_refresh_total_empty_jobs
5353

@@ -112,4 +112,4 @@ scrape_configs:
112112
### mv_refresh_duration
113113

114114
- 类型:Histogram
115-
- 描述:执行成功的物化视图刷新作业的持续时间
115+
- 描述:刷新作业的挂钟时长,单位为毫秒。对于多批次作业,从第一个 Task Run 开始到最后一个 Task Run 完成为止计算

fe/fe-core/src/main/java/com/starrocks/metric/MaterializedViewMetricsEntity.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public final class MaterializedViewMetricsEntity implements IMaterializedViewMet
4444
private final List<Metric> metrics = Lists.newArrayList();
4545

4646
// refresh
47-
// increased once the materialized view's refresh job is triggered.
47+
// increased once per refresh job, when it reaches a terminal state.
4848
public LongCounterMetric counterRefreshJobTotal;
4949
// increased only if the materialized view's refresh job is success refreshed.
5050
public LongCounterMetric counterRefreshJobSuccessTotal;
@@ -85,7 +85,7 @@ public final class MaterializedViewMetricsEntity implements IMaterializedViewMet
8585
public GaugeMetric<Integer> counterPartitionCount;
8686

8787
// histogram(ms)
88-
// record the materialized view's refresh job duration only if it's refreshed successfully.
88+
// records the refresh job's wall-clock duration, once per job on its terminal run.
8989
public Histogram histRefreshJobDuration;
9090

9191
public Optional<String> dbNameOpt = Optional.empty();
@@ -369,6 +369,7 @@ public void increaseRefreshJobStatus(Constants.TaskRunState status) {
369369
if (status == null || !status.isFinishState()) {
370370
return;
371371
}
372+
// Counted once per refresh job, on its terminal task run.
372373
this.counterRefreshJobTotal.increase(1L);
373374
switch (status) {
374375
case MERGED:

fe/fe-core/src/main/java/com/starrocks/scheduler/MVTaskRunProcessor.java

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import com.google.common.annotations.VisibleForTesting;
1818
import com.google.common.base.Preconditions;
1919
import com.google.common.base.Stopwatch;
20+
import com.google.common.base.Strings;
21+
import com.google.common.collect.Lists;
2022
import com.google.common.util.concurrent.Uninterruptibles;
2123
import com.starrocks.catalog.Database;
2224
import com.starrocks.catalog.MaterializedView;
@@ -34,6 +36,7 @@
3436
import com.starrocks.qe.ConnectContext;
3537
import com.starrocks.qe.QueryDetail;
3638
import com.starrocks.qe.QueryState;
39+
import com.starrocks.qe.ShowMaterializedViewStatus;
3740
import com.starrocks.qe.StmtExecutor;
3841
import com.starrocks.scheduler.mv.MVRefreshExecutor;
3942
import com.starrocks.scheduler.mv.MVRefreshProcessor;
@@ -55,6 +58,9 @@
5558
import org.apache.commons.lang3.StringUtils;
5659
import org.apache.logging.log4j.Logger;
5760

61+
import java.util.Collections;
62+
import java.util.Comparator;
63+
import java.util.List;
5864
import java.util.Map;
5965
import java.util.Set;
6066
import java.util.UUID;
@@ -194,22 +200,33 @@ public Constants.TaskRunState processTaskRun(TaskRunContext context) throws Exce
194200
Preconditions.checkState(mv != null);
195201
mvMetricsEntity = MaterializedViewMetricsRegistry.getInstance().getMetricsEntity(mv.getMvId());
196202
this.taskRunState = retryProcessTaskRun(context);
203+
boolean spawnedNext = false;
197204
if (this.taskRunState == Constants.TaskRunState.SUCCESS) {
198205
logger.info("Refresh materialized view {} finished successfully.", mv.getName());
199206
// advance LAST_FRESHNESS_CONFIRMED_AT even when the refresh changed no version
200207
mvRefreshProcessor.confirmFreshness();
201-
// if success, try to generate next task run
202-
mvRefreshProcessor.generateNextTaskRunIfNeeded();
208+
spawnedNext = mvRefreshProcessor.generateNextTaskRunIfNeeded();
209+
if (!spawnedNext && mvRefreshProcessor.hasNextBatchRun()) {
210+
// A pending next batch could not be enqueued (e.g. the task-run queue is full): the
211+
// refresh is incomplete, so fail it through the normal failure path below, keeping the
212+
// metric, task history and materialized_view_refresh_jobs consistent on FAILED.
213+
throw new DmlException("Materialized view %s refresh incomplete: " +
214+
"a pending batch could not be scheduled", mv.getName());
215+
}
203216
} else {
204217
logger.info("Refresh materialized view {} failed with state: {}.", mv.getName(), taskRunState);
205218
}
206-
// update metrics
207-
mvMetricsEntity.increaseRefreshJobStatus(taskRunState);
219+
// Count the job once on its terminal run; intermediate successful batches are not counted.
220+
if (!spawnedNext) {
221+
mvMetricsEntity.increaseRefreshJobStatus(this.taskRunState);
222+
recordRefreshJobDuration(mvMetricsEntity);
223+
}
208224
connectContext.getState().setOk();
209225
}
210226
} catch (Exception e) {
211227
if (mvMetricsEntity != null) {
212228
mvMetricsEntity.increaseRefreshJobStatus(Constants.TaskRunState.FAILED);
229+
recordRefreshJobDuration(mvMetricsEntity);
213230
}
214231
connectContext.getState().setError(e.getMessage());
215232
throw e;
@@ -320,16 +337,13 @@ public Constants.TaskRunState doProcessTaskRun(TaskRunContext taskRunContext,
320337
final MVRefreshProcessor.ProcessExecPlan processExecPlan = mvRefreshProcessor.getProcessExecPlan(taskRunContext);
321338
if (processExecPlan.state() == Constants.TaskRunState.SKIPPED) {
322339
logger.info("MV {} refresh task skipped, no partitions to refresh", mv.getName());
323-
long elapsed = watch.elapsed(TimeUnit.MILLISECONDS);
324-
mvMetricsEntity.updateRefreshDuration(elapsed);
325340
return Constants.TaskRunState.SKIPPED;
326341
}
327342

328343
// refresh materialized view
329344
Constants.TaskRunState result = mvRefreshProcessor.execProcessExecPlan(taskRunContext, processExecPlan, executor);
330345
long elapsed = watch.elapsed(TimeUnit.MILLISECONDS);
331346
logger.info("refresh mv success, cost time(ms): {}", DebugUtil.DECIMAL_FORMAT_SCALE_3.format(elapsed));
332-
mvMetricsEntity.updateRefreshDuration(elapsed);
333347
return result;
334348
}
335349

@@ -451,4 +465,43 @@ public String getExtraExplainInfo(StatementBase statement) {
451465
public RuntimeProfile getRuntimeProfile() {
452466
return runtimeProfile;
453467
}
468+
469+
// Records the job's wall-clock duration once, on the terminal run, reusing the exact roll-up the
470+
// materialized_view_refresh_jobs system table uses so the metric and the table never diverge.
471+
private void recordRefreshJobDuration(IMaterializedViewMetricsEntity metricsEntity) {
472+
try {
473+
if (mvTaskRunContext == null || mvTaskRunContext.getStatus() == null) {
474+
return;
475+
}
476+
String jobId = mvTaskRunContext.getStatus().getStartTaskRunId();
477+
if (Strings.isNullOrEmpty(jobId)) {
478+
return;
479+
}
480+
List<TaskRunStatus> batch = Lists.newArrayList();
481+
// Include archived runs: a long multi-batch refresh may have had early batches archived out of
482+
// in-memory history before this terminal run, and the system table's roll-up also reads archived history.
483+
String dbName = db == null ? null : db.getFullName();
484+
if (dbName != null) {
485+
String mvTaskName = TaskBuilder.getMvTaskName(mv.getId());
486+
for (TaskRunStatus s : GlobalStateMgr.getCurrentState().getTaskManager().getTaskRunHistory()
487+
.lookupLastJobOfTasks(dbName, Collections.singleton(mvTaskName))) {
488+
if (jobId.equals(s.getStartTaskRunId()) && s.getState() != Constants.TaskRunState.MERGED) {
489+
batch.add(s);
490+
}
491+
}
492+
}
493+
batch.add(mvTaskRunContext.getStatus());
494+
// fromTaskRuns picks first/last by createTime — sort the same way.
495+
batch.sort(Comparator.comparingLong(TaskRunStatus::getCreateTime));
496+
ShowMaterializedViewStatus.RefreshJobStatus rjs = ShowMaterializedViewStatus.fromTaskRuns(batch);
497+
long startBasis = rjs.getMvRefreshProcessTime() > 0
498+
? rjs.getMvRefreshProcessTime() : rjs.getMvRefreshStartTime();
499+
// The terminal run's finishTime may not be persisted yet; fall back to now().
500+
long endMs = rjs.getMvRefreshEndTime() > 0 ? rjs.getMvRefreshEndTime() : System.currentTimeMillis();
501+
long wallMs = Math.max(0L, endMs - startBasis);
502+
metricsEntity.updateRefreshDuration(wallMs);
503+
} catch (Exception e) {
504+
logger.warn("skip refresh job duration metric for mv {}", mv.getName(), e);
505+
}
506+
}
454507
}

fe/fe-core/src/main/java/com/starrocks/scheduler/mv/MVRefreshProcessor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,9 @@ public abstract BaseTableSnapshotInfo buildBaseTableSnapshotInfo(BaseTableInfo b
202202

203203
/**
204204
* Generate the next task run to be processed and set it to the nextTaskRun field.
205+
* Returns true iff this call enqueued a successor task run, false otherwise.
205206
*/
206-
public abstract void generateNextTaskRunIfNeeded();
207+
public abstract boolean generateNextTaskRunIfNeeded();
207208

208209
/**
209210
* Whether this refresh will spawn another batch task run after the current one.

fe/fe-core/src/main/java/com/starrocks/scheduler/mv/hybrid/MVHybridRefreshProcessor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ public BaseTableSnapshotInfo buildBaseTableSnapshotInfo(BaseTableInfo baseTableI
146146
}
147147

148148
@Override
149-
public void generateNextTaskRunIfNeeded() {
150-
getCurrentProcessor().generateNextTaskRunIfNeeded();
149+
public boolean generateNextTaskRunIfNeeded() {
150+
return getCurrentProcessor().generateNextTaskRunIfNeeded();
151151
}
152152

153153
@Override

fe/fe-core/src/main/java/com/starrocks/scheduler/mv/ivm/MVIVMRefreshProcessor.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import com.starrocks.scheduler.Constants;
4444
import com.starrocks.scheduler.ExecuteOption;
4545
import com.starrocks.scheduler.MvTaskRunContext;
46+
import com.starrocks.scheduler.SubmitResult;
4647
import com.starrocks.scheduler.TaskBuilder;
4748
import com.starrocks.scheduler.TaskManager;
4849
import com.starrocks.scheduler.TaskRun;
@@ -469,9 +470,9 @@ static final class AdaptiveDelta {
469470
}
470471

471472
@Override
472-
public void generateNextTaskRunIfNeeded() {
473+
public boolean generateNextTaskRunIfNeeded() {
473474
if (!hasNextTaskRun || mvContext.getTaskRun().isKilled()) {
474-
return;
475+
return false;
475476
}
476477
TaskManager taskManager = GlobalStateMgr.getCurrentState().getTaskManager();
477478
Map<String, String> properties = mvContext.getProperties();
@@ -522,9 +523,11 @@ public void generateNextTaskRunIfNeeded() {
522523
.setExecuteOption(option)
523524
.build();
524525
nextTaskRun = taskRun;
525-
} else {
526-
taskManager.executeTask(taskName, option);
526+
return true;
527527
}
528+
// Report the job as continued only if the successor run was accepted; a rejected submit (e.g. queue
529+
// full) means no successor runs, so the current run stays the job's terminal run.
530+
return taskManager.executeTask(taskName, option).getStatus() == SubmitResult.SubmitStatus.SUBMITTED;
528531
}
529532

530533
@Override

fe/fe-core/src/main/java/com/starrocks/scheduler/mv/pct/MVPCTRefreshProcessor.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import com.starrocks.scheduler.Constants;
3838
import com.starrocks.scheduler.ExecuteOption;
3939
import com.starrocks.scheduler.MvTaskRunContext;
40+
import com.starrocks.scheduler.SubmitResult;
4041
import com.starrocks.scheduler.TaskBuilder;
4142
import com.starrocks.scheduler.TaskManager;
4243
import com.starrocks.scheduler.TaskRun;
@@ -295,9 +296,9 @@ private InsertStmt prepareRefreshPlan(PCellSortedSet mvToRefreshedPartitions,
295296
}
296297

297298
@Override
298-
public void generateNextTaskRunIfNeeded() {
299+
public boolean generateNextTaskRunIfNeeded() {
299300
if (!mvContext.hasNextBatchPartition() || mvContext.getTaskRun().isKilled()) {
300-
return;
301+
return false;
301302
}
302303

303304
TaskManager taskManager = GlobalStateMgr.getCurrentState().getTaskManager();
@@ -370,9 +371,11 @@ public void generateNextTaskRunIfNeeded() {
370371
.setExecuteOption(option)
371372
.build();
372373
nextTaskRun = taskRun;
373-
} else {
374-
taskManager.executeTask(taskName, option);
374+
return true;
375375
}
376+
// Report the job as continued only if the successor run was accepted; a rejected submit (e.g. queue
377+
// full) means no successor runs, so the current run stays the job's terminal run.
378+
return taskManager.executeTask(taskName, option).getStatus() == SubmitResult.SubmitStatus.SUBMITTED;
376379
}
377380

378381
@Override

0 commit comments

Comments
 (0)