main independently shipped 021_hashtag_counts_respect_bans and 022_client_upload_idempotency. sqlx::migrate! embeds ./migrations and refuses two files per version, so these three had to move before the branch could merge at all. Contents are untouched — only the version prefixes change. Renumbering (rather than renumbering main's) is the safe direction: main is already deployed, so its 021 and 022 are applied in production and their versions are now immutable.
56 lines
2.6 KiB
SQL
56 lines
2.6 KiB
SQL
-- 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;
|