9393import java .util .concurrent .ExecutionException ;
9494import java .util .concurrent .Executor ;
9595import java .util .concurrent .Executors ;
96+ import java .util .concurrent .TimeUnit ;
9697import java .util .stream .Collectors ;
9798
9899import 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