mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-16 10:33:27 +08:00
This change includes a major refactoring to support PostgreSQL 12. Note that many tests aren't passing at this point. Changes include, but are not limited to: - Handle changes related to table access methods - New way to expand hypertables since expansion has changed in PostgreSQL 12 (more on this below). - Handle changes related to table expansion for UPDATE/DELETE - Fixes for various TimescaleDB optimizations that were affected by planner changes in PostgreSQL (gapfill, first/last, etc.) Before PostgreSQL 12, planning was organized something like as follows: 1. construct add `RelOptInfo` for base and appendrels 2. add restrict info, joins, etc. 3. perform the actual planning with `make_one_rel` For our optimizations we would expand hypertables in the middle of step 1; since nothing in the query planner before `make_one_rel` cared about the inheritance children, we didn’t have to be too precises about where we were doing it. However, with PG12, and the optimizations around declarative partitioning, PostgreSQL now does care about when the children are expanded, since it wants as much information as possible to perform partition-pruning. Now planning is organized like: 1. construct add RelOptInfo for base rels only 2. add restrict info, joins, etc. 3. expand appendrels, removing irrelevant declarative partitions 4. perform the actual planning with make_one_rel Step 3 always expands appendrels, so when we also expand them during step 1, the hypertable gets expanded twice, and things in the planner break. The changes to support PostgreSQL 12 attempts to solve this problem by keeping the hypertable root marked as a non-inheritance table until `make_one_rel` is called, and only then revealing to PostgreSQL that it does in fact have inheritance children. While this strategy entails the least code change on our end, the fact that the first hook we can use to re-enable inheritance is `set_rel_pathlist_hook` it does entail a number of annoyances: 1. this hook is called after the sizes of tables are calculated, so we must recalculate the sizes of all hypertables, as they will not have taken the chunk sizes into account 2. the table upon which the hook is called will have its paths planned under the assumption it has no inheritance children, so if it's a hypertable we have to replan it's paths Unfortunately, the code for doing these is static, so we need to copy them into our own codebase, instead of just using PostgreSQL's. In PostgreSQL 12, UPDATE/DELETE on inheritance relations have also changed and are now planned in two stages: - In stage 1, the statement is planned as if it was a `SELECT` and all leaf tables are discovered. - In stage 2, the original query is planned against each leaf table, discovered in stage 1, directly, not part of an Append. Unfortunately, this means we cannot look in the appendrelinfo during UPDATE/DELETE planning, in particular to determine if a table is a chunk, as the appendrelinfo is not at the point we wish to do so initialized. This has consequences for how we identify operations on chunks (sometimes for blocking and something for enabling functionality).
118 lines
4.2 KiB
MySQL
118 lines
4.2 KiB
MySQL
-- This file and its contents are licensed under the Apache License 2.0.
|
|
-- Please see the included NOTICE for copyright information and
|
|
-- LICENSE-APACHE for a copy of the license.
|
|
|
|
--parallel queries require big-ish tables so collect them all here
|
|
--so that we need to generate queries only once.
|
|
|
|
-- output with analyze is not stable because it depends on worker assignment
|
|
\set PREFIX 'EXPLAIN (costs off)'
|
|
|
|
\set CHUNK1 _timescaledb_internal._hyper_1_1_chunk
|
|
\set CHUNK2 _timescaledb_internal._hyper_1_2_chunk
|
|
|
|
CREATE TABLE test (i int, j double precision, ts timestamp);
|
|
SELECT create_hypertable('test','i',chunk_time_interval:=500000);
|
|
INSERT INTO test SELECT x, x+0.1, _timescaledb_internal.to_timestamp(x*1000) FROM generate_series(0,1000000-1,10) AS x;
|
|
|
|
ANALYZE test;
|
|
ALTER TABLE :CHUNK1 SET (parallel_workers=2);
|
|
ALTER TABLE :CHUNK2 SET (parallel_workers=2);
|
|
|
|
SET work_mem TO '50MB';
|
|
SET force_parallel_mode = 'on';
|
|
SET max_parallel_workers_per_gather = 4;
|
|
SET parallel_setup_cost TO 0;
|
|
|
|
EXPLAIN (costs off) SELECT first(i, j) FROM "test";
|
|
SELECT first(i, j) FROM "test";
|
|
|
|
EXPLAIN (costs off) SELECT last(i, j) FROM "test";
|
|
SELECT last(i, j) FROM "test";
|
|
|
|
EXPLAIN (costs off) SELECT time_bucket('1 second', ts) sec, last(i, j)
|
|
FROM "test"
|
|
GROUP BY sec
|
|
ORDER BY sec
|
|
LIMIT 5;
|
|
|
|
-- test single copy parallel plan with parallel chunk append
|
|
:PREFIX SELECT time_bucket('1 second', ts) sec, last(i, j)
|
|
FROM "test"
|
|
WHERE length(version()) > 0
|
|
GROUP BY sec
|
|
ORDER BY sec
|
|
LIMIT 5;
|
|
|
|
SELECT time_bucket('1 second', ts) sec, last(i, j)
|
|
FROM "test"
|
|
GROUP BY sec
|
|
ORDER BY sec
|
|
LIMIT 5;
|
|
|
|
--test variants of histogram
|
|
EXPLAIN (costs off) SELECT histogram(i, 1, 1000000, 2) FROM "test";
|
|
SELECT histogram(i, 1, 1000000, 2) FROM "test";
|
|
|
|
EXPLAIN (costs off) SELECT histogram(i, 1,1000001,10) FROM "test";
|
|
SELECT histogram(i, 1, 1000001, 10) FROM "test";
|
|
|
|
EXPLAIN (costs off) SELECT histogram(i, 0,100000,5) FROM "test";
|
|
SELECT histogram(i, 0, 100000, 5) FROM "test";
|
|
|
|
EXPLAIN (costs off) SELECT histogram(i, 10,100000,5) FROM "test";
|
|
SELECT histogram(i, 10, 100000, 5) FROM "test";
|
|
|
|
EXPLAIN (costs off) SELECT histogram(NULL, 10,100000,5) FROM "test" WHERE i = coalesce(-1,j);
|
|
SELECT histogram(NULL, 10,100000,5) FROM "test" WHERE i = coalesce(-1,j);
|
|
|
|
-- test parallel ChunkAppend
|
|
:PREFIX SELECT i FROM "test" WHERE length(version()) > 0;
|
|
|
|
-- test worker assignment
|
|
-- first chunk should have 1 worker and second chunk should have 2
|
|
SET max_parallel_workers_per_gather TO 2;
|
|
:PREFIX SELECT count(*) FROM "test" WHERE i >= 400000 AND length(version()) > 0;
|
|
SELECT count(*) FROM "test" WHERE i >= 400000 AND length(version()) > 0;
|
|
|
|
-- test worker assignment
|
|
-- first chunk should have 2 worker and second chunk should have 1
|
|
:PREFIX SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;
|
|
SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;
|
|
|
|
-- test ChunkAppend with # workers < # childs
|
|
SET max_parallel_workers_per_gather TO 1;
|
|
:PREFIX SELECT count(*) FROM "test" WHERE length(version()) > 0;
|
|
SELECT count(*) FROM "test" WHERE length(version()) > 0;
|
|
|
|
-- test ChunkAppend with # workers > # childs
|
|
SET max_parallel_workers_per_gather TO 2;
|
|
:PREFIX SELECT count(*) FROM "test" WHERE i >= 500000 AND length(version()) > 0;
|
|
SELECT count(*) FROM "test" WHERE i >= 500000 AND length(version()) > 0;
|
|
|
|
RESET max_parallel_workers_per_gather;
|
|
|
|
-- test partial and non-partial plans
|
|
-- these will not be parallel on PG < 11
|
|
ALTER TABLE :CHUNK1 SET (parallel_workers=0);
|
|
ALTER TABLE :CHUNK2 SET (parallel_workers=2);
|
|
:PREFIX SELECT count(*) FROM "test" WHERE i > 400000 AND length(version()) > 0;
|
|
|
|
ALTER TABLE :CHUNK1 SET (parallel_workers=2);
|
|
ALTER TABLE :CHUNK2 SET (parallel_workers=0);
|
|
:PREFIX SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;
|
|
|
|
ALTER TABLE :CHUNK1 RESET (parallel_workers);
|
|
ALTER TABLE :CHUNK2 RESET (parallel_workers);
|
|
|
|
-- now() is not marked parallel safe in PostgreSQL < 12 so using now()
|
|
-- in a query will prevent parallelism but CURRENT_TIMESTAMP and
|
|
-- transaction_timestamp() are marked parallel safe
|
|
:PREFIX SELECT i FROM "test" WHERE ts < CURRENT_TIMESTAMP;
|
|
|
|
:PREFIX SELECT i FROM "test" WHERE ts < transaction_timestamp();
|
|
|
|
-- this won't be parallel query because now() is parallel restricted in PG < 12
|
|
:PREFIX SELECT i FROM "test" WHERE ts < now();
|
|
|