perf(api): collapse N+1 in /api/artists/{id} + 1 round-trip in /api/albums/{id}
Two new sqlc queries replace three sequential per-album round trips that were dominating detail-screen latency. GetAlbumWithArtist: handleGetAlbum was doing GetAlbumByID then GetArtistByID — separate round trips for one logical lookup. The new query joins albums + artists with sqlc.embed and returns both in one SELECT. Detail-page DB cost: 3 trips → 2. ListAlbumsByArtistWithTrackCount: handleGetArtist was loading the artist's album list, then issuing one CountTracksByAlbum per album to populate track_count. On a 30-album artist that's 32 sequential queries — each ~5ms over a local DB, ~30ms over a remote one. The new query embeds the album row + a correlated count(*) subquery, so every album's track count comes back in one SELECT regardless of album count. Detail-page DB cost: 1 + N → 1 + 1. Together these account for the bulk of cold-cache navigation latency on the Flutter client. Combined with the existing SWR + nav hydration on the client side, detail screens should render their header instantly and the body within one round trip instead of N+constant.
This commit is contained in:
@@ -14,6 +14,26 @@ RETURNING *;
|
||||
-- name: GetAlbumByID :one
|
||||
SELECT * FROM albums WHERE id = $1;
|
||||
|
||||
-- name: GetAlbumWithArtist :one
|
||||
-- Combined fetch for /api/albums/{id}: returns the album row + the
|
||||
-- joined artist name in a single round trip. Replaces a sequential
|
||||
-- GetAlbumByID + GetArtistByID pair on the hot detail-page path.
|
||||
SELECT sqlc.embed(albums), artists.name AS artist_name
|
||||
FROM albums
|
||||
JOIN artists ON artists.id = albums.artist_id
|
||||
WHERE albums.id = $1;
|
||||
|
||||
-- name: ListAlbumsByArtistWithTrackCount :many
|
||||
-- Replaces the N+1 pattern in handleGetArtist (1 + N CountTracksByAlbum
|
||||
-- per album). Returns each album joined with its track count via a
|
||||
-- correlated subquery — single round trip regardless of album count.
|
||||
SELECT sqlc.embed(albums),
|
||||
(SELECT count(*) FROM tracks t WHERE t.album_id = albums.id)::bigint
|
||||
AS track_count
|
||||
FROM albums
|
||||
WHERE albums.artist_id = $1
|
||||
ORDER BY release_date NULLS LAST, sort_title;
|
||||
|
||||
-- name: GetAlbumByArtistAndTitle :one
|
||||
-- Scanner uses this for the no-mbid dedupe path: resolve-or-create.
|
||||
SELECT * FROM albums WHERE artist_id = $1 AND title = $2 LIMIT 1;
|
||||
|
||||
Reference in New Issue
Block a user