1
1
use std:: collections:: HashMap ;
2
2
use std:: path:: Path ;
3
3
4
- use crate :: postgres_dbms:: { PostgresDBMS , POSTGRES_DBMS_NAME } ;
4
+ use crate :: postgres_dbms:: PostgresDBMS ;
5
+ use crate :: truecard:: TruecardGetter ;
5
6
use crate :: { benchmark:: Benchmark , datafusion_dbms:: DatafusionDBMS , tpch:: TpchConfig } ;
6
7
7
8
use anyhow:: { self } ;
8
9
use async_trait:: async_trait;
9
10
10
- /// This struct performs cardinality testing across one or more dbmss .
11
+ /// This struct performs cardinality testing across one or more DBMSs .
11
12
/// Another design would be for the CardtestRunnerDBMSHelper trait to expose a function
12
13
/// to evaluate the Q-error. However, I chose not to do this design for reasons
13
14
/// described in the comments of the CardtestRunnerDBMSHelper trait. This is why
14
- /// you would use CardtestRunner even for computing the Q-error of a single dbms .
15
+ /// you would use CardtestRunner even for computing the Q-error of a single DBMS .
15
16
pub struct CardtestRunner {
16
17
pub dbmss : Vec < Box < dyn CardtestRunnerDBMSHelper > > ,
18
+ truecard_getter : Box < dyn TruecardGetter > ,
19
+ }
20
+
21
+ pub struct Cardinfo {
22
+ pub qerror : f64 ,
23
+ pub estcard : usize ,
24
+ pub truecard : usize ,
17
25
}
18
26
19
27
impl CardtestRunner {
20
- pub async fn new ( dbmss : Vec < Box < dyn CardtestRunnerDBMSHelper > > ) -> anyhow:: Result < Self > {
21
- Ok ( CardtestRunner { dbmss } )
28
+ pub async fn new (
29
+ dbmss : Vec < Box < dyn CardtestRunnerDBMSHelper > > ,
30
+ truecard_getter : Box < dyn TruecardGetter > ,
31
+ ) -> anyhow:: Result < Self > {
32
+ Ok ( CardtestRunner {
33
+ dbmss,
34
+ truecard_getter,
35
+ } )
22
36
}
23
37
24
- /// Get the Q-error of a query using the cost models of all dbmss being tested
38
+ /// Get the Q-error of a query using the cost models of all DBMSs being tested
25
39
/// Q-error is defined in [Leis 2015](https://15721.courses.cs.cmu.edu/spring2024/papers/16-costmodels/p204-leis.pdf)
26
40
/// One detail not specified in the paper is that Q-error is based on the ratio of true and estimated cardinality
27
41
/// of the entire query, not of a subtree of the query. This detail is specified in Section 7.1 of
28
42
/// [Yang 2020](https://arxiv.org/pdf/2006.08109.pdf)
29
- pub async fn eval_benchmark_qerrors_alldbs (
43
+ pub async fn eval_benchmark_cardinfos_alldbs (
30
44
& mut self ,
31
45
benchmark : & Benchmark ,
32
- ) -> anyhow:: Result < HashMap < String , Vec < f64 > > > {
33
- let mut qerrors_alldbs = HashMap :: new ( ) ;
34
-
35
- // postgres runs faster and is less buggy so we use their true cardinalities
36
- // in the future, it's probably a good idea to get the truecards of datafusion to ensure that they match
37
- let pg_dbms = self
38
- . dbmss
39
- . iter_mut ( )
40
- . find ( |dbms| dbms. get_name ( ) == POSTGRES_DBMS_NAME )
41
- . unwrap ( ) ;
42
- let pg_truecards = pg_dbms. eval_benchmark_truecards ( benchmark) . await ?;
46
+ ) -> anyhow:: Result < HashMap < String , Vec < Cardinfo > > > {
47
+ let mut cardinfos_alldbs = HashMap :: new ( ) ;
48
+ let truecards = self
49
+ . truecard_getter
50
+ . get_benchmark_truecards ( benchmark)
51
+ . await ?;
43
52
44
53
for dbms in & mut self . dbmss {
45
54
let estcards = dbms. eval_benchmark_estcards ( benchmark) . await ?;
46
- let qerrors = estcards
55
+ let cardinfos = estcards
47
56
. into_iter ( )
48
- . zip ( pg_truecards. iter ( ) )
49
- . map ( |( estcard, truecard) | CardtestRunner :: calc_qerror ( estcard, * truecard) )
57
+ . zip ( truecards. iter ( ) )
58
+ . map ( |( estcard, & truecard) | Cardinfo {
59
+ qerror : CardtestRunner :: calc_qerror ( estcard, truecard) ,
60
+ estcard,
61
+ truecard,
62
+ } )
50
63
. collect ( ) ;
51
- qerrors_alldbs . insert ( String :: from ( dbms. get_name ( ) ) , qerrors ) ;
64
+ cardinfos_alldbs . insert ( String :: from ( dbms. get_name ( ) ) , cardinfos ) ;
52
65
}
53
66
54
- Ok ( qerrors_alldbs )
67
+ Ok ( cardinfos_alldbs )
55
68
}
56
69
57
70
fn calc_qerror ( estcard : usize , truecard : usize ) -> f64 {
@@ -62,54 +75,47 @@ impl CardtestRunner {
62
75
}
63
76
}
64
77
65
- /// This trait defines helper functions to enable cardinality testing on a dbms
66
- /// The reason a "get qerror" function is not exposed is to allow for greater
67
- /// flexibility. If we exposed "get qerror" for each dbms, we would need to
68
- /// get the true and estimated cardinalities for _each_ dbms. However, we
69
- /// can now choose to only get the true cardinalities of _one_ dbms to
70
- /// improve performance or even cache the true cardinalities. Additionally, if
71
- /// we do want to get the true cardinalities of all dbmss, we can compare
72
- /// them against each other to ensure they're all equal. All these options are
73
- /// possible when exposing "get true card" and "get est card" instead of a
74
- /// single "get qerror". If you want to compute the Q-error of a single
75
- /// dbms, just create a CardtestRunner with a single dbms as input.
76
- /// When exposing a "get true card" and "get est card" interface, you could
77
- /// ostensibly do it on the granularity of a single SQL string or on the
78
- /// granularity of an entire benchmark. I chose the latter for a simple reason:
79
- /// different dbmss might have different SQL strings for the same conceptual
80
- /// query (see how qgen in tpch-kit takes in dbms as an input).
78
+ /// This trait defines helper functions to enable cardinality testing on a DBMS
79
+ /// The reason "get true card" is not a function here is because we don't need to call
80
+ /// "get true card" for all DBMSs we are testing, since they'll all return the same
81
+ /// answer. We also cache true cardinalities instead of executing queries every time
82
+ /// since executing OLAP queries could take minutes to hours. Due to both of these
83
+ /// factors, we conceptually view getting the true cardinality as a completely separate
84
+ /// problem from getting the estimated cardinalities of each DBMS.
85
+ /// When exposing a "get est card" interface, you could do it on the granularity of
86
+ /// a single SQL string or on the granularity of an entire benchmark. I chose the
87
+ /// latter for a simple reason: different DBMSs might have different SQL strings
88
+ /// for the same conceptual query (see how qgen in tpch-kit takes in DBMS as an input).
81
89
/// When more performance tests are implemented, you would probably want to extract
82
90
/// get_name() into a generic "DBMS" trait.
83
91
#[ async_trait]
84
92
pub trait CardtestRunnerDBMSHelper {
85
93
// get_name() has &self so that we're able to do Box<dyn CardtestRunnerDBMSHelper>
86
94
fn get_name ( & self ) -> & str ;
87
95
88
- // The order of queries has to be the same between these two functions.
96
+ // The order of queries in the returned vector has to be the same between all databases,
97
+ // and it has to be the same as the order returned by TruecardGetter.
89
98
async fn eval_benchmark_estcards (
90
99
& mut self ,
91
100
benchmark : & Benchmark ,
92
101
) -> anyhow:: Result < Vec < usize > > ;
93
- async fn eval_benchmark_truecards (
94
- & mut self ,
95
- benchmark : & Benchmark ,
96
- ) -> anyhow:: Result < Vec < usize > > ;
97
102
}
98
103
99
- pub async fn cardtest < P : AsRef < Path > + Clone > (
104
+ pub async fn cardtest < P : AsRef < Path > > (
100
105
workspace_dpath : P ,
101
106
pguser : & str ,
102
107
pgpassword : & str ,
103
108
tpch_config : TpchConfig ,
104
- ) -> anyhow:: Result < HashMap < String , Vec < f64 > > > {
105
- let pg_dbms = PostgresDBMS :: build ( workspace_dpath. clone ( ) , pguser, pgpassword) ?;
106
- let df_dbms = DatafusionDBMS :: new ( workspace_dpath) . await ?;
107
- let dbmss: Vec < Box < dyn CardtestRunnerDBMSHelper > > = vec ! [ Box :: new( pg_dbms) , Box :: new( df_dbms) ] ;
109
+ ) -> anyhow:: Result < HashMap < String , Vec < Cardinfo > > > {
110
+ let pg_dbms = Box :: new ( PostgresDBMS :: build ( & workspace_dpath, pguser, pgpassword) ?) ;
111
+ let truecard_getter = pg_dbms. clone ( ) ;
112
+ let df_dbms = Box :: new ( DatafusionDBMS :: new ( & workspace_dpath) . await ?) ;
113
+ let dbmss: Vec < Box < dyn CardtestRunnerDBMSHelper > > = vec ! [ pg_dbms, df_dbms] ;
108
114
109
115
let tpch_benchmark = Benchmark :: Tpch ( tpch_config. clone ( ) ) ;
110
- let mut cardtest_runner = CardtestRunner :: new ( dbmss) . await ?;
111
- let qerrors_alldbs = cardtest_runner
112
- . eval_benchmark_qerrors_alldbs ( & tpch_benchmark)
116
+ let mut cardtest_runner = CardtestRunner :: new ( dbmss, truecard_getter ) . await ?;
117
+ let cardinfos_alldbs = cardtest_runner
118
+ . eval_benchmark_cardinfos_alldbs ( & tpch_benchmark)
113
119
. await ?;
114
- Ok ( qerrors_alldbs )
120
+ Ok ( cardinfos_alldbs )
115
121
}
0 commit comments