DROP INDEX
completely drops the index.
The transaction has to be committed before it takes effect for new queries, but that's not normally an issue. You are probably seeing other testing artifacts like:
- Postgres flips to a different query plan after statistics have slightly changed. That would indicate that your cost settings might be inappropriate or some other poor configuration.
- Repeated execution of the query has populated the cache (which can make a big difference for big tables). For halfway comparable results run all candidates a couple of times.
- Your query is based on "the last ten minutes". There can be 1000 rows and 10 minutes later, there can be just 1. Could make a big difference.
Query
For starters, remove completely unnecessary parts:
SELECT COUNT(*) AS count
FROM (
SELECT 1
FROM loc_zones z
JOIN loc_zone_events ze ON ze.zone_id = z.id
JOIN raw_events r ON r.id = ze.raw_event_id
WHERE z.app_id = 1
AND round(EXTRACT('epoch' FROM NOW() - ze.timestamp) / 60) BETWEEN 0 AND 10
GROUP BY r.device_id
) AS t;
Or:
SELECT COUNT(DISTINCT r.device_id) AS count
FROM loc_zones z
JOIN loc_zone_events ze ON ze.zone_id = z.id
JOIN raw_events r ON r.id = ze.raw_event_id
WHERE z.app_id = 1
AND round(EXTRACT('epoch' FROM NOW() - ze.timestamp) / 60) BETWEEN 0 AND 10
(Not necessarily faster, count(DISTINCT col)
is no performance hero.)
But there is more:
Your WHERE
condition round(...)
is not sargable. To retrieve events of "the last 10 minutes", use instead:
...
AND ze.timestamp >= now() - interval '10 min'
AND ze.timestamp < now(); -- only if there can be timestamps in the future
This is sargable and can use an index on ze.timestamp
.
Note: Your expression was using round()
instead of trunc()
, which effectively
covers the range (-0.5, 10.5)
, which are 11 minutes (not 10), or 10.5 minutes if there are no future timestamps. Deal with that difference one way or the other ...
Index
Since only the last 10 minutes seem to be relevant, you could further improve that with a partial index. The special difficulty here is the moving time frame. This related answer has a complete solution for that:
Building on that, you then have a partial index like:
CREATE INDEX ze_timestamp_recent_idx ON tbl (timestamp DESC);
WHERE created_at > f_min_ts();
And adapt the query like:
WHERE ...
AND ze.timestamp > f_min_ts() -- to match partial index
AND ze.timestamp >= now() - interval '10 min'
AND ze.timestamp < now();
Aside: don't use the basic type name timestamp
as column name.