I reviewed EXPLAIN ANALYZE
output for a query and found a couple subqueries were scanning the whole table. This is done to get only the most recent record in those tables related to an appointment.
After some research, I decided that it would be reasonable to use a lateral join on those subqueries to dramatically reduce the amount of data scanned. EXPLAIN ANALYZE
suggested the cost of the whole query with lateral joins would be about a quarter of the original. So we proceeded.
Within two hours of deploying the query change, our DB server was maxed out at 100% and basically unresponsive. Reverting the query to use subqueries scanning the tables restored the CPU usage to something sane. Our DB is running in AWS RDS for PostgreSQL using a t2.xlarge. Performance insights showed a substantial increase in ClientWrite. See .
The query using subqueries along with the EXPLAIN
output: https://explain.depesz.com/s/wES6.
select appointments.*,
reportSnapshots.created_at as latestSnapshotTime,
responses.created_at as latestResponseTime
from appointments
left join (
SELECT DISTINCT ON (appointmentId) created_at, appointmentId
FROM reportSnapshots
ORDER BY appointmentId, created_at DESC
) reportSnapshots on appointments.id = reportSnapshots.appointmentId
left join (
SELECT DISTINCT ON (appointmentId) created_at, appointmentId
FROM responses
ORDER BY appointmentId, created_at DESC
) responses on appointments.id = responses.appointmentId
where appointments.organizationId = 16 and appointments.locationId = '51'
and appointments.cancelled = false and appointments.filteredIn = true
and start between '2021-05-04T00:00:00-06:00' and '2021-05-04T23:59:59-06:00'
and appointments.locationId in (61,60,140,53,138,130,133,131,55,51,100)
group by appointments.id,
reportSnapshots.created_at,
responses.created_at
order by start ASC, start ASC, id ASC
limit 100
The query using lateral join along with the EXPLAIN
output: https://explain.depesz.com/s/B2vp.
select appointments.*,
reportSnapshots.created_at as latestSnapshotTime,
responses.created_at as latestResponseTime
from appointments
left join lateral (
SELECT DISTINCT ON (appointmentId) created_at, appointmentId
FROM reportSnapshots
WHERE reportSnapshots.appointmentId = appointments.id
ORDER BY appointmentId, created_at DESC
) reportSnapshots on appointments.id = reportSnapshots.appointmentId
left join lateral (
SELECT DISTINCT ON (appointmentId) created_at, appointmentId
FROM responses
WHERE responses.appointmentId = appointments.id
ORDER BY appointmentId, created_at DESC
) responses on appointments.id = responses.appointmentId
where appointments.organizationId = 16 and appointments.locationId = '51'
and appointments.cancelled = false and appointments.filteredIn = true
and start between '2021-05-04T00:00:00-06:00' and '2021-05-04T23:59:59-06:00'
and appointments.locationId in (61,60,140,53,138,130,133,131,55,51,100)
group by appointments.id,
reportSnapshots.created_at,
responses.created_at
order by start ASC, start ASC, id ASC
limit 100
Obviously, I did not understand what the EXPLAIN
output was telling me about the queries. What did I miss that could have told me the DB load would be higher with the lateral join query despite a lower cost?