Skip to content

Commit 7689979

Browse files
[BugFix] Fix mv rewrite prepare lock table too many time (backport #65803) (backport #65818) (#65820)
Signed-off-by: shuming.li <ming.moriarty@gmail.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: shuming.li <ming.moriarty@gmail.com>
1 parent c51ca6d commit 7689979

1 file changed

Lines changed: 49 additions & 8 deletions

File tree

fe/fe-core/src/main/java/com/starrocks/sql/optimizer/MvRewritePreprocessor.java

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
import java.util.concurrent.ExecutionException;
9494
import java.util.concurrent.Executor;
9595
import java.util.concurrent.Executors;
96+
import java.util.concurrent.TimeUnit;
9697
import java.util.stream.Collectors;
9798

9899
import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService;
@@ -330,12 +331,18 @@ private OptExpression extractLogicalPlanWithView(OptExpression logicalTree,
330331
}
331332
}
332333

333-
private static MaterializedView copyOnlyMaterializedView(MaterializedView mv) {
334+
private static MaterializedView copyOnlyMaterializedView(MaterializedView mv, long timeoutMs) {
334335
// Query will not lock dbs in the optimizer stage, so use a shallow copy of mv to avoid
335336
// metadata race for different operations.
336337
// Ensure to re-optimize if the mv's version has changed after the optimization.
338+
long timeoutMsPerTry = timeoutMs <= 0 ? 1000 : timeoutMs;
337339
Locker locker = new Locker();
338-
locker.lockTableWithIntensiveDbLock(mv.getDbId(), mv.getId(), LockType.READ);
340+
// Add a timeout to avoid waiting too long for the lock in some cases to avoid affecting query latency.
341+
if (!locker.tryLockTableWithIntensiveDbLock(mv.getDbId(), mv.getId(), LockType.READ,
342+
timeoutMsPerTry, TimeUnit.MILLISECONDS)) {
343+
logMVPrepare("Failed to lock mv {} for copying, use mv directly", mv.getName());
344+
return mv;
345+
}
339346
try {
340347
MaterializedView copiedMV = new MaterializedView();
341348
mv.copyOnlyForQuery(copiedMV);
@@ -513,6 +520,7 @@ private List<MaterializedViewWrapper> chooseBestRelatedMVsByCorrelations(Set<Tab
513520
int queryScanOpNum = MvUtils.getOlapScanNode(queryOptExpression).size();
514521
Set<String> queryTableNames = queryTables.stream().map(t -> t.getName()).collect(Collectors.toSet());
515522
List<MVCorrelation> mvCorrelations = Lists.newArrayList();
523+
long timeoutMs = getPrepareTimeoutMsPerMV(connectContext, Math.min(validMVs.size(), maxRelatedMVsLimit));
516524
for (MaterializedViewWrapper wrapper : validMVs) {
517525
MaterializedView mv = wrapper.getMV();
518526
List<BaseTableInfo> baseTableInfos = mv.getBaseTableInfos();
@@ -560,6 +568,8 @@ public List<MaterializedViewWrapper> chooseBestRelatedMVs(Set<Table> queryTables
560568
Set<MaterializedViewWrapper> relatedMVs,
561569
OptExpression queryOptExpression) {
562570
// choose all valid mvs and filter mvs that cannot be rewritten for the query
571+
int maxRelatedMVsLimit = connectContext.getSessionVariable().getCboMaterializedViewRewriteRelatedMVsLimit();
572+
// to make unit test more stable, force build mv plan in unit test
563573
Set<MaterializedViewWrapper> validMVs = relatedMVs.stream()
564574
.filter(wrapper -> isMVValidToRewriteQuery(connectContext, wrapper.getMV(),
565575
isForceLoadMVPlan(wrapper.getMV()), queryTables, false).isValid())
@@ -568,7 +578,6 @@ public List<MaterializedViewWrapper> chooseBestRelatedMVs(Set<Table> queryTables
568578
validMVs.size(), relatedMVs.size());
569579

570580
// choose max config related mvs for mv rewrite to avoid too much optimize time
571-
int maxRelatedMVsLimit = connectContext.getSessionVariable().getCboMaterializedViewRewriteRelatedMVsLimit();
572581
return chooseBestRelatedMVsByCorrelations(queryTables, validMVs, queryOptExpression, maxRelatedMVsLimit);
573582
}
574583

@@ -700,6 +709,7 @@ public void prepareRelatedMVs(Set<Table> queryTables,
700709

701710
List<Pair<MaterializedViewWrapper, MvUpdateInfo>> mvInfos =
702711
Lists.newArrayListWithExpectedSize(mvWithPlanContexts.size());
712+
long timeoutMs = getPrepareTimeoutMsPerMV(connectContext, mvInfos.size());
703713
for (MaterializedViewWrapper wrapper : mvWithPlanContexts) {
704714
MaterializedView mv = wrapper.getMV();
705715
try {
@@ -723,7 +733,7 @@ public void prepareRelatedMVs(Set<Table> queryTables,
723733
newDirectExecutorService();
724734
for (Pair<MaterializedViewWrapper, MvUpdateInfo> mvInfo : mvInfos) {
725735
futures.add(CompletableFuture.supplyAsync(
726-
() -> prepareMV(tracers, queryTables, mvInfo.first, mvInfo.second), exec)
736+
() -> prepareMV(tracers, queryTables, mvInfo.first, mvInfo.second, timeoutMs), exec)
727737
);
728738
}
729739
try {
@@ -745,7 +755,7 @@ public void prepareRelatedMVs(Set<Table> queryTables,
745755
}
746756

747757
private Void prepareMV(Tracers tracers, Set<Table> queryTables, MaterializedViewWrapper mvWithPlanContext,
748-
MvUpdateInfo mvUpdateInfo) {
758+
MvUpdateInfo mvUpdateInfo, long timeoutMs) {
749759
MaterializedView mv = mvWithPlanContext.getMV();
750760
MvPlanContext mvPlanContext = mvWithPlanContext.getMvPlanContext();
751761
Set<String> partitionNamesToRefresh = mvUpdateInfo.getMvToRefreshPartitionNames();
@@ -756,7 +766,7 @@ private Void prepareMV(Tracers tracers, Set<Table> queryTables, MaterializedView
756766
MvUtils.shrinkToSize(partitionNamesToRefresh, Config.max_mv_task_run_meta_message_values_length));
757767

758768
MaterializationContext materializationContext = buildMaterializationContext(context, mv, mvPlanContext,
759-
mvUpdateInfo, queryTables, mvWithPlanContext.getLevel());
769+
mvUpdateInfo, queryTables, mvWithPlanContext.getLevel(), timeoutMs);
760770
if (materializationContext == null) {
761771
return null;
762772
}
@@ -767,6 +777,23 @@ private Void prepareMV(Tracers tracers, Set<Table> queryTables, MaterializedView
767777
return null;
768778
}
769779

780+
/**
781+
* MV Preprocessing timeout is calculated based on the number of MVs to ensure mv preprocessor does not take too long.
782+
* @param mvCount: the number of MVs to process
783+
* @return: timeout in milliseconds for each MV preparation
784+
*/
785+
private static long getPrepareTimeoutMsPerMV(ConnectContext connectContext, int mvCount) {
786+
if (connectContext == null) {
787+
return 1000;
788+
}
789+
long defaultTimeout = connectContext.getSessionVariable().getOptimizerExecuteTimeout() / 2;
790+
if (mvCount == 0) {
791+
return defaultTimeout;
792+
}
793+
// Ensure at least 1 second per MV, but not more than the total timeout
794+
return Math.max(1000, defaultTimeout / mvCount);
795+
}
796+
770797
/**
771798
* Get mv's partition names to refresh for partitioned MV.
772799
* @param mv: input materialized view
@@ -833,7 +860,21 @@ public static MaterializationContext buildMaterializationContext(OptimizerContex
833860
MvUpdateInfo mvUpdateInfo,
834861
Set<Table> queryTables,
835862
int level) {
836-
Preconditions.checkState(mvPlanContext != null);
863+
long timeoutMs = getPrepareTimeoutMsPerMV(context.getConnectContext(), 1);
864+
return buildMaterializationContext(context, mv, mvPlanContext, mvUpdateInfo, queryTables, level, timeoutMs);
865+
}
866+
867+
private static MaterializationContext buildMaterializationContext(OptimizerContext context,
868+
MaterializedView mv,
869+
MvPlanContext mvPlanContext,
870+
MvUpdateInfo mvUpdateInfo,
871+
Set<Table> queryTables,
872+
int level,
873+
long timeoutMs) {
874+
if (mvPlanContext == null) {
875+
logMVPrepare(context.getConnectContext(), "MV {} plan context is null", mv.getName());
876+
return null;
877+
}
837878
OptExpression mvPlan = mvPlanContext.getLogicalPlan();
838879
Preconditions.checkState(mvPlan != null);
839880

@@ -850,7 +891,7 @@ public static MaterializationContext buildMaterializationContext(OptimizerContex
850891

851892
// If query tables are set which means use related mv for non lock optimization,
852893
// copy mv's metadata into a ready-only object.
853-
MaterializedView copiedMV = (context.getQueryTables() != null) ? copyOnlyMaterializedView(mv) : mv;
894+
MaterializedView copiedMV = (context.getQueryTables() != null) ? copyOnlyMaterializedView(mv, timeoutMs) : mv;
854895
return buildMaterializationContext(context, copiedMV, mvPlanContext, mvUpdateInfo,
855896
baseTables, intersectingTables, mvPlan, level);
856897
}

0 commit comments

Comments
 (0)