timescaledb/tsl/test/sql/delete_exclusion.sql
Sven Klemm 347b45f109 Add chunk exclusion for DELETE for PG14
Currently only IMMUTABLE constraints will exclude chunks from a DELETE plan,
with this patch STABLE expressions will be used to exclude chunks as well.
This is a big performance improvement as chunks not matching partitioning
column constraints don't have to be scanned for DELETEs.
Additionally this improves usability of DELETEs on hypertables with some
chunks compressed. Previously you weren't able to do DELETE on those hypertables
which had non-IMMUTABLE constraints. Since the codepath for DELETE is
different for PG < 14 this patch only adds the optimization for PG14.

With this patch the plan for DELETE on hypertables looks like this:

 Custom Scan (HypertableModify) (actual rows=0 loops=1)
   ->  Delete on metrics (actual rows=0 loops=1)
         Delete on metrics metrics_1
         Delete on _hyper_5_8_chunk metrics
         Delete on _hyper_5_11_chunk metrics
         Delete on _hyper_5_12_chunk metrics
         Delete on _hyper_5_13_chunk metrics
         Delete on _hyper_5_14_chunk metrics_2
         ->  Custom Scan (ChunkAppend) on metrics (actual rows=1 loops=1)
               Chunks excluded during startup: 4
               ->  Seq Scan on metrics metrics_1 (actual rows=0 loops=1)
                     Filter: ("time" > (now() - '3 years'::interval))
               ->  Bitmap Heap Scan on _hyper_5_14_chunk metrics_2 (actual rows=1 loops=1)
                     Recheck Cond: ("time" > (now() - '3 years'::interval))
                     Heap Blocks: exact=1
                     ->  Bitmap Index Scan on _hyper_5_14_chunk_metrics_time_idx (actual rows=1 loops=1)
                           Index Cond: ("time" > (now() - '3 years'::interval))
2022-03-28 18:54:05 +02:00

162 lines
7.8 KiB
PL/PgSQL

-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
\set PREFIX 'EXPLAIN (ANALYZE,VERBOSE,SUMMARY OFF,TIMING OFF,COSTS OFF)'
CREATE TABLE metrics_int2(c1 int,c2 int, c3 int, c4 int, c5 int, time int2 NOT NULL);
CREATE TABLE metrics_int4(c1 int,c2 int, c3 int, c4 int, c5 int, time int4 NOT NULL);
CREATE TABLE metrics_int8(c1 int,c2 int, c3 int, c4 int, c5 int, time int8 NOT NULL);
CREATE TABLE metrics_date(c1 int,c2 int, c3 int, c4 int, c5 int, time date NOT NULL);
CREATE TABLE metrics_timestamp(c1 int,c2 int, c3 int, c4 int, c5 int, time timestamp NOT NULL);
CREATE TABLE metrics_timestamptz(c1 int,c2 int, c3 int, c4 int, c5 int, time timestamptz NOT NULL);
CREATE TABLE metrics_space(c1 int,c2 int, c3 int, c4 int, c5 int, time timestamp NOT NULL, device text);
SELECT table_name FROM create_hypertable('metrics_int2','time',chunk_time_interval:=10);
SELECT table_name FROM create_hypertable('metrics_int4','time',chunk_time_interval:=10);
SELECT table_name FROM create_hypertable('metrics_int8','time',chunk_time_interval:=10);
SELECT table_name FROM create_hypertable('metrics_date','time');
SELECT table_name FROM create_hypertable('metrics_timestamp','time');
SELECT table_name FROM create_hypertable('metrics_timestamptz','time');
SELECT table_name FROM create_hypertable('metrics_space','time','device',4);
CREATE FUNCTION drop_column(text) RETURNS VOID LANGUAGE PLPGSQL AS $$
DECLARE
tbl name;
BEGIN
FOR tbl IN SELECT 'metrics_' || unnest(ARRAY['int2','int4','int8','date','timestamp','timestamptz','space'])
LOOP
EXECUTE format('ALTER TABLE %I DROP COLUMN %I;', tbl, $1);
END LOOP;
END;
$$;
-- create 4 chunks each with different physical layout
SELECT drop_column('c1');
INSERT INTO metrics_int2(time) VALUES (0);
INSERT INTO metrics_int4(time) VALUES (0);
INSERT INTO metrics_int8(time) VALUES (0);
INSERT INTO metrics_date(time) VALUES ('2000-01-01');
INSERT INTO metrics_timestamp(time) VALUES ('2000-01-01');
INSERT INTO metrics_timestamptz(time) VALUES ('2000-01-01');
INSERT INTO metrics_space(time,device) VALUES ('2000-01-01','1'),('2000-01-01','2');
SELECT drop_column('c2');
INSERT INTO metrics_int2(time) VALUES (10);
INSERT INTO metrics_int4(time) VALUES (10);
INSERT INTO metrics_int8(time) VALUES (10);
INSERT INTO metrics_date(time) VALUES ('2001-01-01');
INSERT INTO metrics_timestamp(time) VALUES ('2001-01-01');
INSERT INTO metrics_timestamptz(time) VALUES ('2001-01-01');
INSERT INTO metrics_space(time,device) VALUES ('2001-01-01','1'),('2001-01-01','2');
SELECT drop_column('c3');
INSERT INTO metrics_int2(time) VALUES (20);
INSERT INTO metrics_int4(time) VALUES (20);
INSERT INTO metrics_int8(time) VALUES (20);
INSERT INTO metrics_date(time) VALUES ('2002-01-01');
INSERT INTO metrics_timestamp(time) VALUES ('2002-01-01');
INSERT INTO metrics_timestamptz(time) VALUES ('2002-01-01');
INSERT INTO metrics_space(time,device) VALUES ('2002-01-01','1'),('2002-01-01','2');
SELECT drop_column('c4');
INSERT INTO metrics_int2(time) VALUES (30);
INSERT INTO metrics_int4(time) VALUES (30);
INSERT INTO metrics_int8(time) VALUES (30);
INSERT INTO metrics_date(time) VALUES ('2003-01-01');
INSERT INTO metrics_timestamp(time) VALUES ('2003-01-01');
INSERT INTO metrics_timestamptz(time) VALUES ('2003-01-01');
INSERT INTO metrics_space(time,device) VALUES ('2003-01-01','1'),('2003-01-01','2');
SELECT drop_column('c5');
-- immutable constraints
-- should not have ChunkAppend since constraint is immutable and postgres already does the exclusion
-- should only hit 1 chunk and base table
:PREFIX DELETE FROM metrics_int2 WHERE time = 15;
:PREFIX DELETE FROM metrics_int4 WHERE time = 15;
:PREFIX DELETE FROM metrics_int8 WHERE time = 15;
-- stable constraints
-- should have ChunkAppend since constraint is stable
-- should only hit 1 chunk and base table
:PREFIX DELETE FROM metrics_int2 WHERE time = length(substring(version(),1,23));
:PREFIX DELETE FROM metrics_int4 WHERE time = length(substring(version(),1,23));
:PREFIX DELETE FROM metrics_int8 WHERE time = length(substring(version(),1,23));
-- should have ChunkAppend since constraint is stable
-- should only hit 1 chunk and base table, toplevel rows should be 1
:PREFIX DELETE FROM metrics_int2 WHERE time = length(substring(version(),1,20)) RETURNING 'returning', time;
:PREFIX DELETE FROM metrics_int4 WHERE time = length(substring(version(),1,20)) RETURNING 'returning', time;
:PREFIX DELETE FROM metrics_int8 WHERE time = length(substring(version(),1,20)) RETURNING 'returning', time;
-- immutable constraints
-- should not have ChunkAppend since constraint is immutable and postgres already does the exclusion
-- should only hit 1 chunk and base table
BEGIN;
:PREFIX DELETE FROM metrics_date WHERE time = '2000-01-01';
:PREFIX DELETE FROM metrics_timestamp WHERE time = '2000-01-01';
:PREFIX DELETE FROM metrics_timestamptz WHERE time = '2000-01-01';
:PREFIX DELETE FROM metrics_space WHERE time = '2000-01-01' AND device = '1';
:PREFIX DELETE FROM metrics_space WHERE time = '2000-01-01';
ROLLBACK;
-- stable constraints
-- should have ChunkAppend since constraint is stable
-- should only hit 1 chunk and base table
BEGIN;
:PREFIX DELETE FROM metrics_date WHERE time = '2000-01-01'::text::date;
:PREFIX DELETE FROM metrics_timestamp WHERE time = '2000-01-01'::text::timestamp;
:PREFIX DELETE FROM metrics_timestamptz WHERE time = '2000-01-01'::text::timestamptz;
ROLLBACK;
-- space partitioning
-- should have ChunkAppend since constraint is stable
-- should only hit 1 chunk and base table
BEGIN;
:PREFIX DELETE FROM metrics_space WHERE time = '2000-01-01'::text::timestamptz AND device = format('1');
ROLLBACK;
BEGIN;
:PREFIX DELETE FROM metrics_space WHERE device = format('1');
ROLLBACK;
-- should have ChunkAppend since constraint is stable
-- should only hit 1 chunk and base table, toplevel rows should be 1
BEGIN;
:PREFIX DELETE FROM metrics_date WHERE time = '2000-01-01'::text::date RETURNING 'returning', time;
:PREFIX DELETE FROM metrics_timestamp WHERE time = '2000-01-01'::text::timestamp RETURNING 'returning', time;
:PREFIX DELETE FROM metrics_timestamptz WHERE time = '2000-01-01'::text::timestamptz RETURNING 'returning', time;
ROLLBACK;
-- subselects
-- no chunk exclusion for subqueries joins atm
:PREFIX DELETE FROM metrics_int4 WHERE time IN (SELECT time FROM metrics_int2) AND time < length(version());
:PREFIX DELETE FROM metrics_int4 WHERE time IN (SELECT time FROM metrics_int2 WHERE time < length(version()));
:PREFIX DELETE FROM metrics_int4 WHERE time IN (SELECT time FROM metrics_int2 WHERE time < length(version())) AND time < length(version());
-- join
-- no chunk exclusion for subqueries joins atm
:PREFIX DELETE FROM metrics_int4 m4 USING metrics_int2 m2;
:PREFIX DELETE FROM metrics_int4 m4 USING metrics_int2 m2 WHERE m4.time = m2.time;
:PREFIX DELETE FROM metrics_int4 m4 USING metrics_int2 m2 WHERE m4.time = m2.time AND m4.time < length(version());
-- test interaction with compression
-- with chunk exclusion for compressed chunks operations that would
-- error because they hit compressed chunks before can succeed now
-- if those chunks get excluded
CREATE TABLE metrics_compressed(time timestamptz NOT NULL, device int, value float);
SELECT table_name FROM create_hypertable('metrics_compressed','time');
ALTER TABLE metrics_compressed SET (timescaledb.compress);
-- create first chunk and compress
INSERT INTO metrics_compressed VALUES ('2000-01-01',1,0.5);
SELECT count(compress_chunk(chunk)) FROM show_chunks('metrics_compressed') chunk;
-- create more chunks
INSERT INTO metrics_compressed VALUES ('2010-01-01',1,0.5),('2011-01-01',1,0.5),('2012-01-01',1,0.5);
-- delete from uncompressed chunks with non-immutable constraints
BEGIN;
:PREFIX DELETE FROM metrics_compressed WHERE time > '2005-01-01'::text::timestamptz;
ROLLBACK;