-
Notifications
You must be signed in to change notification settings - Fork 55
/
auto_tuning.c
209 lines (180 loc) · 6.1 KB
/
auto_tuning.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/*
*******************************************************************************
*
* AUTOMATIC QUERY TUNING
*
* This module automatically implements basic strategies of tuning AQO for best
* PostgreSQL performance.
*
*******************************************************************************
*
* Copyright (c) 2016-2022, Postgres Professional
*
* IDENTIFICATION
* aqo/auto_tuning.c
*
*/
#include "postgres.h"
#include "common/pg_prng.h"
#include "aqo.h"
#include "storage.h"
/*
* Auto tuning criteria criteria of an query convergence by overall cardinality
* of plan nodes.
*/
double auto_tuning_convergence_error = 0.01;
static double get_estimation(double *elems, int nelems);
static bool is_stable(double *elems, int nelems);
static bool converged_cq(double *elems, int nelems);
static bool is_in_infinite_loop_cq(double *elems, int nelems);
/*
* Returns mean value of the array of doubles.
*/
double
get_mean(double *elems, int nelems)
{
double sum = 0;
int i;
Assert(nelems > 0);
for (i = 0; i < nelems; ++i)
sum += elems[i];
return sum / nelems;
}
/*
* Having a time series it tries to predict its next value.
* Now it do simple window averaging.
*/
static double
get_estimation(double *elems, int nelems)
{
int start;
Assert(nelems > 0);
if (nelems > auto_tuning_window_size)
start = nelems - auto_tuning_window_size;
else
start = 0;
return get_mean(&elems[start], nelems - start);
}
/*
* Checks whether the series is stable with absolute or relative error.
*/
static bool
is_stable(double *elems, int nelems)
{
double est,
last;
Assert(nelems > 1);
est = get_mean(elems, nelems - 1);
last = elems[nelems - 1];
return (est * (1. + auto_tuning_convergence_error) > last || est + auto_tuning_convergence_error > last) &&
(est * (1. - auto_tuning_convergence_error) < last || est - auto_tuning_convergence_error < last);
}
/*
* Tests whether cardinality qualities series is converged, i. e. learning
* process may be considered as finished.
* Now it checks whether the cardinality quality stopped decreasing with
* absolute or relative error.
*/
static bool
converged_cq(double *elems, int nelems)
{
if (nelems < auto_tuning_window_size + 2)
return false;
return is_stable(&elems[nelems - auto_tuning_window_size - 1],
auto_tuning_window_size + 1);
}
/*
* Tests whether cardinality qualities series is converged, i. e. learning
* process may be considered as finished.
* Now it checks whether the cardinality quality stopped decreasing with
* absolute or relative error 0.1.
*/
static bool
is_in_infinite_loop_cq(double *elems, int nelems)
{
if (nelems - auto_tuning_infinite_loop < auto_tuning_window_size + 2)
return false;
return !converged_cq(elems, nelems) &&
!converged_cq(elems, nelems - auto_tuning_window_size);
}
/*
* Here we use execution statistics for the given query tuning. Note that now
* we cannot execute queries on our own wish, so the tuning now is in setting
* use_aqo and learn_aqo parameters for the query type.
*
* Now the workflow is quite simple:
*
* Firstly, we run a new query type auto_tuning_window_size times without our
* method to have an execution time statistics for such type of queries.
* Secondly, we run the query type with both AQO usage and AQO learning enabled
* until convergence.
*
* If AQO provides better execution time for the query type according to
* collected statistics, we prefer to enable it, otherwise we prefer to disable
* it.
* In the stable workload case we perform an exploration. That means that with
* some probability which depends on execution time with and without using AQO
* we run the slower method to check whether it remains slower.
* Cardinality statistics collection is enabled by default in this mode.
* If we find out that cardinality quality diverged during the exploration, we
* return to step 2 and run the query type with both AQO usage and AQO learning
* enabled until convergence.
* If after auto_tuning_max_iterations steps we see that for this query
* it is better not to use AQO, we set auto_tuning, learn_aqo and use_aqo for
* this query to false.
*/
void
automatical_query_tuning(uint64 queryid, StatEntry *stat)
{
double unstability = auto_tuning_exploration;
double t_aqo,
t_not_aqo;
double p_use = -1;
int64 num_iterations;
num_iterations = stat->execs_with_aqo + stat->execs_without_aqo;
query_context.learn_aqo = true;
if (stat->execs_without_aqo < auto_tuning_window_size + 1)
query_context.use_aqo = false;
else if (!converged_cq(stat->est_error_aqo, stat->cur_stat_slot_aqo) &&
!is_in_infinite_loop_cq(stat->est_error_aqo,
stat->cur_stat_slot_aqo))
query_context.use_aqo = true;
else
{
/*
* Query is converged by cardinality error. Now AQO checks convergence
* by execution time. It is volatile, probabilistic part of code.
* XXX: this logic of auto tuning may be reworked later.
*/
t_aqo = get_estimation(stat->exec_time_aqo, stat->cur_stat_slot_aqo) +
get_estimation(stat->plan_time_aqo, stat->cur_stat_slot_aqo);
t_not_aqo = get_estimation(stat->exec_time, stat->cur_stat_slot) +
get_estimation(stat->plan_time, stat->cur_stat_slot);
p_use = t_not_aqo / (t_not_aqo + t_aqo);
/*
* Here p_use<0.5 and p_use->0, if AQO decreases performance,
* Otherwise, p_use>0.5 and p_use->1.
*/
p_use = 1 / (1 + exp((p_use - 0.5) / unstability));
/*
* Here p_use in (0.5..max) if AQO decreases preformance.
* p_use in (0..0.5), otherwise.
*/
p_use -= 1 / (1 + exp(-0.5 / unstability));
p_use /= 1 - 2 / (1 + exp(-0.5 / unstability));
/*
* If our decision is using AQO for this query class, then learn on new
* queries of this type. Otherwise, turn off.
*/
query_context.use_aqo = pg_prng_double(&pg_global_prng_state) < p_use;
query_context.learn_aqo = query_context.use_aqo;
}
if (num_iterations <= auto_tuning_max_iterations || p_use > 0.5)
aqo_queries_store(queryid, query_context.fspace_hash,
query_context.learn_aqo, query_context.use_aqo, true,
&aqo_queries_nulls);
else
aqo_queries_store(queryid,
query_context.fspace_hash, false, false, false,
&aqo_queries_nulls);
}