-- Make a feed page cost a page, not the whole event. -- -- The previous definition (016) computed like_count/comment_count with LEFT JOINs and a -- GROUP BY. Postgres CAN push the `event_id = $1` qual and the keyset predicate through the -- view — verified with EXPLAIN, it uses idx_upload_event_created_id — but it CANNOT push -- ORDER BY ... LIMIT across a GroupAggregate. So every feed request aggregated every upload in -- the event (times its likes and comments) and only then sorted and took 21 rows. The cost -- grew with the event, not with the page, and page 1 — the most expensive one — is exactly -- what refreshFeedInPlace refetches on every completed upload, from every open feed in the -- venue. -- -- Correlated scalar subqueries move the counts ABOVE the Limit in the plan: they are evaluated -- once per returned row, so 21 index lookups instead of a full aggregation. -- -- The rewrite is EXACTLY equivalent, not merely close: -- * "like" is keyed (upload_id, user_id), so COUNT(DISTINCT l.user_id) == count(*). -- * comment.id is the primary key, so COUNT(DISTINCT c.id) == count(*). -- * one row per upload either way — the GROUP BY was on u.id. -- Column names, order and types are unchanged (count(*) and COUNT(DISTINCT ...) are both -- bigint), so no Rust code changes. -- -- No new index needed: idx_like_upload plus the (upload_id, user_id) PK serve the like -- subquery, and idx_comment_upload ... WHERE deleted_at IS NULL matches the comment -- subquery's predicate exactly. -- -- One thing a future editor needs to know: the hashtag-filtered feed joins upload_hashtag -- against this view. That was safe before only because the GROUP BY collapsed the join -- fan-out; it is safe now because the view is one row per upload and upload_hashtag is keyed -- (upload_id, hashtag_id) with a single tag filtered. Adding a second tag filter would need -- fresh thought. -- Not CASCADE: if something ever comes to depend on this view, the migration should fail -- loudly rather than silently drop it. DROP VIEW IF EXISTS v_feed; CREATE VIEW v_feed AS SELECT u.id, u.event_id, u.user_id, usr.display_name AS uploader_name, usr.is_banned, usr.uploads_hidden, u.preview_path, u.thumbnail_path, u.display_path, u.mime_type, u.caption, u.created_at, (SELECT count(*) FROM "like" l WHERE l.upload_id = u.id) AS like_count, (SELECT count(*) FROM comment c WHERE c.upload_id = u.id AND c.deleted_at IS NULL) AS comment_count FROM upload u JOIN "user" usr ON u.user_id = usr.id WHERE u.deleted_at IS NULL AND usr.uploads_hidden = FALSE AND usr.is_banned = FALSE;