Skip to content

Commit f80695d

Browse files
committed
Rewrite aqo_execution_time in C.
Now, I guess, the extension is truly relocatable. We should check it by some tests on extension moving.
1 parent be06f13 commit f80695d

File tree

6 files changed

+158
-122
lines changed

6 files changed

+158
-122
lines changed

‎aqo--1.4--1.5.sql

+39-112
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,43 @@ CREATE VIEW aqo_queries AS SELECT * FROM aqo_queries();
7575

7676
/* UI functions */
7777

78+
79+
CREATE FUNCTION aqo_enable_query(queryid bigint)
80+
RETURNS void
81+
AS 'MODULE_PATHNAME', 'aqo_enable_query'
82+
LANGUAGE C STRICT VOLATILE;
83+
84+
CREATE FUNCTION aqo_disable_query(queryid bigint)
85+
RETURNS void
86+
AS 'MODULE_PATHNAME', 'aqo_enable_query'
87+
LANGUAGE C STRICT VOLATILE;
88+
89+
CREATE FUNCTION aqo_queries_update(
90+
queryid bigint, fs bigint, learn_aqo bool, use_aqo bool, auto_tuning bool)
91+
RETURNS bool
92+
AS 'MODULE_PATHNAME', 'aqo_queries_update'
93+
LANGUAGE C VOLATILE;
94+
95+
--
96+
-- Get cardinality error of queries the last time they were executed.
97+
-- IN:
98+
-- controlled - show queries executed under a control of AQO (true);
99+
-- executed without an AQO control, but AQO has a stat on the query (false).
100+
--
101+
-- OUT:
102+
-- num - sequental number. Smaller number corresponds to higher error.
103+
-- id - ID of a query.
104+
-- fshash - feature space. Usually equal to zero or ID.
105+
-- error - AQO error that calculated on plan nodes of the query.
106+
-- nexecs - number of executions of queries associated with this ID.
107+
--
108+
CREATE OR REPLACE FUNCTION aqo_cardinality_error(controlled boolean)
109+
RETURNS TABLE(num bigint, id bigint, fshash bigint, error float, nexecs bigint)
110+
AS 'MODULE_PATHNAME', 'aqo_cardinality_error'
111+
LANGUAGE C STRICT VOLATILE;
112+
COMMENT ON FUNCTION aqo_cardinality_error(boolean) IS
113+
'Get cardinality error of queries the last time they were executed. Order queries according to an error value.';
114+
78115
--
79116
-- Show execution time of queries, for which AQO has statistics.
80117
-- controlled - show stat on executions where AQO was used for cardinality
@@ -83,48 +120,8 @@ CREATE VIEW aqo_queries AS SELECT * FROM aqo_queries();
83120
--
84121
CREATE OR REPLACE FUNCTION aqo_execution_time(controlled boolean)
85122
RETURNS TABLE(num bigint, id bigint, fshash bigint, exec_time float, nexecs bigint)
86-
AS $$
87-
BEGIN
88-
IF (controlled) THEN
89-
-- Show a query execution time made with AQO support for the planner
90-
-- cardinality estimations. Here we return result of last execution.
91-
RETURN QUERY
92-
SELECT
93-
row_number() OVER (ORDER BY (exectime, queryid, fs_hash) DESC) AS nn,
94-
queryid, fs_hash, exectime, execs
95-
FROM (
96-
SELECT
97-
aq.queryid AS queryid,
98-
aq.fs AS fs_hash,
99-
execution_time_with_aqo[array_length(execution_time_with_aqo, 1)] AS exectime,
100-
executions_with_aqo AS execs
101-
FROM aqo_queries aq JOIN aqo_query_stat aqs
102-
ON aq.queryid = aqs.queryid
103-
WHERE TRUE = ANY (SELECT unnest(execution_time_with_aqo) IS NOT NULL)
104-
) AS q1
105-
ORDER BY nn ASC;
106-
107-
ELSE
108-
-- Show a query execution time made without any AQO advise.
109-
-- Return an average value across all executions.
110-
RETURN QUERY
111-
SELECT
112-
row_number() OVER (ORDER BY (exectime, queryid, fs_hash) DESC) AS nn,
113-
queryid, fs_hash, exectime, execs
114-
FROM (
115-
SELECT
116-
aq.queryid AS queryid,
117-
aq.fs AS fs_hash,
118-
(SELECT AVG(t) FROM unnest(execution_time_without_aqo) t) AS exectime,
119-
executions_without_aqo AS execs
120-
FROM aqo_queries aq JOIN aqo_query_stat aqs
121-
ON aq.queryid = aqs.queryid
122-
WHERE TRUE = ANY (SELECT unnest(execution_time_without_aqo) IS NOT NULL)
123-
) AS q1
124-
ORDER BY (nn) ASC;
125-
END IF;
126-
END;
127-
$$ LANGUAGE plpgsql;
123+
AS 'MODULE_PATHNAME', 'aqo_execution_time'
124+
LANGUAGE C STRICT VOLATILE;
128125
COMMENT ON FUNCTION aqo_execution_time(boolean) IS
129126
'Get execution time of queries. If controlled = true (AQO could advise cardinality estimations), show time of last execution attempt. Another case (AQO not used), return an average value of execution time across all known executions.';
130127

@@ -152,76 +149,6 @@ LANGUAGE C STRICT VOLATILE;
152149
COMMENT ON FUNCTION aqo_cleanup() IS
153150
'Remove unneeded rows from the AQO ML storage';
154151

155-
--
156-
-- Get cardinality error of queries the last time they were executed.
157-
-- IN:
158-
-- controlled - show queries executed under a control of AQO (true);
159-
-- executed without an AQO control, but AQO has a stat on the query (false).
160-
--
161-
-- OUT:
162-
-- num - sequental number. Smaller number corresponds to higher error.
163-
-- id - ID of a query.
164-
-- fshash - feature space. Usually equal to zero or ID.
165-
-- error - AQO error that calculated on plan nodes of the query.
166-
-- nexecs - number of executions of queries associated with this ID.
167-
--
168-
CREATE OR REPLACE FUNCTION aqo_cardinality_error(controlled boolean)
169-
RETURNS TABLE(num bigint, id bigint, fshash bigint, error float, nexecs bigint)
170-
AS 'MODULE_PATHNAME', 'aqo_cardinality_error'
171-
LANGUAGE C STRICT VOLATILE;
172-
COMMENT ON FUNCTION aqo_cardinality_error(boolean) IS
173-
'Get cardinality error of queries the last time they were executed. Order queries according to an error value.';
174-
175-
CREATE FUNCTION aqo_status(hash bigint)
176-
RETURNS TABLE (
177-
"learn" BOOL,
178-
"use aqo" BOOL,
179-
"auto tune" BOOL,
180-
"fspace hash" bigINT,
181-
"t_naqo" TEXT,
182-
"err_naqo" TEXT,
183-
"iters" BIGINT,
184-
"t_aqo" TEXT,
185-
"err_aqo" TEXT,
186-
"iters_aqo" BIGINT
187-
) AS $$
188-
SELECT learn_aqo,use_aqo,auto_tuning,fs,
189-
to_char(execution_time_without_aqo[n4],'9.99EEEE'),
190-
to_char(cardinality_error_without_aqo[n2],'9.99EEEE'),
191-
executions_without_aqo,
192-
to_char(execution_time_with_aqo[n3],'9.99EEEE'),
193-
to_char(cardinality_error_with_aqo[n1],'9.99EEEE'),
194-
executions_with_aqo
195-
FROM aqo_queries aq, aqo_query_stat aqs,
196-
(SELECT array_length(n1,1) AS n1, array_length(n2,1) AS n2,
197-
array_length(n3,1) AS n3, array_length(n4,1) AS n4
198-
FROM
199-
(SELECT cardinality_error_with_aqo AS n1,
200-
cardinality_error_without_aqo AS n2,
201-
execution_time_with_aqo AS n3,
202-
execution_time_without_aqo AS n4
203-
FROM aqo_query_stat aqs WHERE
204-
aqs.queryid = $1) AS al) AS q
205-
WHERE (aqs.queryid = aq.queryid) AND
206-
aqs.queryid = $1;
207-
$$ LANGUAGE SQL;
208-
209-
CREATE FUNCTION aqo_enable_query(queryid bigint)
210-
RETURNS void
211-
AS 'MODULE_PATHNAME', 'aqo_enable_query'
212-
LANGUAGE C STRICT VOLATILE;
213-
214-
CREATE FUNCTION aqo_disable_query(queryid bigint)
215-
RETURNS void
216-
AS 'MODULE_PATHNAME', 'aqo_enable_query'
217-
LANGUAGE C STRICT VOLATILE;
218-
219-
CREATE FUNCTION aqo_queries_update(queryid bigint, fs bigint, learn_aqo bool,
220-
use_aqo bool, auto_tuning bool)
221-
RETURNS bool
222-
AS 'MODULE_PATHNAME', 'aqo_queries_update'
223-
LANGUAGE C VOLATILE;
224-
225152
--
226153
-- Remove all records in the AQO storage.
227154
-- Return number of rows removed.

‎expected/schema.out

+4-2
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ SELECT * FROM test;
2525
-- Check AQO service relations state after some manipulations
2626
-- Exclude fields with hash values from the queries. Hash is depend on
2727
-- nodefuncs code which is highly PostgreSQL version specific.
28-
SELECT query_text FROM aqo_query_texts;
28+
SELECT query_text FROM aqo_query_texts
29+
ORDER BY (md5(query_text)) DESC;
2930
query_text
3031
---------------------------------------
3132
COMMON feature space (do not delete!)
3233
SELECT * FROM test;
3334
(2 rows)
3435

35-
SELECT learn_aqo, use_aqo, auto_tuning FROM aqo_queries;
36+
SELECT learn_aqo, use_aqo, auto_tuning FROM aqo_queries
37+
ORDER BY (learn_aqo, use_aqo, auto_tuning);
3638
learn_aqo | use_aqo | auto_tuning
3739
-----------+---------+-------------
3840
f | f | f

‎expected/top_queries.out

+4-4
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,17 @@ SELECT count(*) FROM (SELECT x, y FROM t2 GROUP BY GROUPING SETS ((x,y), (x), (y
6363
31
6464
(1 row)
6565

66-
SELECT num, to_char(error, '9.99EEEE') FROM aqo_cardinality_error(false) AS te
66+
SELECT to_char(error, '9.99EEEE') FROM aqo_cardinality_error(false) AS te
6767
WHERE te.fshash = (
6868
SELECT fs FROM aqo_queries
6969
WHERE aqo_queries.queryid = (
7070
SELECT aqo_query_texts.queryid FROM aqo_query_texts
7171
WHERE query_text = 'SELECT count(*) FROM (SELECT x, y FROM t2 GROUP BY GROUPING SETS ((x,y), (x), (y), ())) AS q1;'
7272
)
7373
);
74-
num | to_char
75-
-----+-----------
76-
1 | 1.94e+00
74+
to_char
75+
-----------
76+
1.94e+00
7777
(1 row)
7878

7979
-- Should return zero

‎sql/schema.sql

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ SELECT * FROM test;
2121
-- Check AQO service relations state after some manipulations
2222
-- Exclude fields with hash values from the queries. Hash is depend on
2323
-- nodefuncs code which is highly PostgreSQL version specific.
24-
SELECT query_text FROM aqo_query_texts;
25-
SELECT learn_aqo, use_aqo, auto_tuning FROM aqo_queries;
24+
SELECT query_text FROM aqo_query_texts
25+
ORDER BY (md5(query_text)) DESC;
26+
SELECT learn_aqo, use_aqo, auto_tuning FROM aqo_queries
27+
ORDER BY (learn_aqo, use_aqo, auto_tuning);
2628
DROP SCHEMA IF EXISTS test1 CASCADE;

‎sql/top_queries.sql

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ CREATE TABLE t2 AS SELECT mod(gs,10) AS x, mod(gs+1,10) AS y
3333
SELECT count(*) FROM (SELECT x, y FROM t1 GROUP BY GROUPING SETS ((x,y), (x), (y), ())) AS q1;
3434
SELECT count(*) FROM (SELECT x, y FROM t2 GROUP BY GROUPING SETS ((x,y), (x), (y), ())) AS q1;
3535

36-
SELECT num, to_char(error, '9.99EEEE') FROM aqo_cardinality_error(false) AS te
36+
SELECT to_char(error, '9.99EEEE') FROM aqo_cardinality_error(false) AS te
3737
WHERE te.fshash = (
3838
SELECT fs FROM aqo_queries
3939
WHERE aqo_queries.queryid = (

‎storage.c

+106-1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ PG_FUNCTION_INFO_V1(aqo_reset);
103103
PG_FUNCTION_INFO_V1(aqo_cleanup);
104104
PG_FUNCTION_INFO_V1(aqo_drop_class);
105105
PG_FUNCTION_INFO_V1(aqo_cardinality_error);
106+
PG_FUNCTION_INFO_V1(aqo_execution_time);
106107

107108

108109
bool
@@ -2282,7 +2283,7 @@ aqo_cardinality_error(PG_FUNCTION_ARGS)
22822283
nexecs = controlled ? sentry->execs_with_aqo : sentry->execs_without_aqo;
22832284
ce = controlled ? sentry->est_error_aqo : sentry->est_error;
22842285

2285-
values[AQE_NN] = Int32GetDatum(counter++);
2286+
values[AQE_NN] = Int32GetDatum(++counter);
22862287
values[AQE_QUERYID] = Int64GetDatum(qentry->queryid);
22872288
values[AQE_FS] = Int64GetDatum(qentry->fs);
22882289
values[AQE_NEXECS] = Int64GetDatum(nexecs);
@@ -2296,3 +2297,107 @@ aqo_cardinality_error(PG_FUNCTION_ARGS)
22962297
tuplestore_donestoring(tupstore);
22972298
return (Datum) 0;
22982299
}
2300+
2301+
typedef enum {
2302+
ET_NN = 0, ET_QUERYID, ET_FS, ET_EXECTIME, ET_NEXECS, ET_TOTAL_NCOLS
2303+
} et_output_order;
2304+
2305+
/*
2306+
* XXX: maybe to merge with aqo_cardinality_error ?
2307+
* XXX: Do we really want sequental number ?
2308+
*/
2309+
Datum
2310+
aqo_execution_time(PG_FUNCTION_ARGS)
2311+
{
2312+
bool controlled = PG_GETARG_BOOL(0);
2313+
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
2314+
TupleDesc tupDesc;
2315+
MemoryContext per_query_ctx;
2316+
MemoryContext oldcontext;
2317+
Tuplestorestate *tupstore;
2318+
Datum values[AQE_TOTAL_NCOLS];
2319+
bool nulls[AQE_TOTAL_NCOLS];
2320+
HASH_SEQ_STATUS hash_seq;
2321+
QueriesEntry *qentry;
2322+
StatEntry *sentry;
2323+
int counter = 0;
2324+
2325+
/* check to see if caller supports us returning a tuplestore */
2326+
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
2327+
ereport(ERROR,
2328+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2329+
errmsg("set-valued function called in context that cannot accept a set")));
2330+
if (!(rsinfo->allowedModes & SFRM_Materialize))
2331+
ereport(ERROR,
2332+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2333+
errmsg("materialize mode required, but it is not allowed in this context")));
2334+
2335+
/* Switch into long-lived context to construct returned data structures */
2336+
per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
2337+
oldcontext = MemoryContextSwitchTo(per_query_ctx);
2338+
2339+
/* Build a tuple descriptor for our result type */
2340+
if (get_call_result_type(fcinfo, NULL, &tupDesc) != TYPEFUNC_COMPOSITE)
2341+
elog(ERROR, "return type must be a row type");
2342+
Assert(tupDesc->natts == ET_TOTAL_NCOLS);
2343+
2344+
tupstore = tuplestore_begin_heap(true, false, work_mem);
2345+
rsinfo->returnMode = SFRM_Materialize;
2346+
rsinfo->setResult = tupstore;
2347+
rsinfo->setDesc = tupDesc;
2348+
2349+
MemoryContextSwitchTo(oldcontext);
2350+
2351+
LWLockAcquire(&aqo_state->queries_lock, LW_SHARED);
2352+
LWLockAcquire(&aqo_state->stat_lock, LW_SHARED);
2353+
2354+
hash_seq_init(&hash_seq, queries_htab);
2355+
while ((qentry = hash_seq_search(&hash_seq)) != NULL)
2356+
{
2357+
bool found;
2358+
double *et;
2359+
int64 nexecs;
2360+
int nvals;
2361+
double tm = 0;
2362+
2363+
memset(nulls, 0, ET_TOTAL_NCOLS * sizeof(nulls[0]));
2364+
2365+
sentry = (StatEntry *) hash_search(stat_htab, &qentry->queryid,
2366+
HASH_FIND, &found);
2367+
if (!found)
2368+
/* Statistics not found by some reason. Just go further */
2369+
continue;
2370+
2371+
nvals = controlled ? sentry->cur_stat_slot_aqo : sentry->cur_stat_slot;
2372+
if (nvals == 0)
2373+
/* No one stat slot filled */
2374+
continue;
2375+
2376+
nexecs = controlled ? sentry->execs_with_aqo : sentry->execs_without_aqo;
2377+
et = controlled ? sentry->exec_time_aqo : sentry->exec_time;
2378+
2379+
if (!controlled)
2380+
{
2381+
int i;
2382+
/* Calculate average execution time */
2383+
for (i = 0; i < nvals; i++)
2384+
tm += et[i];
2385+
tm /= nvals;
2386+
}
2387+
else
2388+
tm = et[nvals - 1];
2389+
2390+
values[ET_NN] = Int32GetDatum(++counter);
2391+
values[ET_QUERYID] = Int64GetDatum(qentry->queryid);
2392+
values[ET_FS] = Int64GetDatum(qentry->fs);
2393+
values[ET_NEXECS] = Int64GetDatum(nexecs);
2394+
values[ET_EXECTIME] = Float8GetDatum(tm);
2395+
tuplestore_putvalues(tupstore, tupDesc, values, nulls);
2396+
}
2397+
2398+
LWLockRelease(&aqo_state->stat_lock);
2399+
LWLockRelease(&aqo_state->queries_lock);
2400+
2401+
tuplestore_donestoring(tupstore);
2402+
return (Datum) 0;
2403+
}

0 commit comments

Comments
 (0)