I have a dbfiddle demo with the following tables defined:
CREATE TABLE status_table (
base_name text NOT NULL
, version smallint NOT NULL
, ref_time int NOT NULL
, processed bool NOT NULL
, processing bool NOT NULL
, updated int NOT NULL
, PRIMARY KEY (base_name, version)
);
CREATE TABLE data_table (
location text NOT NULL
, param_id text NOT NULL
, ref_time int NOT NULL
, fcst_time smallint NOT NULL
, timestamp int NOT NULL
, value text NOT NULL
, PRIMARY KEY (location, param_id, ref_time, fcst_time)
);
There are no other indexes defined.
Note that for each row in data_table
, it is the case that ref_time + fcst_time = timestamp
which I know isn't ideal but it's the way it's evolved. So ref_time
is like a base time (for a batch of data) and fcst_time
is an offset time, giving the actual timestamp
for a data record (there is a timeseries of data records for each batch starting at ref_time
and having a progressively increasing timestamp
or fcst_time
and a single data value
).
I then have the following complicated query for deleting selected rows from data_table
. It is pulling some info as stats
from status_table
and joining that onto data_table
, then selecting rows that should not be deleted (sel1
and sel2
), and then deleting all rows of data_table
that are not in sel1
and also not in sel2
.
As an aside, sel1
basically corresponds to my query for reading data from data_table
(though I limit to a particular location
when doing so, and therefore it's quite quick)... therefore sel1
is just the set of rows that might be selected in a query... I want to keep those and not delete them.
Then sel2
are those rows that relate to data that is still being processed, so I need to keep those too.
So with that in mind, here is the query:
WITH
stats AS (
SELECT ref_time
, max(updated) < (round(extract(epoch from now()) / 60) - 200) AS settled
, (count(*) FILTER (WHERE processed) = count(*)) AND (max(updated) < (round(extract(epoch from now()) / 60) - 200)) AS ready
FROM status_table
GROUP BY ref_time
),
min_ts AS (
SELECT ref_time FROM stats WHERE ready ORDER BY ref_time DESC LIMIT 1
),
sel1 AS (
-- we need to keep all of these rows (don't delete)
SELECT DISTINCT ON (d.location, d.timestamp, d.param_id)
d.location, d.param_id, d.ref_time, d.fcst_time
FROM data_table AS d
INNER JOIN stats s USING (ref_time)
WHERE s.ready AND d.timestamp >= (SELECT ref_time FROM min_ts)
ORDER BY d.location, d.timestamp, d.param_id, d.ref_time DESC
),
sel2 AS (
-- we also need to keep all of these rows (don't delete)
SELECT
d.location, d.param_id, d.ref_time, d.fcst_time
FROM data_table AS d
INNER JOIN stats AS s USING (ref_time)
WHERE NOT s.settled
)
DELETE FROM data_table
WHERE
(location, param_id, ref_time, fcst_time) NOT IN (SELECT location, param_id, ref_time, fcst_time FROM sel1)
AND
(location, param_id, ref_time, fcst_time) NOT IN (SELECT location, param_id, ref_time, fcst_time FROM sel2);
But I'm finding that this is horribly slow in my actual database. I know that I need to optimise my indexes and possibly primary keys, and have tried various things without any real success, so I'm a bit lost.
Here is the output of an EXPLAIN
for the above query on my actual database:
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
Delete on data_table (cost=4002975.62..118180240066541.86 rows=0 width=0)
CTE stats
-> HashAggregate (cost=234.02..234.21 rows=4 width=6)
Group Key: status_table.ref_time
-> Seq Scan on status_table (cost=0.00..164.01 rows=7001 width=9)
-> Seq Scan on data_table (cost=4002741.41..118180240066307.66 rows=19567628 width=6)
Filter: ((NOT (SubPlan 3)) AND (NOT (SubPlan 4)))
SubPlan 3
-> Materialize (cost=4002741.30..4293628.93 rows=7691318 width=18)
-> Subquery Scan on sel1 (cost=4002741.30..4210105.34 rows=7691318 width=18)
-> Unique (cost=4002741.30..4133192.16 rows=7691318 width=22)
InitPlan 2 (returns $1)
-> Limit (cost=0.09..0.09 rows=1 width=4)
-> Sort (cost=0.09..0.10 rows=2 width=4)
Sort Key: stats.ref_time DESC
-> CTE Scan on stats (cost=0.00..0.08 rows=2 width=4)
Filter: ready
-> Sort (cost=4002741.20..4035353.91 rows=13045086 width=22)
Sort Key: d.location, d."timestamp", d.param_id, d.ref_time DESC
-> Hash Join (cost=0.11..1925948.51 rows=13045086 width=22)
Hash Cond: (d.ref_time = s.ref_time)
-> Seq Scan on data_table d (cost=0.00..1697659.40 rows=26090171 width=22)
Filter: ("timestamp" >= $1)
-> Hash (cost=0.08..0.08 rows=2 width=4)
-> CTE Scan on stats s (cost=0.00..0.08 rows=2 width=4)
Filter: ready
SubPlan 4
-> Materialize (cost=0.11..2611835.48 rows=39135256 width=18)
-> Hash Join (cost=0.11..2186850.21 rows=39135256 width=18)
Hash Cond: (d_1.ref_time = s_1.ref_time)
-> Seq Scan on data_table d_1 (cost=0.00..1501983.12 rows=78270512 width=18)
-> Hash (cost=0.08..0.08 rows=2 width=4)
-> CTE Scan on stats s_1 (cost=0.00..0.08 rows=2 width=4)
Filter: (NOT settled)
JIT:
Functions: 45
Options: Inlining true, Optimization true, Expressions true, Deforming true
(37 rows)