1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15+ use std:: collections:: BTreeMap ;
1516use std:: collections:: HashMap ;
1617use std:: collections:: HashSet ;
1718use std:: sync:: Arc ;
@@ -20,10 +21,13 @@ use databend_common_base::runtime::spawn;
2021use databend_common_catalog:: table_context:: TableContext ;
2122use databend_common_exception:: ErrorCode ;
2223use databend_common_exception:: Result ;
24+ use databend_common_expression:: Expr ;
2325
2426use crate :: IndexType ;
27+ use crate :: Metadata ;
2528use crate :: MetadataRef ;
2629use crate :: ScalarExpr ;
30+ use crate :: Symbol ;
2731use crate :: optimizer:: Optimizer ;
2832use crate :: optimizer:: OptimizerContext ;
2933use crate :: optimizer:: ir:: SExpr ;
@@ -42,6 +46,8 @@ use crate::plans::RelOperator;
4246
4347const EMIT_THRESHOLD : usize = 10000 ;
4448const RELATION_THRESHOLD : usize = 10 ;
49+ const CLUSTERED_PROBE_COLUMN_FACTOR : f64 = 0.85 ;
50+ const CLUSTERED_PROBE_EXPRESSION_FACTOR : f64 = 0.95 ;
4551
4652/// The join reorder algorithm follows the paper: Dynamic Programming Strikes Back
4753/// See the paper for more details.
@@ -81,6 +87,120 @@ impl DPhpyOptimizer {
8187 self . opt_ctx . get_metadata ( )
8288 }
8389
90+ fn clustered_probe_join_cost_factor (
91+ & self ,
92+ probe_node : & JoinNode ,
93+ join_conditions : & [ ( ScalarExpr , ScalarExpr ) ] ,
94+ ) -> Result < f64 > {
95+ let probe_join_keys = {
96+ let mut probe_join_keys = Vec :: with_capacity ( join_conditions. len ( ) ) ;
97+ for ( probe_key, _) in join_conditions {
98+ probe_join_keys. push ( probe_key. as_symbol_expr ( ) ?) ;
99+ }
100+ probe_join_keys
101+ } ;
102+ if probe_join_keys. is_empty ( ) {
103+ return Ok ( 1.0 ) ;
104+ }
105+
106+ let probe_expr = probe_node
107+ . s_expr
108+ . clone ( )
109+ . unwrap_or_else ( || probe_node. s_expr ( & self . join_relations ) ) ;
110+ let stat_info = probe_expr. derive_cardinality ( ) ?;
111+ Ok (
112+ Self :: best_cluster_key_candidate ( & stat_info. statistics . cluster_keys , & probe_join_keys)
113+ . unwrap_or ( 1.0 ) ,
114+ )
115+ }
116+
117+ fn best_cluster_key_candidate (
118+ cluster_keys : & BTreeMap < IndexType , Vec < Expr < Symbol > > > ,
119+ probe_join_keys : & [ Expr < Symbol > ] ,
120+ ) -> Option < f64 > {
121+ cluster_keys
122+ . iter ( )
123+ . filter_map ( |( _, cluster_key) | {
124+ let factor = Self :: cluster_key_prefix_cost_factor ( cluster_key, probe_join_keys) ;
125+ ( factor < 1.0 ) . then_some ( factor)
126+ } )
127+ . min_by ( |left, right| left. total_cmp ( right) )
128+ }
129+
130+ fn cluster_key_prefix_cost_factor (
131+ cluster_key : & [ Expr < Symbol > ] ,
132+ probe_join_keys : & [ Expr < Symbol > ] ,
133+ ) -> f64 {
134+ let mut matched_join_keys = vec ! [ false ; probe_join_keys. len( ) ] ;
135+ let mut factor = 1.0 ;
136+
137+ for cluster_key_expr in cluster_key {
138+ let mut matched = false ;
139+ for ( idx, join_key_expr) in probe_join_keys. iter ( ) . enumerate ( ) {
140+ if matched_join_keys[ idx] {
141+ continue ;
142+ }
143+ if let Some ( key_factor) =
144+ Self :: cluster_key_match_factor ( cluster_key_expr, join_key_expr)
145+ {
146+ factor *= key_factor;
147+ matched_join_keys[ idx] = true ;
148+ matched = true ;
149+ break ;
150+ }
151+ }
152+ if !matched {
153+ return factor;
154+ }
155+ }
156+
157+ factor
158+ }
159+
160+ fn cluster_key_match_factor (
161+ cluster_key_expr : & Expr < Symbol > ,
162+ join_key_expr : & Expr < Symbol > ,
163+ ) -> Option < f64 > {
164+ match cluster_key_expr {
165+ Expr :: ColumnRef ( cluster_key)
166+ if let Expr :: ColumnRef ( join_key) = join_key_expr
167+ && cluster_key. id == join_key. id =>
168+ {
169+ Some ( CLUSTERED_PROBE_COLUMN_FACTOR )
170+ }
171+ _ if cluster_key_expr == join_key_expr => Some ( CLUSTERED_PROBE_EXPRESSION_FACTOR ) ,
172+ _ => None ,
173+ }
174+ }
175+
176+ fn relation_set_label ( & self , relation_set : & [ IndexType ] ) -> String {
177+ let metadata = self . metadata ( ) ;
178+ let metadata = metadata. read ( ) ;
179+ let mut names = Vec :: new ( ) ;
180+ for relation_idx in relation_set {
181+ if let Some ( relation) = self . join_relations . get ( * relation_idx) {
182+ Self :: collect_scan_table_names ( & metadata, & relation. s_expr ( ) , & mut names) ;
183+ }
184+ }
185+ names. sort ( ) ;
186+ if names. is_empty ( ) {
187+ format ! ( "{relation_set:?}" )
188+ } else {
189+ format ! ( "[{}]" , names. join( ", " ) )
190+ }
191+ }
192+
193+ fn collect_scan_table_names ( metadata : & Metadata , expr : & SExpr , names : & mut Vec < String > ) {
194+ if let RelOperator :: Scan ( scan) = expr. plan ( ) {
195+ names. push ( metadata. table ( scan. table_index ) . name ( ) . to_string ( ) ) ;
196+ return ;
197+ }
198+
199+ for child in expr. children ( ) {
200+ Self :: collect_scan_table_names ( metadata, child, names) ;
201+ }
202+ }
203+
84204 /// Process children of a node in parallel
85205 async fn new_children ( & mut self , s_expr : & SExpr ) -> Result < SExpr > {
86206 // Parallel process children: start a new dphyp for each child.
@@ -807,7 +927,7 @@ impl DPhpyOptimizer {
807927 right_cardinality : f64 ,
808928 left_join : JoinNode ,
809929 right_join : JoinNode ,
810- ) -> Result < JoinNode > {
930+ ) -> Result < ( JoinNode , f64 ) > {
811931 let parent_set = union ( left, right) ;
812932
813933 if !join_conditions. is_empty ( ) {
@@ -826,13 +946,18 @@ impl DPhpyOptimizer {
826946 } ;
827947
828948 // Calculate cost for inner join
829- let cost = join_node. cardinality ( & self . join_relations ) . await ?
949+ let probe_factor = self . clustered_probe_join_cost_factor (
950+ & join_node. children [ 0 ] ,
951+ join_node. join_conditions . as_ref ( ) ,
952+ ) ?;
953+ let cardinality = join_node. cardinality ( & self . join_relations ) . await ?;
954+ let cost = cardinality * probe_factor
830955 + join_node. children [ 0 ] . cost
831956 + join_node. children [ 1 ] . cost ;
832957
833958 let mut result = join_node;
834959 result. set_cost ( cost) ;
835- Ok ( result)
960+ Ok ( ( result, probe_factor ) )
836961 } else {
837962 // Create cross join
838963 let join_node = JoinNode {
@@ -849,7 +974,7 @@ impl DPhpyOptimizer {
849974 s_expr : None ,
850975 } ;
851976
852- Ok ( join_node)
977+ Ok ( ( join_node, 1.0 ) )
853978 }
854979 }
855980
@@ -878,7 +1003,7 @@ impl DPhpyOptimizer {
8781003 }
8791004
8801005 // Create join node
881- let join_node = self
1006+ let ( join_node, probe_factor ) = self
8821007 . create_join_node (
8831008 left,
8841009 right,
@@ -904,7 +1029,28 @@ impl DPhpyOptimizer {
9041029 } ;
9051030
9061031 // Update `dp_table` if we found a better plan
907- if parent_node. is_none ( ) || parent_node. unwrap ( ) . cost > join_node. cost {
1032+ let selected = parent_node. is_none ( ) || parent_node. unwrap ( ) . cost > join_node. cost ;
1033+ if self . opt_ctx . get_flag ( "explain_memo" ) {
1034+ let previous_best = parent_node
1035+ . map ( |node| format ! ( "{:.3}" , node. cost) )
1036+ . unwrap_or_else ( || "-" . to_string ( ) ) ;
1037+ self . opt_ctx . add_optimizer_trace (
1038+ self . name ( ) ,
1039+ "join_order_candidate" ,
1040+ format ! (
1041+ "parent: {}, left: {}, right: {}, cost: {:.3}, previous best: {}, probe factor: {:.3}, selected: {}" ,
1042+ self . relation_set_label( & parent_set) ,
1043+ self . relation_set_label( left) ,
1044+ self . relation_set_label( right) ,
1045+ join_node. cost,
1046+ previous_best,
1047+ probe_factor,
1048+ selected,
1049+ ) ,
1050+ ) ;
1051+ }
1052+
1053+ if selected {
9081054 self . dp_table . insert ( parent_set, join_node) ;
9091055 }
9101056
0 commit comments