I have a large (~ 100 million rows) timeseries table t_16
in Postgres 11.5 where the primary key is a field abs_date_time
of type timestamp
.
This is a follow up of this question:
Initially I thought it was related to a CTE. But this query is slow, even without CTE.
How can I make the following query use the primary key index, to avoid a full table scan?
tsrange
This query takes ~20sec on my dev PC:
SELECT t_16_gen.*
FROM t_16_gen,
(VALUES (tsrange('["2019-11-26 12:00:00","2019-11-26 12:00:15")'))
, (tsrange('["2019-11-26 13:00:00","2019-11-26 13:00:15")'))) as ranges (time_range)
WHERE (abs_date_time >= LOWER(ranges.time_range)
AND abs_date_time < UPPER(ranges.time_range));
Explain plan:
Gather (cost=1000.00..6185287.15 rows=20571433 width=80)
Workers Planned: 2
-> Nested Loop (cost=0.00..4127143.85 rows=8571430 width=80)
Join Filter: ((t_16_gen.abs_date_time >= lower("*VALUES*".column1)) AND (t_16_gen.abs_date_time < upper("*VALUES*".column1)))
-> Parallel Seq Scan on t_16_gen (cost=0.00..1620000.38 rows=38571438 width=80)
-> Values Scan on "*VALUES*" (cost=0.00..0.03 rows=2 width=32)
In production the set of tsranges comes from a UDF - but there will always be only a few ranges (<200) and each range will have less than 1500 rows and the ranges will not overlap.
Simple timestamps instead of tsrange
Whenwe use timestamps directly (i.e. not using tsrange, LOWER()
and UPPER()
), the query is already faster. This query takes ~7sec on my dev PC:
SELECT t_16_gen.*
FROM t_16_gen,
(VALUES ('2019-11-26 12:00:00'::timestamp,'2019-11-26 12:00:15'::timestamp)
, ('2019-11-26 13:00:00','2019-11-26 13:00:15')) as ranges (start_incl, end_excl)
WHERE (abs_date_time >= ranges.start_incl
AND abs_date_time < ranges.end_excl);
Explain plan:
Nested Loop (cost=0.00..5400001.28 rows=20571433 width=80)
Join Filter: ((t_16_gen.abs_date_time >= "*VALUES*".column1) AND (t_16_gen.abs_date_time < "*VALUES*".column2))
-> Seq Scan on t_16_gen (cost=0.00..2160000.50 rows=92571450 width=80)
-> Materialize (cost=0.00..0.04 rows=2 width=16)
-> Values Scan on "*VALUES*" (cost=0.00..0.03 rows=2 width=16)
OR
conditions = FAST
When I rewrite the query to use OR conditions it is fast. This query takes ~200ms on my dev PC:
SELECT t_16_gen.*
FROM t_16_gen
WHERE (abs_date_time >= '2019-11-26 12:00:00' AND abs_date_time < '2019-11-26 12:00:15')
OR (abs_date_time >= '2019-11-26 13:00:00' AND abs_date_time < '2019-11-26 13:00:15');
Explain plan:
Gather (cost=13326.98..1533350.92 rows=923400 width=80)
Workers Planned: 2
-> Parallel Bitmap Heap Scan on t_16_gen (cost=12326.98..1440010.92 rows=384750 width=80)
Recheck Cond: (((abs_date_time >= '2019-11-26 12:00:00'::timestamp without time zone) AND (abs_date_time < '2019-11-26 12:00:15'::timestamp without time zone)) OR ((abs_date_time >= '2019-11-26 13:00:00'::timestamp without time zone) AND (abs_date_time < '2019-11-26 13:00:15'::timestamp without time zone)))
-> BitmapOr (cost=12326.98..12326.98 rows=925714 width=0)
-> Bitmap Index Scan on t_16_pkey (cost=0.00..5932.64 rows=462857 width=0)
Index Cond: ((abs_date_time >= '2019-11-26 12:00:00'::timestamp without time zone) AND (abs_date_time < '2019-11-26 12:00:15'::timestamp without time zone))
-> Bitmap Index Scan on t_16_pkey (cost=0.00..5932.64 rows=462857 width=0)
Index Cond: ((abs_date_time >= '2019-11-26 13:00:00'::timestamp without time zone) AND (abs_date_time < '2019-11-26 13:00:15'::timestamp without time zone))
UNION
= FAST
When I rewrite the query to use UNION conditions it is also fast. This query takes ~220ms on my dev PC:
SELECT t_16_gen.*
FROM t_16_gen
WHERE (abs_date_time >= '2019-11-26 12:00:00' AND abs_date_time < '2019-11-26 12:00:15')
UNION
SELECT t_16_gen.*
FROM t_16_gen
WHERE (abs_date_time >= '2019-11-26 13:00:00' AND abs_date_time < '2019-11-26 13:00:15');
Explain plan:
Unique (cost=1032439.64..1069468.20 rows=925714 width=80)
-> Sort (cost=1032439.64..1034753.93 rows=925714 width=80)
" Sort Key: t_16_gen.abs_date_time, t_16_gen.c_422, t_16_gen.c_423, t_16_gen.c_424, t_16_gen.c_425, t_16_gen.c_426, t_16_gen.c_427, t_16_gen.c_428, t_16_gen.c_429, t_16_gen.c_430, t_16_gen.c_431, t_16_gen.c_432, t_16_gen.c_433, t_16_gen.c_434, t_16_gen.c_435"
-> Append (cost=0.57..892513.13 rows=925714 width=80)
-> Index Scan using t_16_pkey on t_16_gen (cost=0.57..439313.71 rows=462857 width=80)
Index Cond: ((abs_date_time >= '2019-11-26 12:00:00'::timestamp without time zone) AND (abs_date_time < '2019-11-26 12:00:15'::timestamp without time zone))
-> Index Scan using t_16_pkey on t_16_gen t_16_gen_1 (cost=0.57..439313.71 rows=462857 width=80)
Index Cond: ((abs_date_time >= '2019-11-26 13:00:00'::timestamp without time zone) AND (abs_date_time < '2019-11-26 13:00:15'::timestamp without time zone))
Reproducing the issue
To reproduce the issue, I can create a new table and fill it with dummy data. Then restart the database before each test, so that the data is not cached.
Note: the insert query may run for several minutes!
create table if not exists t_16_gen (
abs_date_time timestamp constraint t_16_pkey primary key,
c_422 bigint,
c_423 bigint,
c_424 real,
c_425 real,
c_426 real,
c_427 real,
c_428 real,
c_429 real,
c_430 bigint,
c_431 real,
c_432 real,
c_433 real,
c_434 bigint,
c_435 real
);
INSERT INTO t_16_gen
SELECT ts, 1,2,3,4,5,6,7,8,9,10,11,12,13,14
FROM (SELECT generate_series('2019-11-26'::timestamp, '2019-11-27', '1 millisecond') as ts) as gs;
SELECT t_16.* FROM t_16 WHERE (abs_date_time >= '2019-11-26 12:00:00' AND abs_date_time < '2019-11-26 12:10:00')
?